UKFC逆向入门文档二

First Post:

Last Update:

逆向很依赖汇编的功底,所以平时要多研究研究汇编。

逆向题没有办法说系统学习,如果真要系统性的学习的话,那就是整个计算机领域的知识了,所以我们在初学的时候,就是以做题为主(BUU,NSS),遇到什么学什么,有足够的题量之后自然就入门了

BUUCTF [ACTF新生赛2020]easyre

ida的分析风格

初始ida分析结果:

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[12]; // [esp+12h] [ebp-2Eh] BYREF
int v5[3]; // [esp+1Eh] [ebp-22h]
char v6[5]; // [esp+2Ah] [ebp-16h] BYREF
int v7; // [esp+2Fh] [ebp-11h]
int v8; // [esp+33h] [ebp-Dh]
int v9; // [esp+37h] [ebp-9h]
char v10; // [esp+3Bh] [ebp-5h]
int i; // [esp+3Ch] [ebp-4h]

__main();
qmemcpy(v4, "*F'\"N,\"(I?+@", sizeof(v4));
printf("Please input:");
scanf("%s", v6);
if ( v6[0] != 65 || v6[1] != 67 || v6[2] != 84 || v6[3] != 70 || v6[4] != 123 || v10 != 125 )
return 0;
v5[0] = v7;
v5[1] = v8;
v5[2] = v9;
for ( i = 0; i <= 11; ++i )
{
if ( v4[i] != _data_start__[*((char *)v5 + i) - 1] )
return 0;
}
printf("You are correct!");
return 0;
}

v6是一个char类型的数组,他的大小很明显不可能是5,应该是17(6+11),而ida分析错误,分析为了5个char加3个int,(5*1+3 *4也是17),所以我们可以手动更改一下v6的大小,按y键,将大小改为17即可,更改后效果如下:

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[12]; // [esp+12h] [ebp-2Eh] BYREF
int v5[3]; // [esp+1Eh] [ebp-22h]
char v6[17]; // [esp+2Ah] [ebp-16h] BYREF
char v7; // [esp+3Bh] [ebp-5h]
int i; // [esp+3Ch] [ebp-4h]

__main();
qmemcpy(v4, "*F'\"N,\"(I?+@", sizeof(v4));
printf("Please input:");
scanf("%s", v6);
if ( v6[0] != 65 || v6[1] != 67 || v6[2] != 84 || v6[3] != 70 || v6[4] != 123 || v7 != 125 )
return 0;
v5[0] = *(_DWORD *)&v6[5];
v5[1] = *(_DWORD *)&v6[9];
v5[2] = *(_DWORD *)&v6[13];
for ( i = 0; i <= 11; ++i )
{
if ( v4[i] != _data_start__[*((char *)v5 + i) - 1] )
return 0;
}
printf("You are correct!");
return 0;
}

这个时候v7,8,9就消失了,成为了v6数组内的一部分,更好看

再说一下这个部分:

1
2
3
4
5
for ( i = 0; i <= 11; ++i )
{
if ( v4[i] != _data_start__[*((char *)v5 + i) - 1] )
return 0;
}

v5可以看到上面的定义为int类型,而在下面的if条件中,左边是v4数组,是char类型的,右边是v5,它本身是int类型的,并且调用他的方法是v5+i,即数组首元素地址加偏移量的方式。(说到这,插一句:c语言中的指针知识在初学逆向时是很重要的)前面还有一个(char*),这个就是强制类型转换,将一个int *的v5强制按照char *的方式来读取其内存中的东西,这样就可以按照一个字节的方式依次取出v5里存放的东西了

简单逻辑逆向

解密脚本如下:

1
2
3
4
5
6
7
8
9
10
11
v4 = [42,70,39,34,78,44,34,40,73,63,43,64]

model = r"}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)(" + chr(0x27) + r'&%$# !"'

pos = []

for i in v4:
pos.append(model.find(chr(i))+1)
s = [chr(x + 1) for x in pos]
flag = ''.join(s)
print ('flag{'+flag+'}')

BUUCTF [GXYCTF2019]luck_guy

linux远程动调

