0%

buuctf_网鼎杯singal

查壳

显示无壳,然后拖进IDA

image-20220105103958326

IDA分析

image-20220105160547017

​ 实际上程序运行会首先弹出个string:,我们搜索字符串,然后ctrl+x找到read函数位置,

image-20220105162133474

从unk_403040地址开始作为int数组赋值给v4,由于intel是小端存储,所以每四个字节,从后往前查看字节,也可以在IDA,选中后按D键,不勾选dup。

image-20220105171355771

image-20220105173005912

image-20220105173100556

查看一下主要的函数vm_operad()

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
int __cdecl vm_operad(int *a1, int a2)
{
int result; // eax
char v3[100]; // [esp+13h] [ebp-E5h]
char v4[100]; // [esp+77h] [ebp-81h]
char v5; // [esp+DBh] [ebp-1Dh]
int v6; // [esp+DCh] [ebp-1Ch]
int v7; // [esp+E0h] [ebp-18h]
int v8; // [esp+E4h] [ebp-14h]
int v9; // [esp+E8h] [ebp-10h]
int v10; // [esp+ECh] [ebp-Ch]

v10 = 0;
v9 = 0;
v8 = 0;
v7 = 0;
v6 = 0;
while ( 1 )
{
result = v10;
if ( v10 >= a2 )
return result;
switch ( a1[v10] )
{
case 1:
v4[v7] = v5;
++v10;
++v7;
++v9;
break;
case 2:
v5 = a1[v10 + 1] + v3[v9];
v10 += 2;
break;
case 3:
v5 = v3[v9] - LOBYTE(a1[v10 + 1]);
v10 += 2;
break;
case 4:
v5 = a1[v10 + 1] ^ v3[v9];
v10 += 2;
break;
case 5:
v5 = a1[v10 + 1] * v3[v9];
v10 += 2;
break;
case 6:
++v10;
break;
case 7:
if ( v4[v8] != a1[v10 + 1] )
{
printf("what a shame...");
exit(0);
}
++v8;
v10 += 2;
break;
case 8:
v3[v6] = v5;
++v10;
++v6;
break;
case 10:
read(v3);
++v10;
break;
case 11:
v5 = v3[v9] - 1;
++v10;
break;
case 12:
v5 = v3[v9] + 1;
++v10;
break;
default:
continue;
}
}
}

​ 首先第一次是执行case 10, 这个指令就是read函数操作:读取输入并且检测字符长度是否为15,这个输入就是正确的flag。然后再进行一系列操作,然后在case 7的时候将v4中的值一个一个地与unk_403040的下一个比特一个个比较(**我们可以打印出v10的值去对比一下a1数组中的值:)**)

把代码修改一下

image-20220106172329314

image-20220106172243828

调代码的时候可以看到v4的值其实都是v5的值,v5是由flag和unk_403040算出来的,所以反着算就知道flag啦

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
int decode(int * opcode, int len_114)
{
int i;
//获取flag 的关键数据
unsigned char v4[] = { 0X22, 0X3F, 0X34, 0X32, 0X72, 0X33, 0X18, 0XFFFFFFA7, 0X31, 0XFFFFFFF1, 0X28, 0XFFFFFF84, 0XFFFFFFC1, 0X1E, 0X7A };
//执行opcode 的索引,即执行顺序
char order[100] = { 1, 3, 4, 6, 7, 9, 10, 12, 13, 15, 16, 17, 18, 19, 20, 22, 23, 25, 26, 28, 29, 30, 31, 32, 33, 35, 36, 38, 39, 41, 42, 44, 45, 46, 47, 48, 49, 51, 52, 54, 55, 57, 58, 60, 61, 63, 64, 66, 67, 69, 70, 72, 73, 75, 76, 78, 79, 81, 82, 83, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114 };
unsigned char flag[100] = {};
int m = 15;
int n = 15;
int j = 15;
int v5;
for (int k = strlen(order) - 1; k >= 0 ; k--)
{
i = order[k];
switch (opcode[i]) // 倒序执行
{
case 1:
--m;
--n;
v5 = v4[m];
break;
case 2:
flag[n] = v5 - char(opcode[i + 1]) ;
break;
case 3:
flag[n] = v5 + char(opcode[i + 1]);
break;
case 4:
flag[n] = (v5 ^ opcode[i + 1]) & 0XFF;
break;
case 5:
flag[n] = v5 / opcode[i + 1];
break;
case 6:
break;
case 8:
v5 = flag[--j];
break;
case 11:
flag[n] = v5 + 1;
break;
case 12:
flag[n] = v5 - 1;
break;
}
}

printf("%s", flag);
return 0;
}

flag{757515121f3d478}

小tips:

sub/subs汇编指令是进行减法运算指令。一般在函数内部第一句就是sub指令,是用来开辟内存空间。之前就说过栈空间是从高地址向低地址扩展的,而且是一块连续的空间,sp之前介绍寄存器的时候就说过是栈顶,所以第一条指令就是sp向低地址移动一段距离,也就是开辟一块栈空间。比如sub esp,10h 就是在栈上分配0x10个字节的空间。

我们再说一下IDA7.5和7.0的区别,在分析的时候遇到这样的情况,第一张图是7.5,第二张是7.0,汇编代码都是一样的,都是先取一个数组地址然后将ebp + var_18的值加上(相当于取数组下标),7.5是解析成为一个数组,前100字节和后100字节分别用来存储不同数据,所以意思都一样,注意理解就好。

image-20220106160414061

image-20220106160232346

image-20220106160332076