0x00前言 终于走出ADWorld的新手区了,逆向和PWN将同期不同时进行更新。。。
快考试了,说实话压力有点大😭
不说了,上题!
0x01 EasyRE 先审题,经典Easy了。。一看难度才两颗星,盲猜不简单。。
拖入IDA,shift
+f12
查看字符串,跟进关键函数
关键代码如下
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 v7 = 0 i64; v8 = 0 i64; sub_401050((const char *)&unk_402158, (unsigned int )&v7); v0 = strlen ((const char *)&v7); if ( v0 >= 0x10 && v0 == 24 ) { v1 = 0 ; v2 = (char *)&v8 + 7 ; do { v3 = *v2--; byte_40336C[v1++] = v3; } while ( v1 < 24 ); v4 = 0 ; do { byte_40336C[v4] = (byte_40336C[v4] + 1 ) ^ 6 ; ++v4; } while ( v4 < 0x18 ); v5 = strcmp (byte_40336C, (const char *)&unk_402124); if ( v5 ) v5 = -(v5 < 0 ) | 1 ; if ( !v5 ) { sub_401020("right\n" , v7); system("pause" ); } }
程序分为上下两个do while循环,第二个逻辑很简单,关键点:byte_40336C[v4] = (byte_40336C[v4] + 1) ^ 6
第一个循环似乎每个变量都没有明确指向,很难理解
终极解决方案:看汇编
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 .text:00401100 loc_401100: ; CODE XREF: sub_401080+8E↓j .text:00401100 mov al, [esi] .text:00401102 lea esi, [esi-1] .text:00401105 mov byte_40336C[edx], al .text:0040110B inc edx .text:0040110C cmp edx, ecx .text:0040110E jl short loc_401100 .text:00401110 pop esi .text:00401111 .text:00401111 loc_401111: ; CODE XREF: sub_401080+6F↑j .text:00401111 xor edx, edx .text:00401113 .text:00401113 loc_401113: ; CODE XREF: sub_401080+A6↓j .text:00401113 mov al, byte_40336C[edx] .text:00401119 inc al .text:0040111B xor al, 6 .text:0040111D mov byte_40336C[edx], al .text:00401123 inc edx .text:00401124 cmp edx, ecx .text:00401126 jb short loc_401113 .text:00401128 mov ecx, offset unk_402124 .text:0040112D mov eax, offset byte_40336C .text:00401132
其余代码不必理会,主要看关键点,在loc_401100
中,从高地址到低地址 ,依次将esi存放的地址值所指向的数据 放入byte_40336C[edx]
中,所以此处操作(也就是第一个do while循环)是将输入的字符进行倒叙。
∴函数逻辑是将输入字符进行倒叙,然后分别+1并与6异或,最终数据为(const char *)&unk_402124
的值。
脚本如下:
1 2 3 4 5 6 a = [0x78 , 0x49 , 0x72 , 0x43 , 0x6A , 0x7E , 0x3C , 0x72 , 0x7C , 0x32 , 0x74 , 0x57 , 0x73 , 0x76 , 0x33 , 0x50 , 0x74 , 0x49 , 0x7F , 0x7A , 0x6E , 0x64 , 0x6B , 0x61 ,0x00 ,0x00 ,0x00 ,0x00 ] print(len(a)) flag = '' for i in range(len(a)-1 ,-1 ,-1 ): flag += chr((a[i] ^ 6 ) - 1 ) print(flag)
结果:flag{xNqU4otPq3ys9wkDsN}
0x02 Shuffle 签到题,看代码
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 53 54 55 v50 = __readgsdword(0x14 u); s = 83 ; v11 = 69 ; v12 = 67 ; v13 = 67 ; v14 = 79 ; v15 = 78 ; v16 = 123 ; v17 = 87 ; v18 = 101 ; v19 = 108 ; v20 = 99 ; v21 = 111 ; v22 = 109 ; v23 = 101 ; v24 = 32 ; v25 = 116 ; v26 = 111 ; v27 = 32 ; v28 = 116 ; v29 = 104 ; v30 = 101 ; v31 = 32 ; v32 = 83 ; v33 = 69 ; v34 = 67 ; v35 = 67 ; v36 = 79 ; v37 = 78 ; v38 = 32 ; v39 = 50 ; v40 = 48 ; v41 = 49 ; v42 = 52 ; v43 = 32 ; v44 = 67 ; v45 = 84 ; v46 = 70 ; v47 = 33 ; v48 = 125 ; v49 = 0 ; v3 = time(0 ); v4 = getpid(); srand(v3 + v4); for ( i = 0 ; i <= 99 ; ++i ) { v5 = rand() % 0x28 u; v6 = rand() % 0x28 u; v7 = *(&s + v5); *(&s + v5) = *(&s + v6); *(&s + v6) = v7; } puts (&s); return 0 ; }
将上面奇怪的变量的ASCii转成字符,
结果:SECCON{Welcome to the SECCON 2014 CFT!}
0x03 re-for-50-plz-50 打开程序,发现IDA无法反编译成C语言
之前没做过MIPS的题目,附上MIPS常用指令集 ,和汇编差距挺大的,不太好理解,好吧我没太看懂
但是可以看到有个异或操作,盲猜一下,对逐个字符串进行异或
1 2 3 4 5 6 a = 'cbtcqLUBChERV[[Nh@_X^D]X_YPV[CJ' flag = '' for i in range(len(a)): flag += chr(ord(a[i]) ^0x37 ) print(flag)
结果出来了TUCTF{but_really_whoisjohngalt}
回头一定补一下MIPS的知识,下次一定…
0x04 dmd-50 又是考验小细节
先看关键代码
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 v43 = __readfsqword(0x28 u); std ::operator <<<std ::char_traits<char >>(&std ::cout , "Enter the valid key!\n" , envp); std ::operator >><char ,std ::char_traits<char >>(&edata, &v42); std ::allocator<char >::allocator(&v38); std ::string ::string (&v39, &v42, &v38); md5((MD5 *)&v40, (const std ::string *)&v39); v41 = (_BYTE *)std ::string ::c_str((std ::string *)&v40); std ::string ::~string ((std ::string *)&v40); std ::string ::~string ((std ::string *)&v39); std ::allocator<char >::~allocator(&v38); if ( *v41 != '7' || v41[1 ] != '8' || v41[2 ] != '0' || v41[3 ] != '4' || v41[4 ] != '3' || v41[5 ] != '8' || v41[6 ] != 'd' || v41[7 ] != '5' || v41[8 ] != 'b' || v41[9 ] != '6' || v41[10 ] != 'e' || v41[11 ] != '2' || v41[12 ] != '9' || v41[13 ] != 'd' || v41[14 ] != 'b' || v41[15 ] != '0' || v41[16 ] != '8' || v41[17 ] != '9' || v41[18 ] != '8' || v41[19 ] != 'b' || v41[20 ] != 'c' || v41[21 ] != '4' || v41[22 ] != 'f' || v41[23 ] != '0' || v41[24 ] != '2' || v41[25 ] != '2' || v41[26 ] != '5' || v41[27 ] != '9' || v41[28 ] != '3' || v41[29 ] != '5' || v41[30 ] != 'c' || v41[31 ] != '0' )
提交这串字符,答案错误
发现上面有个md5加密
所以程序是将输入的结果进行md5加密之后再和这个字符串进行比较
所以将其解密即可
提交,还是错误…
知识盲区 :md5(md5($pass)) :这是md5的变种算法,第一次加密后,结果转换成小写,对结果再加密一次
则解密为:解密一次后,再解密一次
但这题只加密了一次,所以需对grape再加密一次
所以结果:flag{b781cbb29054db12f88f08c6e161c199}
Tips: 舍友x1ngg3
提供了一个特别好用的网址,md5解密 ,人傻了…
0x05 Mysterious 拿到程序,先运行一下
感觉要动态调试,有点害怕,先用常规做法试一下
32位,IDA ,找字符串
跟踪到函数,找到关键代码
这里的知识点在于两个C库的函数:
int atoi(const char \*str)
把参数 str 所指向的字符串转换为一个整数(类型为 int 型)
itoa(number, string, radix)
把一个整数转换为字符串
number:要转换的数据;string:目标字符串的地址;radix:转换后的进制数,可以是10进制、16进制等。
由于两个函数长得比较像,我一度以为程序逻辑混乱…
因此,存在两种解法
1.
输入atoi转整形得到结果+1等于123,则输入开始的数字部分为122
所以输入122xyz
即可
2._itoa()
函数可知,v5是由int型变量v10转换成的字符串,已知v10==123,所以把flag拼接起来即可
flag{123_Buff3r_0v3rf|0w}
至此进阶区做完第一行
0x06 Windows_Reverse1 这题真的研究了好久,有意思,脑洞很大,适合细品
upx -d
脱壳,
脱完之后有点小问题,无法运行,影响不大,但是OD动调不了
其实程序开启了ASLR(地址随机化), 并且出题人又在程序中采用了绝对地址的方式, 所以程序才不能正常运行.
需将该PE文件的IMAGE_OPTIONAL_HEADER\DllCharacteristics中的MAGE_DLLCHARACTERISTICS_DYNAMIC_BASE标志去掉即可. 即将PE中8140的数据改为8100
现在开始进入正题
拖入IDA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int __cdecl main (int argc, const char **argv, const char **envp) { char v4; char v5; char v6; char Dst; v6 = 0 ; memset (&Dst, 0 , 0x3FF u); v4 = 0 ; memset (&v5, 0 , 0x3FF u); printf ("please input code:" ); scanf ("%s" , &v6); sub_401000(&v6); if ( !strcmp (&v4, "DDCTF{reverseME}" ) ) printf ("You've got it!!%s\n" , &v4); else printf ("Try again later.\n" ); return 0 ; }
主函数不难,输入v6,然后经过sub_401000(&v6)函数之后,出来v4与字符串DDCTF{reverseME}
比较
如果相同则验证成功
查看一下v6与v4在栈中的情况(v6即var_404 ,v4即var_804)
先不管别的,跟进sub_401000(&v6)函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 unsigned int __cdecl sub_401000 (const char *a1) { _BYTE *v1; unsigned int v2; unsigned int result; int v4; v2 = 0 ; result = strlen (a1); if ( result ) { v4 = a1 - v1; do { *v1 = byte_402FF8[(char )v1[v4]]; ++v2; ++v1; result = strlen (a1); } while ( v2 < result ); } return result; }
疑惑来了,a1很清楚这是输入的字符串,那么v4,v1,以及byte_402FF8[(char)v1[v4]]是什么意思呢?
第一感觉就是伪代码表明很不明确,
应该看一下汇编,图中做了注释
看懂汇编之后,再回到sub_401000函数
那么a1和v1就联系起来了,可以理解为v6就是a1,v4就是v1
函数内部又定义了一个v4= a1-v1,即输入字符串和v1字符串的地址差值
这里用了一种从未见过的索引方法,其实 v1[v4]
和v1+v4
是等价的, 而在循环刚开始的时候v1+v4
等于a1
, 随着v1
的递增, v1[v4]
也会遍历a1
数组中的各个元素的地址。
也可以理解为将a1字符本身作为索引,去相应的地址取数据进行替换。
跟进0x402FF9的位置
可以看到下面有一堆数据,和402FF9地址相差32位,为什么呢?
联想到ASCII表中的可见字符正是从第32位开始的,所以程序逻辑明了了
通过输入字符串的ascii码作为索引,来到该表中的数据进行替换,最后与预定字符串进行比较
脚本如下:
1 2 3 4 5 6 7 8 9 s = ' ' *32 + '~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)(\'&%$#"!' ss = 'DDCTF{reverseME}' flag = '' for i in range(len(ss)): flag += chr(s.index(ss[i])) print(flag)
出题人脑洞不小,,,逆向题整出pwn的感觉
学到了,不容易