linux端:

  • 将ida文件下IDA_Pro_7.7\dbgsrv文件夹下的linux_server(64)放到linux里
  • 查看linux的ip:ifconfig
  • 给权限:chmod 777 linux_server64
  • 运行:./linux_server64

windows端:

  • f9:remote linux debugger
  • hostname里填写刚刚查到的ip地址,其余不用管

动调起来后就可以随意修改代码逻辑

简单逻辑逆向

初步看逻辑,就是他定义了一个随机数,这个随机数必须先走case4,再走case5,最后走case1,就可以成功的把flag打印出来,那么到这里就有两个思路,一个是仿写一下case4,5的代码逻辑,另一种是让程序执行到case4那里,再执行case5,1,让他自己把flag打印出来

修改代码需要在汇编层面,汇编可以尝试啃啃书或者视频课,但重要的是多看。

1
2
3
4
5
6
7
8
9
flag="GXY{do_not_"
f2=[0x7F,0x66,0x6F,0x60,0x67,0x75,0x63,0x69][::-1] #小端序的问题,所以要逆序一下
for j in range(8):
if j%2==1 :
s=chr(f2[j]-2)
else:
s=chr(f2[j]-1)
flag+=s
print (flag)

NSSCTF [GDOUCTF 2023]Check_Your_Luck

z3

简单,就是学习一下z3这个工具的使用

1
2
3
4
5
6
7
8
9
10
11
from z3 import *
s = Solver()
v , w, x, y ,z = Ints('v w x y z')
s.add(v * 23 + w * -32 + x * 98 + y * 55 + z * 90 == 333322)
s.add(v * 123 + w * -322 + x * 68 + y * 67 + z * 32 == 707724)
s.add(v * 266 + w * -34 + x * 43 + y * 8 + z * 32 == 1272529)
s.add(v * 343 + w * -352 + x * 58 + y * 65 + z * 5 == 1672457)
s.add(v * 231 + w * -321 + x * 938 + y * 555 + z * 970 == 3372367)
if s.check() == sat:
print(s.model())
#NSSCTF{4544_123_677_1754_777}

NSSCTF [LitCTF 2023]程序和人有一个能跑就行了

隐藏逻辑(动态调试)

RC4

RC4加密算法与逆向方法简析 - Moominn - 博客园 (cnblogs.com)

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include<stdio.h>
typedef struct _RC4INFO
{
unsigned char s_box[256];
unsigned char t_box[256];
}RC4_INFO, * PRC4_INFO;

void rc4_init(PRC4_INFO prc4, unsigned char key[], unsigned int keylen)
{
int i = 0;
int j = 0;
unsigned char tmp;
if (prc4 == NULL)
{
return;
}
for (i = 0; i < 256; i++)
{
prc4->s_box[i] = i;
prc4->t_box[i] = key[i % keylen];
}
for (i = 0; i < 256; i++)
{
j = (j + prc4->s_box[i] + prc4->t_box[i]) % 256;
tmp = prc4->s_box[i];
prc4->s_box[i] = prc4->s_box[j];
prc4->s_box[j] = tmp;
}
}

void rc4_crypt(unsigned char data[], unsigned int datalen, unsigned char key[], unsigned int keylen)
{
int dn = 0;
int i = 0;
int j = 0;
int t = 0;
unsigned char tmp;

RC4_INFO rc4;
rc4_init(&rc4, key, keylen);

for (dn = 0; dn < datalen; dn++)
{
i = (i + 1) % 256;
j = (j + rc4.s_box[i]) % 256;

tmp = rc4.s_box[i];
rc4.s_box[i] = rc4.s_box[j];
rc4.s_box[j] = tmp;

t = (rc4.s_box[i] + rc4.s_box[j]) % 256;
data[dn] ^= rc4.s_box[t];
}
}

void EntryBuffer(unsigned char data[], unsigned int datalen)
{
//注意这两种写法需要-1的区别
//unsigned char key[] ={ 108,105,116,99,116,102 };//key
unsigned char key[] = "litctf";
rc4_crypt(data, datalen, key, sizeof(key) / sizeof(key[0])-1);
}

