ADWorld_RE (updating..)

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 = 0i64;
v8 = 0i64;
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):#len(a)=28,范围是0-27,所以len(a)需要-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(0x14u);
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() % 0x28u;
v6 = rand() % 0x28u;
v7 = *(&s + v5);
*(&s + v5) = *(&s + v6);
*(&s + v6) = v7;
}
puts(&s);
return 0;
}

将上面奇怪的变量的ASCii转成字符,

image-20200604170208998

结果:SECCON{Welcome to the SECCON 2014 CFT!}


0x03 re-for-50-plz-50

打开程序,发现IDA无法反编译成C语言

image-20200604174520680

之前没做过MIPS的题目,附上MIPS常用指令集,和汇编差距挺大的,不太好理解,好吧我没太看懂

但是可以看到有个异或操作,盲猜一下,对逐个字符串进行异或

1
2
3
4
5
6
a = 'cbtcqLUBChERV[[Nh@_X^D]X_YPV[CJ'
#print(len(a))
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(0x28u);
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加密之后再和这个字符串进行比较

所以将其解密即可

image-20200604182904416

提交,还是错误…

知识盲区:md5(md5($pass)) :这是md5的变种算法,第一次加密后,结果转换成小写,对结果再加密一次

则解密为:解密一次后,再解密一次

但这题只加密了一次,所以需对grape再加密一次

image-20200604184503509

所以结果:flag{b781cbb29054db12f88f08c6e161c199}

Tips: 舍友x1ngg3提供了一个特别好用的网址,md5解密 ,人傻了…


0x05 Mysterious

拿到程序,先运行一下

image-20200615205826004

感觉要动态调试,有点害怕,先用常规做法试一下

32位,IDA ,找字符串

image-20200615205736269

跟踪到函数,找到关键代码

image-20200615210342429

这里的知识点在于两个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

这题真的研究了好久,有意思,脑洞很大,适合细品

image-20200624005331848

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; // [esp+4h] [ebp-804h]
char v5; // [esp+5h] [ebp-803h]
char v6; // [esp+404h] [ebp-404h]
char Dst; // [esp+405h] [ebp-403h]

v6 = 0;
memset(&Dst, 0, 0x3FFu);
v4 = 0;
memset(&v5, 0, 0x3FFu);
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)

image-20200624010923376

image-20200624010941085

先不管别的,跟进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; // ecx
unsigned int v2; // edi
unsigned int result; // eax
int v4; // ebx

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]]是什么意思呢?

第一感觉就是伪代码表明很不明确,

应该看一下汇编,图中做了注释

image-20200624012926510

看懂汇编之后,再回到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的位置

image-20200624015346354

可以看到下面有一堆数据,和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)


#str1.index(str2[i])表示str2中的字符在str1中的序号

出题人脑洞不小,,,逆向题整出pwn的感觉

学到了,不容易