注意
本文最后更新于 2024-02-12,文中内容可能已过时。
UNCTF2020 Reverse Writeup
队伍:打CTF不靠实力靠运气
作者:wjhwjhn
re_checkin
反编译
运行程序。
babypy
正解:
反编译出pyc,修改头部字节,得到py文件,分析py文件流程
我的解:
看到很多3130,想到ASCII中的1和0
替换文本,31换成1,30换成0
2进制转16进制
16进制转文本
easyMaze
静态分析:
开头unctf{ ,结尾 }
wasd控制方向,碰到0或者D死亡,走到S逃脱。迷宫是10 * 10大小的
动态分析:
得到迷宫,把0都替换成D便于识别
DoDDoDDDSD DooooDDooo oDDDoDDoDD oooooDDoDD oDDDDooooo oDDoDoDoDo oDoooooDDD oDDoDDoooo oDDDDDDDoD oooooooooD
手动模拟一下路线,然后就得到flag了。
这道题目还有个反调试,利用的是IsDebuggerPresent()。
但是我用静动态分析的方法,就可以绕过了。
ezRust
猜测两个参数分别是YLBNB 和RUSTPROGRAMING
这道题是真的水,估计很多人看到这么多东西就吓坏了。
ICU
发现题目会提供加密后的结果并输出,并且发现加密后的结果存在规律。明文可以与密文对应。
发现代码中密文与这样一串字符进行结果,所以这个就是我们要爆破的密文。
HSWEH2vXHmRtGZRJvSmKviwtviv4Ga5rD25Mvl:u6ewBUKg9
编写python脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import subprocess
import string
def GetKey ( data ):
run ()
p . stdin . write (( data + " \r\n " ) . encode ( "GBK" ))
p . stdin . flush ()
p . stdout . readline () . decode ( "GBK" )
key = p . stdout . readline () . decode ( "GBK" )
p . stdout . close ()
p . stdin . close ()
return key
def run ():
while True :
line = p . stdout . readline ()
if not line :
break
if "Please Input" in line . decode ( "GBK" ):
break
FileIn = r "C:\Users\wjh\Downloads\Compressed\ICU\ICU.exe"
Table = string . ascii_letters + string . digits + "_" + "{" + "}"
target = "HSWEH2vXHmRtGZRJvSmKviwtviv4Ga5rD25Mvl:u6ewBUK"
PrevList = [ "unctf{" ]
GetLen = 0
while True :
GetLen += 1
for i in PrevList :
TempList = []
for j in Table :
p = subprocess . Popen ( FileIn , stdin = subprocess . PIPE ,
stdout = subprocess . PIPE )
key = GetKey ( i + j )
if target [ 0 : GetLen ] == key [ 0 : GetLen ]:
TempList . append ( i + j )
print ( "KeyWord is " , TempList )
PrevList = TempList
if GetLen >= len ( target ):
break
print ( PrevList )
但是只是这样还是不够的,因为有些时候1位明文可以对应到两位密文,所以到后面List的元素就会有几百,这样的效率肯定是不够的,所以我们使用人工调整的方法,让程序最快的运行。
最后的结果就是得到的结果就是:unctf{we_remember_everything_YLBNB!}
我这个肯定是非预期解了,而且我自认为还算是不错的非预期解,起码这样头发不会少几根哈哈哈。
最后补充一下,这样的爆破方法速度并不会比分析逻辑来的慢,我是第二个提交上的,而且没有对程序的加密方法逻辑进行任何的分析。
base_on_rust
发现程序把加密结果与
RzQyVE1SSldHTTNUSU5SV0c1QkRNTVJXR0UzVEdOUlZHTTNER05CVklZM0RFTlJSRzRaVE1OSlRHTVpURU5LR0dZWkRNTUpYR00zREtNWlJHTTNES1JSV0dVM0VLTlJUR1pERE1OQldHVTJVTU5LR0dWRERPUkE9 进行比较。
猜测这是base64加密后的文本
Base64解密后得到
G42TMRJWGM3TINRWG5BDMMRWGE3TGNRVGM3DGNBVIY3DENRRG4ZTMNJTGMZTENKGGYZDMMJXGM3DKMZRGM3DKRRWGU3EKNRTGZDDMNBWGU2UMNKGGVDDORA=
猜测这是base32加密后的文本
Base32解密后得到
756E6374667B6261736536345F6261736533325F6261736531365F656E636F64655F5F5F7D
猜测这是文本转16进制后得到的文本,
解码后得到
unctf{base64_base32_base16_encode___}
ezre
首先确保flag长度为18位。
其次找到这样的一个函数。
猜测该函数的作用是用于比对传入的两个数是否相等。
并且返回的值分别为0、-1、1
通过简单的验证足以说明我的想法是正确的。
前面的代码这么长一堆,那该怎么分析呢。根据这道题目这个函数实现这么简单功能却用了这么复杂的操作的思想,我猜测加密过程和解密过程是一致的,类似于xor的加密方法。
这样的加密方法我只需要把最后比对的加密结果重新当做参数传回函数中,得到的结果也就是解密后的结果了。
由于当时的源码不小心被我弄丢了,所以这里很难进行演示。
又是由于这种猜测方法的速度快的优势,这道题我是第一个完成的
Trap
这道题目需要两步来操作
把libc解密出来 分析libc中的jo_enc函数。 Libc的解密方法是xor加密,我们只需要得到xor的key就可以成功进行解密。
Xor的key也就是s1文本,我们可以看到在这之前s1文本与s2进行了比较,如果相等才能进入这个分支。于是我们去看一下s2的值是多少
可以看到s2的值是这些,于是我第一次就失败了,问题在于sub_400CBE();函数
在此函数中,s1与0x22进行了异或运算,并且用线程的方式调用
该函数首先调用了
先调用了函数然后,对s2进行了0x33异或运算。
猜测这里是一个检测调试的函数,因为运行到这里就会退出。
分析完内层函数,我们再看看线程运行的函数中的另一个。
该函数直接看难以看出逻辑,但是如果动态调试之后,或者观察汇编就可以得知该函数把a1[也就是s1的字符]进行了增加,增加的长度正好是s1的字符串长度。
有了以上分析后,我们就可以得到一个公式
(Input[x] ^ 0x22) + len(input) == Target[x] ^ 0x33
这个式子是我们要求得到的,其中的input就是s1,target就是s2。
所以我们围绕这个式子进行解密,首先将s2的每一个字符异或0x33,得到的数组用于解密libc文件,解密出来后,我们去
/tmp/libunctf.so
,找到此libc文件,并且提取出进行分析。
这是当时写的解密程序,当然由于修改过多,已经不能使用,但是,只要研究过改程序的人都可以理解并使用修改此程序。
主要思路是:
逆向过程中我们可以发现下文中的dd函数,该函数是内嵌在程序中,由于我该函数背后的数学原理,所以难以得到他的反函数,所以我的做法是,把一个大范围内的所有x的值都带入,然后把得到的y都记录下来,当解密的时候就用得到的y去ma中找到对应的x的值,达到解密的效果,当然这也是一种爆破的体现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <cstdio>
#include <cstring>
#include "defs.h"
using namespace std ;
int ma [ 0x7fffff ];
DWORD ida_chars [] = {
0x684 , 0x66E , 0x740 , 0x1016 , 0x76B , 0x6D8 , 0x280 , 0x7D0 , 0x113C , 0x72B , 0x334 , 0x3D8 , 0x3C8 , 0x4A5 , 0x1101 , 0x66E , 0x10FC , 0x11D1 , 0x61C , 0x61E , 0x15DC , 0x5F5 };
DWORD ddx [] = {
0x514 , 0x52e , 0x450 , 0x1356 , 0x40b , 0x5e8 , 0x4b0 , 0x450 , 0x13ec , 0x40b , 0x514 , 0x5e8 , 0x5e8 , 0x675 , 0x1291 , 0x52e , 0x13ec , 0x1291 ,
0x57c , 0x52e , 0x13ec , 0x675 };
DWORD dd ( DWORD a ){ return a & 0xE827490C | ~ a & 0x17D8B6F3 ;}
int main ()
{
for ( int i = 1 ; i < 0x7ffffff ; i ++ )
{
int d = dd ( i );
if ( d >= 400073826 && d < 400080000 ) ma [ dd ( i ) - 400073826 ] = i ;
}
for ( int m = 0 ; m < 22 ; ++ m )
{
char v4 [] = "941463c8-2bcb-" ; //s1
size_t v3 = strlen ( v4 );
DWORD w = ida_chars [ m ] ^ (( 16 * v4 [ m % v3 ]) & 0xE827490C | ~ ( 16 * v4 [ m % v3 ]) & 0x17D8B6F3 );
printf ( "%d ans = %0x \n " , w );
}
int v14 [ 128 ]; // [rsp+140h] [rbp-420h]
int s [ 129 ]; // [rsp+340h] [rbp-220h]
for ( int i = 0 ; i < 128 ; ++ i )
{
s [ i ] = 2 * i ;
v14 [ i ] = 2 * i + 1 ;
}
for ( int j = 0 ; j < 22 ; ++ j )
for ( int v9 = 1 ; v9 <= 128 ; v9 ++ )
{
int sb = 0 ;
if ( v9 % 2 )
{
//偶数
for ( int l = 0 ; l < v9 ; l += 2 )
sb += v14 [ l ];
}
else
{
//奇数
for ( int k = 0 ; k < v9 ; k += 2 )
sb += s [ k ];
}
if ( sb == ddx [ j ])
printf ( "ans = %d, %c \n " , v9 , v9 );
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
400077799 ans = 514
400077789 ans = 52e
400077475 ans = 450
400074149 ans = 1356
400077560 ans = 40b
400077595 ans = 5e8
400077379 ans = 4b0
400077475 ans = 450
400074015 ans = 13ec
400077560 ans = 40b
400077799 ans = 514
400077595 ans = 5e8
400077595 ans = 5e8
400076934 ans = 675
400073826 ans = 1291
400077789 ans = 52e
400074015 ans = 13ec
400073826 ans = 1291
400077711 ans = 57c
400077789 ans = 52e
400074015 ans = 13ec
400076934 ans = 675
这是第一次运行程序的输出,我们只需要把对应的ans填入ddx数组,就可以成功得到最终的解密内容。
串联结果。
第一次的结果: “941463c8-2bcb-”
第二次的结果:“430c-820f-4889a3fa63f9”
最后得到unctf{941463c8-2bcb-430c-820f-4889a3fa63f9}
因为这道题目解出来的人有8个,比我更好的思路肯定有,所以我就不浪费时间写详细了,除非真的需要,可以再联系我。
做Re题用C++的优势就是,大量的代码可以直接拿来用,并且发现规律进行分析(爆破)。