int main()
{
unsigned char Hell[] = { 0x8D, 0x6C, 0x85, 0x76, 0x32, 0x72, 0xB7, 0x40, 0x88, 0x7E, 0x95, 0xEE, 0xC5, 0xED, 0x2E, 0x71, 0x37, 0xF1, 0x4A, 0x99,0x35, 0x18, 0xA7, 0xB0, 0x00, 0x96, 0xD1 };

//密文或者是原文

EntryBuffer((unsigned char*)Hell, sizeof(Hell) / sizeof(Hell[0]));
for (int i = 0; i < 28; i++)
{
printf("%c", Hell[i]);
}
//EntryBuffer((unsigned char*)Hell, sizeof(Hell) / sizeof(Hell[0])); //由于异或运算的对合性,RC4加密解密使用同一套算法。
//printf("解密后:pData=%s\n\n", Hell);
return 0;
}

[GDOUCTF 2023]doublegame

交叉引用

动态调试

直接运行程序是一个贪吃蛇游戏,撞壁游戏就结束了。

拖入ida,因为没有main函数,所以通过字符串来定位关键逻辑:

image-20230519220455865

看到挺多0,跟进去,来到了这么一个函数:

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
void __noreturn sub_140012CF0()
{
char *v0; // rdi
__int64 i; // rcx
char v2; // [rsp+20h] [rbp+0h] BYREF
char Buffer[22]; // [rsp+30h] [rbp+10h] BYREF
char v4[44]; // [rsp+46h] [rbp+26h] BYREF
char v5[44]; // [rsp+72h] [rbp+52h] BYREF
char v6[44]; // [rsp+9Eh] [rbp+7Eh] BYREF
char v7[44]; // [rsp+CAh] [rbp+AAh] BYREF
char v8[44]; // [rsp+F6h] [rbp+D6h] BYREF
char v9[44]; // [rsp+122h] [rbp+102h] BYREF
char v10[44]; // [rsp+14Eh] [rbp+12Eh] BYREF
char v11[44]; // [rsp+17Ah] [rbp+15Ah] BYREF
char v12[44]; // [rsp+1A6h] [rbp+186h] BYREF
char v13[66]; // [rsp+1D2h] [rbp+1B2h] BYREF
int j; // [rsp+214h] [rbp+1F4h]
int v15; // [rsp+234h] [rbp+214h]
int v16; // [rsp+254h] [rbp+234h]
int v17; // [rsp+274h] [rbp+254h]
int v18; // [rsp+294h] [rbp+274h]
_DWORD v19[25]; // [rsp+2C0h] [rbp+2A0h] BYREF
char v20[100]; // [rsp+324h] [rbp+304h] BYREF
char v21[828]; // [rsp+388h] [rbp+368h] BYREF
char v22; // [rsp+6C4h] [rbp+6A4h]
int v23; // [rsp+6E4h] [rbp+6C4h]
int v24; // [rsp+704h] [rbp+6E4h]

v0 = &v2;
for ( i = 448i64; i; --i )
{
*(_DWORD *)v0 = -858993460;
v0 += 4;
}
sub_14001141A(&unk_1400290A6);
strcpy(Buffer, "000000000000000000000");
strcpy(v4, "0 0 0 0 0 0 0");
strcpy(&v4[22], "0 0 0 00000 00000 0 0");
strcpy(v5, "0 0 0 0");
strcpy(&v5[22], "0 000 000 0 000 0 0 0");
strcpy(v6, "0 0 0 0 0 0 0 0");
strcpy(&v6[22], "0 0 0 00000 000 000 0");
strcpy(v7, "0 0 0 0 0 0 ");
strcpy(&v7[22], "0 000 0 0 000 0 0 0 0");
strcpy(v8, "0 0 0 0 0 0 0 0 0");
strcpy(&v8[22], "0 00000 000 000 0 0 0");
strcpy(v9, "0 0 0 0 0");
strcpy(&v9[22], "000 0 0 0 000 0 0 0 0");
strcpy(v10, "0 0 0 0 0 0 * 0 0 0 0");
strcpy(&v10[22], "0 0000000 0 000 00000");
strcpy(v11, "@ 0 0 0");
strcpy(&v11[22], "0 0 0 0 0 00000000000");
strcpy(v12, "0 0 0 0 0");
strcpy(&v12[22], "000 0 00000 0 000 000");
strcpy(v13, "0 0 0 0 0");
strcpy(&v13[22], "000000000000000000000");
v11[4] = 48;
strcpy((char *)v19, "Please to save the cat!");
memset(&v19[6], 0, 0x4Cui64);
strcpy(v20, "the score is saving cat's key!\n");
memset(&v20[32], 0, 0x44ui64);
qmemcpy(v21, &unk_14001D340, 0x47ui64);
memset(&v21[71], 0, 729);
sub_1400111F9("path\n");
v23 = 0;
v24 = 0;
v15 = 15;
v16 = 0;
v17 = 7;
v18 = 20;
for ( j = 0; j <= 20; ++j )
puts(&Buffer[22 * j]);
sub_1400111F9("Please to save the cat!\n");
while ( v15 != v17 || v16 != v18 )
{
v22 = getchar();
switch ( v22 )
{
case 's':
if ( Buffer[22 * v15 + 22 + v16] != 48 )
{
Buffer[22 * v15++ + v16] = 32;
Buffer[22 * v15 + v16] = 64;
}
break;
case 'w':
if ( Buffer[22 * v15 - 22 + v16] != 48 )
{
Buffer[22 * v15-- + v16] = 32;
Buffer[22 * v15 + v16] = 64;
}
break;
case 'a':
if ( Buffer[22 * v15 - 1 + v16] != 48 )
{
if ( Buffer[22 * v15 - 1 + v16] == 42 )
v7[20] = 48;
Buffer[22 * v15 + v16--] = 32;
Buffer[22 * v15 + v16] = 64;
}
break;
default:
if ( v22 == 100 && Buffer[22 * v15 + 1 + v16] != 48 )
{
Buffer[22 * v15 + v16++] = 32;
Buffer[22 * v15 + v16] = 64;
}
break;
}
system("cls");
for ( j = 0; j <= 20; ++j )
puts(&Buffer[22 * j]);
puts((const char *)&v19[25 * v23]);
if ( v7[20] == 48 )
{
v24 = sub_140011433(0i64);
if ( v24 == 13376013 )
{
v23 = 1;
v7[20] = 32;
Buffer[22 * v15 + v16] = 32;
v15 = 15;
v16 = 0;
v11[0] = 64;
++v23;
}
else
{
sub_1400111F9("error");
}
}
}
system("cls");
Sleep(0x1F4u);
Sleep(0xBB8u);
sub_140011492();
exit(0);
}

很明显是一个迷宫,但刚才的贪吃蛇哪去了(因为是double game啦),那么就再交叉引用看看谁调用了这个迷宫的函数,一直可以追到贪吃蛇游戏里:

image-20230519220541425

这个dword_140022CD0就是当前得分了,当它>13371337时就会跳转到迷宫游戏

逻辑清楚了,那么在判断分数前下个断点,贪吃蛇游戏结束之后断下来,修改两次标志位(sf)跳转到迷宫游戏

接下来通过wsad控制上下左右走迷宫,先把小猫救出来后,程序会让输入key(其实就是贪吃蛇分数13371337),也可以通过判断key的逻辑再把key逆出来:

1
2
3
4
if ( v7[20] == '0' )
{
v25 = sub_7FF777D31433(0i64);
if ( v25 == 13376013 )
1
2
3
4
5
6
7
8
9
10
11
__int64 __fastcall sub_7FF777D31E10(unsigned int a1)
{
unsigned int v2; // [rsp+120h] [rbp+100h] BYREF

v2 = a1;
sub_7FF777D3141A(&unk_7FF777D490A6);
sub_7FF777D311F9("do you know the key to open the door\n");
sub_7FF777D3126C("%d", &v2);
v2 ^= 0x1DC4u;
return v2;
}

所以为了使v25=13376013,需要v2^0x1dc4=13376013,那么key就是13376013^13376013=13371337

输入key之后,又回到原点了,再走一下迷宫,flag就是{key+md5(最短路径)}