# 香山杯 - Nesting - 复现

main 里两个函数,一个初始化数据,另一个是 vm 主程序,定义 vmdata 结构体如下

1
2
3
4
5
6
7
8
9
10
11
12
13
struct vmdata
{
uint8_t mem[3072];
uint64_t consts[71];
uint16_t padding[228];
uint16_t stack[256];
uint16_t index;
uint16_t ptr;
uint16_t res;
uint16_t _rsp;
uint16_t unk2;
uint16_t unk3;
};

对程序进行一个大致的分析

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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
__int64 __fastcall vm(vmdata *a1)
{
unsigned int code; // eax
uint16_t v2; // dx
uint16_t ptr; // ax
uint16_t v4; // cx
uint16_t v5; // cx
uint16_t v6; // ax
__int64 result; // rax
uint16_t reg; // [rsp+1Eh] [rbp-2h]

reg = *(&a1->_rsp + a1->mem[a1->index + 2]);
code = a1->mem[a1->index];
if ( code > 0xFC )
return 0;
if ( a1->mem[a1->index] < 0xB0u )
{
if ( a1->mem[a1->index] <= 0xA0u )
{
if ( a1->mem[a1->index] >= 0x80u )
{
switch ( a1->mem[a1->index] )
{
case 128u:
*(&a1->_rsp + a1->mem[a1->index + 1]) = reg;// mov reg,reg
a1->index += 3;
break;
case 129u:
*(&a1->_rsp + a1->mem[a1->index + 1]) = (a1->mem[a1->index + 3] << 8) | a1->mem[a1->index + 2];// mov reg,imm16
a1->index += 4;
break;
case 144u:
*(&a1->_rsp + a1->mem[a1->index + 1]) += reg;// add reg,reg
a1->index += 3;
break;
case 145u:
*(&a1->_rsp + a1->mem[a1->index + 1]) += a1->mem[a1->index + 2] | (a1->mem[a1->index + 3] << 8);// add reg,imm16
a1->index += 4;
break;
case 160u:
*(&a1->_rsp + a1->mem[a1->index + 1]) -= reg;// sub reg,reg
a1->index += 3;
break;
default:
return 0;
}
}
else if ( code == 0x4C )
{
ptr = a1->ptr;
a1->ptr = ptr - 1;
*(&a1->_rsp + a1->mem[a1->index + 1]) = a1->stack[ptr];// pop
a1->index += 2;
}
else if ( a1->mem[a1->index] <= 0x4Cu )
{
if ( a1->mem[a1->index] > 0x29u )
{
if ( code == 0x48 )
{
v2 = *(&a1->_rsp + a1->mem[a1->index + 1]);
a1->stack[++a1->ptr] = v2; // push
a1->index += 2;
}
}
else if ( a1->mem[a1->index] >= 8u )
{
switch ( a1->mem[a1->index] )
{
case 8u:
*(&a1->_rsp + a1->mem[a1->index + 1]) = a1->mem[reg];// mov reg,[reg]
a1->index += 3;
break;
case 0xAu:
*(&a1->_rsp + a1->mem[a1->index + 1]) = (a1->mem[reg + 1] << 8) | a1->mem[reg];// mov reg, word [reg]
a1->index += 3;
break;
case 0x18u:
a1->mem[*(&a1->_rsp + a1->mem[a1->index + 1])] = reg;// mov [reg],reg
a1->index += 3;
break;
case 0x1Au:
a1->mem[*(&a1->_rsp + a1->mem[a1->index + 1])] = reg;// mov word [reg],reg
a1->mem[*(&a1->_rsp + a1->mem[a1->index + 1]) + 1] = HIBYTE(reg);
a1->index += 3;
break;
case 0x28u:
a1->res = reg == *(&a1->_rsp + a1->mem[a1->index + 1]);// cmp reg,reg
a1->index += 3;
break;
case 0x29u:
a1->res = *(&a1->_rsp + a1->mem[a1->index + 1]) == (a1->mem[a1->index + 2]// cmp reg,imm16
| (a1->mem[a1->index + 3] << 8));
a1->index += 4;
break;
default:
return 0;
}
}
}
}
return 0;
}
switch ( a1->mem[a1->index] )
{
case 0xB0u:
*(&a1->_rsp + a1->mem[a1->index + 1]) ^= reg;// xor
a1->index += 3;
return 0;
case 0xC1u:
*(&a1->_rsp + a1->mem[a1->index + 1]) *= a1->mem[a1->index + 2] | (unsigned __int16)(a1->mem[a1->index + 3] << 8);// mul
a1->index += 4;
return 0;
case 0xE0u:
a1->index = a1->mem[a1->index + 1] | (a1->mem[a1->index + 2] << 8);// jmp
return 0;
case 0xE8u:
if ( a1->res )
a1->index = a1->mem[a1->index + 1] | (a1->mem[a1->index + 2] << 8);// jnz
else
a1->index += 3;
return 0;
case 0xECu:
if ( a1->res )
a1->index += 3;
else
a1->index = a1->mem[a1->index + 1] | (a1->mem[a1->index + 2] << 8);// jz
return 0;
case 0xEEu:
v4 = a1->index + 2;
a1->stack[++a1->ptr] = v4;
a1->index = *(&a1->_rsp + a1->mem[a1->index + 1]);// call
return 0;
case 0xEFu:
v5 = a1->index + 3;
a1->stack[++a1->ptr] = v5;
a1->index = a1->mem[a1->index + 1] | (a1->mem[a1->index + 2] << 8);// call
return 0;
case 0xF0u:
v6 = a1->ptr;
a1->ptr = v6 - 1;
a1->index = a1->stack[v6]; // ret
return 0;
case 0xF8u:
result = 0xFFFFFFFFLL; // exit
break;
case 0xFCu:
if ( a1->_rsp )
a1->_rsp = read(0, &a1->mem[a1->unk2], a1->unk3);
else
write(1, &a1->mem[a1->unk2], a1->unk3);
++a1->index;
return 0;
default:
return 0;
}
return result;
}

有别于常规 vm,可以看到里面是模拟了很多类似于 push、pop、call 这样的操作。如果去 trace,打印出来的应该是指令流而不是数据流

一开始尝试写 idc 断点 trace 指令,类似这样:

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
static Handler1()
{
auto fp, line;
fp = fopen("nest.txt", "a");
auto eax = GetRegValue("eax");
fprintf(fp,"0x%X ",eax);
fclose(fp);
}

static Handler2()
{
auto fp, line;
fp = fopen("nest.txt", "a");
fprintf(fp,"mov ");
auto eax = GetRegValue("eax");
if(eax == 0)
{
fprintf(fp,"r0,");
}
if(eax == 1)
{
fprintf(fp,"r1,");
}
if(eax == 2)
{
fprintf(fp,"r2,");
}
if(eax == 3)
{
fprintf(fp,"r3,");
}
if(eax == 4)
{
fprintf(fp,"r4,");
}
auto rbp = GetRegValue("rbp");
auto rdx = GetRegValue("rdx");
auto reg_addr = Word(rbp - 0x18 + 0x1200) + 2;
auto reg = Byte(rdx + reg_addr);
if(reg == 0)
{
fprintf(fp,"r0\n");
}
if(reg == 1)
{
fprintf(fp,"r1\n");
}
if(reg == 2)
{
fprintf(fp,"r2\n");
}
if(reg == 3)
{
fprintf(fp,"r3\n");
}
if(reg == 4)
{
fprintf(fp,"r4\n");
}
fclose(fp);
}

static Handler3()
{
auto fp, line;
fp = fopen("nest.txt", "a");
auto eax = GetRegValue("eax");
fprintf(fp,"0x%X ",eax);
fclose(fp);
}

static Handler4()
{
auto fp, line;
fp = fopen("nest.txt", "a");
fprintf(fp,"mov ");
auto eax = GetRegValue("eax");
if(eax == 0)
{
fprintf(fp,"r0,");
}
if(eax == 1)
{
fprintf(fp,"r1,");
}
if(eax == 2)
{
fprintf(fp,"r2,");
}
if(eax == 3)
{
fprintf(fp,"r3,");
}
if(eax == 4)
{
fprintf(fp,"r4,");
}
auto rcx = GetRegValue("rcx");
fprintf(fp,"%X\n",rcx);
fclose(fp);
}

但是这样打印出来的数据比较多,大概两万行左右,不好分析

所以还是写 vm 解释器查看完整流程

解释器:

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
139
140
141
142
143
144
145
146
147
148
149
150
#include <windows.h>
#include <iostream>
#include <stdint.h>

uint8_t mem[] =
{
0xE0, 0xE1, 0x01, 0xEF, 0xA3, 0x01, 0x91, 0x00, 0x02, 0x00, 0x0A, 0x00, 0x00, 0xF0, 0xEF, 0xA3,
0x01, 0x91, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0xF0, 0xEF, 0xA3, 0x01, 0x91, 0x00, 0x02, 0x00,
0x08, 0x00, 0x00, 0xF0, 0xEF, 0x19, 0x00, 0xEF, 0xAB, 0x01, 0x80, 0x01, 0x00, 0xEF, 0x0E, 0x00,
0xEF, 0xB7, 0x01, 0x81, 0x00, 0x03, 0x00, 0xEF, 0x92, 0x01, 0xF0, 0xEF, 0x03, 0x00, 0x80, 0x01,
0x00, 0xEF, 0x0E, 0x00, 0xEF, 0xB7, 0x01, 0x81, 0x00, 0x04, 0x00, 0xEF, 0x92, 0x01, 0xF0, 0xEF,
0x19, 0x00, 0xEF, 0xAB, 0x01, 0x80, 0x01, 0x00, 0xEF, 0x0E, 0x00, 0xEF, 0xAB, 0x01, 0x18, 0x00,
0x01, 0x81, 0x00, 0x03, 0x00, 0xEF, 0x92, 0x01, 0xF0, 0xEF, 0x19, 0x00, 0xEF, 0xAB, 0x01, 0x08,
0x01, 0x00, 0xEF, 0x0E, 0x00, 0xEF, 0xB7, 0x01, 0x81, 0x00, 0x03, 0x00, 0xEF, 0x92, 0x01, 0xF0,
0xEF, 0x19, 0x00, 0xEF, 0xAB, 0x01, 0x80, 0x01, 0x00, 0xEF, 0x0E, 0x00, 0x48, 0x00, 0xEF, 0xAB,
0x01, 0x90, 0x01, 0x00, 0x4C, 0x00, 0xEF, 0xB7, 0x01, 0x81, 0x00, 0x03, 0x00, 0xEF, 0x92, 0x01,
0xF0, 0xEF, 0x19, 0x00, 0xEF, 0xAB, 0x01, 0x80, 0x01, 0x00, 0xEF, 0x0E, 0x00, 0x48, 0x00, 0xEF,
0xAB, 0x01, 0xA0, 0x00, 0x01, 0x80, 0x01, 0x00, 0x4C, 0x00, 0xEF, 0xB7, 0x01, 0x81, 0x00, 0x03,
0x00, 0xEF, 0x92, 0x01, 0xF0, 0xEF, 0x19, 0x00, 0xEF, 0xAB, 0x01, 0x80, 0x01, 0x00, 0xEF, 0x0E,
0x00, 0x48, 0x00, 0xEF, 0xAB, 0x01, 0xB0, 0x01, 0x00, 0x4C, 0x00, 0xEF, 0xB7, 0x01, 0x81, 0x00,
0x03, 0x00, 0xEF, 0x92, 0x01, 0xF0, 0xEF, 0x19, 0x00, 0xEF, 0xAB, 0x01, 0x80, 0x01, 0x00, 0xEF,
0x0E, 0x00, 0xEF, 0xAB, 0x01, 0x28, 0x00, 0x01, 0xEC, 0x02, 0x01, 0x81, 0x01, 0x01, 0x00, 0xE0,
0x05, 0x01, 0xB0, 0x01, 0x01, 0x81, 0x00, 0x0E, 0x00, 0xEF, 0xB7, 0x01, 0x81, 0x00, 0x03, 0x00,
0xEF, 0x92, 0x01, 0xF0, 0x81, 0x00, 0x0E, 0x00, 0xEF, 0xAB, 0x01, 0x29, 0x00, 0x01, 0x00, 0xE8,
0x36, 0x01, 0xEF, 0xA3, 0x01, 0x91, 0x00, 0x01, 0x00, 0x0A, 0x01, 0x00, 0x81, 0x00, 0x0F, 0x00,
0xEF, 0xB7, 0x01, 0xE0, 0x3D, 0x01, 0x81, 0x00, 0x03, 0x00, 0xEF, 0x92, 0x01, 0xF0, 0xB0, 0x00,
0x00, 0xEF, 0xAB, 0x01, 0x80, 0x01, 0x00, 0x81, 0x00, 0x01, 0x00, 0xEF, 0xAB, 0x01, 0x80, 0x02,
0x00, 0x81, 0x00, 0x01, 0x00, 0xFC, 0x80, 0x01, 0x00, 0x81, 0x00, 0x00, 0x00, 0xEF, 0xB7, 0x01,
0x81, 0x00, 0x01, 0x00, 0xEF, 0x92, 0x01, 0xF0, 0xB0, 0x00, 0x00, 0xEF, 0xAB, 0x01, 0x80, 0x01,
0x00, 0x81, 0x00, 0x01, 0x00, 0xEF, 0xAB, 0x01, 0x80, 0x02, 0x00, 0x81, 0x00, 0x00, 0x00, 0xFC,
0x80, 0x01, 0x00, 0x81, 0x00, 0x00, 0x00, 0xEF, 0xB7, 0x01, 0x81, 0x00, 0x01, 0x00, 0xEF, 0x92,
0x01, 0xF0, 0x80, 0x01, 0x00, 0xEF, 0xA3, 0x01, 0x90, 0x01, 0x00, 0x81, 0x00, 0x0F, 0x00, 0xEF,
0xB7, 0x01, 0xF0, 0x81, 0x00, 0x0F, 0x00, 0xEF, 0xAB, 0x01, 0xF0, 0xC1, 0x00, 0x02, 0x00, 0x91,
0x00, 0x00, 0x0A, 0x0A, 0x00, 0x00, 0xF0, 0xC1, 0x00, 0x02, 0x00, 0x91, 0x00, 0x00, 0x0A, 0x1A,
0x00, 0x01, 0xF0, 0xEF, 0xA3, 0x01, 0x08, 0x00, 0x00, 0x29, 0x00, 0x0B, 0x00, 0xE8, 0xE0, 0x01,
0xC1, 0x00, 0x02, 0x00, 0x91, 0x00, 0x00, 0x08, 0x0A, 0x00, 0x00, 0xEE, 0x00, 0xE0, 0xC3, 0x01,
0xF0, 0x81, 0x00, 0x0F, 0x00, 0x81, 0x01, 0x00, 0x0C, 0xEF, 0xB7, 0x01, 0xEF, 0xC3, 0x01, 0xF8
};

int main()
{
int index = 0,ptr = -1,res = 0;
while (1)
{
int code = mem[index];
printf("0x%X ",index);
if ( code > 0xFC )
break;
switch ( mem[index] )
{
case 0x80u:
printf("mov reg[%X],reg[%X]\n",mem[index + 1],mem[index + 2]);
index += 3;
break;
case 0x81u:
printf("mov reg[%X],%X\n",mem[index + 1],((mem[index + 3] << 8) | mem[index + 2]));
index += 4;
break;
case 0x90u:
printf("add reg[%X],reg[%X]\n",mem[index + 1],mem[index + 2]);
index += 3;
break;
case 0x91u:
printf("add reg[%X],%X\n",mem[index + 1],(mem[index + 2] | (mem[index + 3] << 8)));
index += 4;
break;
case 0xA0u:
printf("sub reg[%X],%X\n",mem[index + 1],(mem[index + 2] | (mem[index + 3] << 8)));
index += 3;
break;
case 0x4C:
printf("pop\n");
index += 2;
break;
case 8u:
printf("mov reg[%X],[reg[%X]]\n",mem[index + 1],mem[index + 2]);
index += 3;
break;
case 0xAu:
printf("mov reg[%X],word [reg[%X]]\n",mem[index + 1],mem[index + 2]);
index += 3;
break;
case 0x18u:
printf("mov [reg[%X]],reg[%X]\n",mem[index + 1],mem[index + 2]);
index += 3;
break;
case 0x1Au:
printf("mov word [reg[%X]],reg[%X]\n",mem[index + 1],mem[index + 2]);
index += 3;
break;
case 0x28u:
printf("cmp reg[%X],reg[%X]\n",mem[index + 1],mem[index + 2]);
index += 3;
break;
case 0x29u:
printf("cmp reg[%X],%X\n",mem[index + 1],(mem[index + 2] | (mem[index + 3] << 8)));
index += 3;
break;
case 0xB0u:
printf("xor reg[%X],reg[%X]\n",mem[index + 1],mem[index + 2]);
index += 3;
break;
case 0xC1u:
printf("mul reg[%X],%X\n",mem[index + 1],mem[index + 2] | (unsigned __int16)(mem[index + 3] << 8));
index += 4;
break;
case 0xE0u:
printf("jmp %X\n",mem[index + 1] | (mem[index + 2] << 8));
index += 3;
break;
case 0xE8u:
printf("jnz %X\n",mem[index + 1] | (mem[index + 2] << 8));
index += 3;
break;
case 0xECu:
printf("jz %X",mem[index + 1] | (mem[index + 2] << 8));
index += 3;
break;
case 0xEEu:
printf("call reg[%X]\n",mem[index + 1]);
index += 2;
break;
case 0xEFu:
printf("call sub_%X\n",mem[index + 1] | (mem[index + 2] << 8));
index += 3;
break;
case 0xF0u:
printf("ret\n");
index += 1;
break;
case 0xF8u:
printf("exit\n"); // exit
exit(0);
break;
case 0xFCu:
printf("read or write\n");
++index;
break;
case 0x48:
printf("push\n");
index += 2;
break;
default:
index += 1;
break;
}

}

}

打印出来的流程如下:

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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
0x0  jmp  1E1
0x3 call sub_1A3
0x6 add reg[0],2
0xA mov reg[0],word [reg[0]]
0xD ret
0xE call sub_1A3
0x11 add reg[0],1
0x15 mov reg[0],[reg[0]]
0x18 ret
0x19 call sub_1A3
0x1C add reg[0],2
0x20 mov reg[0],[reg[0]]
0x23 ret
0x24 call sub_19
0x27 call sub_1AB
0x2A mov reg[1],reg[0]
0x2D call sub_E
0x30 call sub_1B7
0x33 mov reg[0],3
0x37 call sub_192
0x3A ret
0x3B call sub_3
0x3E mov reg[1],reg[0]
0x41 call sub_E
0x44 call sub_1B7
0x47 mov reg[0],4
0x4B call sub_192
0x4E ret
0x4F call sub_19
0x52 call sub_1AB
0x55 mov reg[1],reg[0]
0x58 call sub_E
0x5B call sub_1AB
0x5E mov [reg[0]],reg[1]
0x61 mov reg[0],3
0x65 call sub_192
0x68 ret
0x69 call sub_19
0x6C call sub_1AB
0x6F mov reg[1],[reg[0]]
0x72 call sub_E
0x75 call sub_1B7
0x78 mov reg[0],3
0x7C call sub_192
0x7F ret
0x80 call sub_19
0x83 call sub_1AB
0x86 mov reg[1],reg[0]
0x89 call sub_E
0x8C push
0x8E call sub_1AB
0x91 add reg[1],reg[0]
0x94 pop
0x96 call sub_1B7
0x99 mov reg[0],3
0x9D call sub_192
0xA0 ret
0xA1 call sub_19
0xA4 call sub_1AB
0xA7 mov reg[1],reg[0]
0xAA call sub_E
0xAD push
0xAF call sub_1AB
0xB2 sub reg[0],8001
0xB5 mov reg[1],reg[0]
0xB8 pop
0xBA call sub_1B7
0xBD mov reg[0],3
0xC1 call sub_192
0xC4 ret
0xC5 call sub_19
0xC8 call sub_1AB
0xCB mov reg[1],reg[0]
0xCE call sub_E
0xD1 push
0xD3 call sub_1AB
0xD6 xor reg[1],reg[0]
0xD9 pop
0xDB call sub_1B7
0xDE mov reg[0],3
0xE2 call sub_192
0xE5 ret
0xE6 call sub_19
0xE9 call sub_1AB
0xEC mov reg[1],reg[0]
0xEF call sub_E
0xF2 call sub_1AB
0xF5 cmp reg[0],reg[1]
0xF8 jz 1020xFB mov reg[1],1
0xFF jmp 105
0x102 xor reg[1],reg[1]
0x105 mov reg[0],E
0x109 call sub_1B7
0x10C mov reg[0],3
0x110 call sub_192
0x113 ret
0x114 mov reg[0],E
0x118 call sub_1AB
0x11B cmp reg[0],1
0x11E 0x11F jnz 136
0x122 call sub_1A3
0x125 add reg[0],1
0x129 mov reg[1],word [reg[0]]
0x12C mov reg[0],F
0x130 call sub_1B7
0x133 jmp 13D
0x136 mov reg[0],3
0x13A call sub_192
0x13D ret
0x13E xor reg[0],reg[0]
0x141 call sub_1AB
0x144 mov reg[1],reg[0]
0x147 mov reg[0],1
0x14B call sub_1AB
0x14E mov reg[2],reg[0]
0x151 mov reg[0],1
0x155 read or write
0x156 mov reg[1],reg[0]
0x159 mov reg[0],0
0x15D call sub_1B7
0x160 mov reg[0],1
0x164 call sub_192
0x167 ret
0x168 xor reg[0],reg[0]
0x16B call sub_1AB
0x16E mov reg[1],reg[0]
0x171 mov reg[0],1
0x175 call sub_1AB
0x178 mov reg[2],reg[0]
0x17B mov reg[0],0
0x17F read or write
0x180 mov reg[1],reg[0]
0x183 mov reg[0],0
0x187 call sub_1B7
0x18A mov reg[0],1
0x18E call sub_192
0x191 ret
0x192 mov reg[1],reg[0]
0x195 call sub_1A3
0x198 add reg[1],reg[0]
0x19B mov reg[0],F
0x19F call sub_1B7
0x1A2 ret
0x1A3 mov reg[0],F
0x1A7 call sub_1AB
0x1AA ret
0x1AB mul reg[0],2
0x1AF add reg[0],A00
0x1B3 mov reg[0],word [reg[0]]
0x1B6 ret
0x1B7 mul reg[0],2
0x1BB add reg[0],A00
0x1BF mov word [reg[0]],reg[1]
0x1C2 ret
0x1C3 call sub_1A3
0x1C6 mov reg[0],[reg[0]]
0x1C9 cmp reg[0],B
0x1CC 0x1CD jnz 1E0
0x1D0 mul reg[0],2
0x1D4 add reg[0],800
0x1D8 mov reg[0],word [reg[0]]
0x1DB call reg[0]
0x1DD jmp 1C3
0x1E0 ret
0x1E1 mov reg[0],F
0x1E5 mov reg[1],C00
0x1E9 call sub_1B7
0x1EC call sub_1C3
0x1EF exit

手动反编译成伪代码

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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
#include <iostream>
#include <stdint.h>
#include <string.h>

uint8_t mem[0x2000];

void mov(int reg_index, uint16_t value)
{
*(uint16_t*)&mem[0x0A00 + reg_index * 2] = value;
}


uint16_t read_reg_word(int reg_index)
{
return *(uint16_t*)&mem[0x0A00 + reg_index * 2];
}


uint16_t sub_01A3()
{
return read_reg_word(0xF);
}

void sub_0192(int offset)
{
uint16_t current_pc = sub_01A3();
uint16_t new_pc = current_pc + (uint16_t)offset;
mov(0xF, new_pc);
}



uint8_t code_pc_1()
{
uint16_t pc = sub_01A3();
return mem[pc + 1];
}


uint8_t code_pc_2()
{
uint16_t pc = sub_01A3();
return mem[pc + 2];
}

uint16_t code_pc_2_word()
{
uint16_t pc = sub_01A3();
return *(uint16_t*)&mem[pc + 2];
}



void sub_0024()
{
uint8_t reg_src_idx = code_pc_2();
uint16_t val_src = read_reg_word(reg_src_idx);
uint8_t reg_dst_idx = code_pc_1();
mov(reg_dst_idx, val_src);
sub_0192(3);
}


void sub_003B()
{
uint16_t val_const = code_pc_2_word();
uint8_t reg_dst_idx = code_pc_1();
mov(reg_dst_idx, val_const);
sub_0192(4);
}


void sub_004F()
{
uint8_t reg_src_idx = code_pc_2();
uint16_t val_src = read_reg_word(reg_src_idx);
uint8_t reg_addr_idx = code_pc_1();
uint16_t addr = read_reg_word(reg_addr_idx);

mem[addr] = (uint8_t)val_src;
sub_0192(3);
}


void sub_0069()
{
uint8_t reg_addr_idx = code_pc_2();
uint16_t addr = read_reg_word(reg_addr_idx);

uint8_t val_mem = mem[addr];

uint8_t reg_dst_idx = code_pc_1();
mov(reg_dst_idx, val_mem);
sub_0192(3);
}

void sub_0080()
{
uint8_t reg_src_idx = code_pc_2();
uint16_t val_src = read_reg_word(reg_src_idx);
uint8_t reg_dst_idx = code_pc_1();
uint16_t val_dst = read_reg_word(reg_dst_idx);

mov(reg_dst_idx, val_dst + val_src);
sub_0192(3);
}


void sub_00A1()
{
uint8_t reg_src_idx = code_pc_2();
uint16_t val_src = read_reg_word(reg_src_idx);
uint8_t reg_dst_idx = code_pc_1();
uint16_t val_dst = read_reg_word(reg_dst_idx);

mov(reg_dst_idx, val_dst - val_src);
sub_0192(3);
}


void sub_00C5()
{
uint8_t reg_src_idx = code_pc_2();
uint16_t val_src = read_reg_word(reg_src_idx);
uint8_t reg_dst_idx = code_pc_1();
uint16_t val_dst = read_reg_word(reg_dst_idx);

mov(reg_dst_idx, val_dst ^ val_src);
sub_0192(3);
}


void sub_00E6()
{
uint8_t reg2_idx = code_pc_2();
uint16_t val2 = read_reg_word(reg2_idx);
uint8_t reg1_idx = code_pc_1();
uint16_t val1 = read_reg_word(reg1_idx);

uint16_t result;
if (val1 == val2)
{
result = 1;
}
else
{
result = 0;
}

mov(0xE, result);
sub_0192(3);
}


void sub_0114()
{
uint16_t flag = read_reg_word(0xE);

if (flag == 1)
{
sub_0192(3);
}
else
{
uint16_t pc = sub_01A3();
uint16_t jmp_addr = *(uint16_t*)&mem[pc + 1];
mov(0xF, jmp_addr);
}

}


void sub_013E()
{
uint16_t arg1 = read_reg_word(0);
uint16_t arg2 = read_reg_word(1);


uint16_t buf_ptr = arg1;
uint16_t len = arg2;
uint16_t bytes_written = 0;

for (int i = 0; i < len; i++) {
std::cout << mem[buf_ptr + i];
bytes_written++;
}


mov(0, bytes_written);
sub_0192(1);
}

void sub_0168()
{
uint16_t arg1 = read_reg_word(0);
uint16_t arg2 = read_reg_word(1);


uint16_t buf_ptr = arg1;
uint16_t len = arg2;
uint16_t bytes_read = 0;

for (int i = 0; i < len; i++) {
char c;
if (std::cin.get(c)) {
mem[buf_ptr + i] = (uint8_t)c;
bytes_read++;
} else {
break;
}
}


mov(0, bytes_read);
sub_0192(1);
}


void sub_01C3()
{
while (true)
{
uint16_t pc = sub_01A3();
uint8_t opcode = mem[pc];

if (opcode == 0x0B)
{
break;
}

uint16_t handler_addr_ptr = 0x0800 + (opcode * 2);

uint16_t handler_addr = *(uint16_t*)&mem[handler_addr_ptr];


switch (handler_addr)
{
case 0x0024: sub_0024(); break;
case 0x003B: sub_003B(); break;
case 0x004F: sub_004F(); break;
case 0x0069: sub_0069(); break;
case 0x0080: sub_0080(); break;
case 0x00A1: sub_00A1(); break;
case 0x00C5: sub_00C5(); break;
case 0x00E6: sub_00E6(); break;
case 0x0114: sub_0114(); break;
case 0x013E: sub_013E(); break;
case 0x0168: sub_0168(); break;

}


}

}


void sub_01E1()
{
mov(0xF, 0x0C00);

sub_01C3();

}

int main()
{

memset(mem, 0, sizeof(mem));

unsigned char data[] = {};

sub_01E1();

return 0;
}

仍然是一个 vm,这里有一些相对偏移需要注意,rip 基址一开始是被设成 0xC00,对比上面结构题可以发现,正好在第一层 vm 的内存后面,switch-case 结构的值是根据 C00 处的操作码到 800 处去找

800 处也就是对应的第二层 vm 各个函数地址,化简一下写出第二层 vm 解释器

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
#include <windows.h>
#include <iostream>
#include <stdint.h>

uint8_t code[] =
{
0x01, 0x00, 0x00, 0x0E, 0x01, 0x01, 0x10, 0x00, 0x0A, 0x01, 0x00, 0x00, 0x0F, 0x01, 0x01, 0x00,
0x01, 0x09, 0x00, 0x03, 0x00, 0x01, 0x02, 0x00, 0x0D, 0x01, 0x0D, 0x00, 0x04, 0x01, 0x04, 0x00,
0x00, 0x01, 0x05, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x04, 0x01,
0x00, 0x06, 0x02, 0x04, 0x06, 0x04, 0x00, 0x0B, 0x06, 0x03, 0x06, 0x06, 0x04, 0x05, 0x06, 0x02,
0x0D, 0x05, 0x03, 0x05, 0x0D, 0x00, 0x07, 0x02, 0x04, 0x07, 0x05, 0x00, 0x0C, 0x07, 0x03, 0x07,
0x07, 0x02, 0x0C, 0x06, 0x02, 0x0B, 0x07, 0x00, 0x08, 0x06, 0x04, 0x08, 0x07, 0x02, 0x0D, 0x08,
0x03, 0x08, 0x0D, 0x00, 0x09, 0x02, 0x04, 0x09, 0x08, 0x03, 0x09, 0x09, 0x01, 0x0A, 0x00, 0x0F,
0x04, 0x0A, 0x00, 0x03, 0x0A, 0x0A, 0x06, 0x0A, 0x09, 0x01, 0x0B, 0x00, 0x0B, 0x04, 0x0B, 0x00,
0x02, 0x0B, 0x0A, 0x04, 0x00, 0x01, 0x07, 0x00, 0x03, 0x08, 0x2D, 0x0C, 0x01, 0x00, 0x00, 0x00,
0x01, 0x01, 0x00, 0x0B, 0x01, 0x02, 0x18, 0x0E, 0x01, 0x03, 0x2A, 0x00, 0x01, 0x0D, 0x01, 0x00,
0x03, 0x04, 0x01, 0x03, 0x05, 0x02, 0x04, 0x01, 0x0D, 0x04, 0x02, 0x0D, 0x07, 0x04, 0x05, 0x08,
0xCE, 0x0C, 0x04, 0x00, 0x0D, 0x07, 0x00, 0x03, 0x08, 0xA0, 0x0C, 0x01, 0x00, 0x10, 0x0E, 0x01,
0x01, 0x04, 0x00, 0x0A, 0x01, 0x01, 0x34, 0x12, 0x07, 0x00, 0x01, 0x08, 0xE1, 0x0C, 0x01, 0x00,
0x14, 0x0E, 0x01, 0x01, 0x04, 0x00, 0x0A, 0x01, 0x01, 0x34, 0x12, 0x07, 0x00, 0x01, 0x08, 0xE1,
0x0C, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

int main()
{
int index = 0;
while(1)
{
printf("0x%X ",index + 0xC00);
switch(code[index])
{
case 0:
printf("mov reg[%X],word reg[%X]\n",code[index + 1],code[index + 2]);
index += 3;
break;
case 1:
printf("mov reg[%X],word %X\n",code[index + 1],*(uint16_t*)&code[index + 2]);
index += 4;
break;
case 2:
printf("mov mem[reg[%X]],byte reg[%X]\n",code[index + 1],code[index + 2]);
index += 3;
break;
case 3:
printf("mov reg[%X],mem[reg[%X]]\n",code[index + 1],code[index + 2]);
index += 3;
break;
case 4:
printf("add reg[%X],reg[%X]\n",code[index + 1],code[index + 2]);
index += 3;
break;
case 5:
printf("sub reg[%X],reg[%X]\n",code[index + 1],code[index + 2]);
index += 3;
break;
case 6:
printf("xor reg[%X],reg[%X]\n",code[index + 1],code[index + 2]);
index += 3;
break;
case 7:
printf("mov reg[0xE],(reg[%X] == reg[%X])\n",code[index + 1],code[index + 2]);
index += 3;
break;
case 8:
printf("if(reg[0xE] != 1)\n");
printf(" jmp %X\n",*(uint16_t*)&code[index + 1]);
index += 3;
break;
case 9:
printf("read\n");
index += 1;
break;
case 10:
printf("printf\n");
index += 1;
break;
case 11:
printf("exit");
Sleep(10000);
break;
}
}
}

得到第二层 vm 的汇编

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
0xC00  mov  reg[0],word E00
0xC04 mov reg[1],word 10
0xC08 printf
0xC09 mov reg[0],word F00
0xC0D mov reg[1],word 100
0xC11 read
0xC12 mov reg[3],word reg[0]
0xC15 mov reg[2],word D00
0xC19 mov reg[D],word 400
0xC1D mov reg[4],word 0
0xC21 mov reg[5],word 0
0xC25 mov reg[1],word 1
0xC29 mov reg[0],word 0
0xC2D add reg[4],reg[1]
0xC30 mov reg[6],word reg[2]
0xC33 add reg[6],reg[4]
0xC36 mov reg[B],word reg[6]
0xC39 mov reg[6],mem[reg[6]]
0xC3C add reg[5],reg[6]
0xC3F mov mem[reg[D]],byte reg[5]
0xC42 mov reg[5],mem[reg[D]]
0xC45 mov reg[7],word reg[2]
0xC48 add reg[7],reg[5]
0xC4B mov reg[C],word reg[7]
0xC4E mov reg[7],mem[reg[7]]
0xC51 mov mem[reg[C]],byte reg[6]
0xC54 mov mem[reg[B]],byte reg[7]
0xC57 mov reg[8],word reg[6]
0xC5A add reg[8],reg[7]
0xC5D mov mem[reg[D]],byte reg[8]
0xC60 mov reg[8],mem[reg[D]]
0xC63 mov reg[9],word reg[2]
0xC66 add reg[9],reg[8]
0xC69 mov reg[9],mem[reg[9]]
0xC6C mov reg[A],word F00
0xC70 add reg[A],reg[0]
0xC73 mov reg[A],mem[reg[A]]
0xC76 xor reg[A],reg[9]
0xC79 mov reg[B],word B00
0xC7D add reg[B],reg[0]
0xC80 mov mem[reg[B]],byte reg[A]
0xC83 add reg[0],reg[1]
0xC86 mov reg[0xE],(reg[0] == reg[3])
0xC89 if(reg[0xE] != 1)
jmp C2D
0xC8C mov reg[0],word 0
0xC90 mov reg[1],word B00
0xC94 mov reg[2],word E18
0xC98 mov reg[3],word 2A
0xC9C mov reg[D],word 1
0xCA0 mov reg[4],mem[reg[1]]
0xCA3 mov reg[5],mem[reg[2]]
0xCA6 add reg[1],reg[D]
0xCA9 add reg[2],reg[D]
0xCAC mov reg[0xE],(reg[4] == reg[5])
0xCAF if(reg[0xE] != 1)
jmp CCE
0xCB2 add reg[0],reg[D]
0xCB5 mov reg[0xE],(reg[0] == reg[3])
0xCB8 if(reg[0xE] != 1)
jmp CA0
0xCBB mov reg[0],word E10
0xCBF mov reg[1],word 4
0xCC3 printf
0xCC4 mov reg[1],word 1234
0xCC8 mov reg[0xE],(reg[0] == reg[1])
0xCCB if(reg[0xE] != 1)
jmp CE1
0xCCE mov reg[0],word E14
0xCD2 mov reg[1],word 4
0xCD6 printf
0xCD7 mov reg[1],word 1234
0xCDB mov reg[0xE],(reg[0] == reg[1])
0xCDE if(reg[0xE] != 1)
jmp CE1
0xCE1 exit

里面同样用了偏移,比如 printf 输出的 E00 处 16 个字符,在程序中调试可以发现是 "input your flag:"

同样的方法可以查找到其他数据。

第二层 vm 进行的是一个 rc4 加密操作,仿写逻辑写出解密脚本即可

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
#include <windows.h>
#include <iostream>
#include <stdint.h>
int main()
{
int sbox[] =
{
0x20, 0x15, 0xCA, 0x88, 0x2E, 0xE5, 0x89, 0x87, 0x80, 0xDF, 0x2A, 0x86, 0x99, 0x5E, 0x14, 0x4E,
0x2C, 0x90, 0x6C, 0x19, 0x03, 0xD8, 0x0D, 0xCC, 0x18, 0xA6, 0x53, 0x8E, 0x7E, 0x94, 0x1E, 0x93,
0x75, 0xB4, 0x36, 0x61, 0x49, 0xC6, 0xD4, 0x58, 0xF5, 0x8A, 0xC3, 0x2B, 0x8F, 0xA2, 0xEE, 0x1D,
0x4C, 0xAE, 0x05, 0x48, 0x9F, 0x96, 0x4D, 0xDC, 0xE6, 0xF9, 0x2F, 0x57, 0x4F, 0x67, 0x3B, 0xC5,
0x42, 0xE9, 0x12, 0x41, 0xAD, 0x3D, 0xCF, 0x1B, 0x4B, 0xB6, 0xF2, 0x04, 0xF7, 0x1A, 0x92, 0xB1,
0x6B, 0xC7, 0x56, 0x37, 0xB8, 0x79, 0xE2, 0x23, 0xEA, 0x3A, 0x52, 0x16, 0xBB, 0x2D, 0xC0, 0xE1,
0xF0, 0x34, 0x8B, 0xC4, 0x65, 0x9E, 0x55, 0xC2, 0x26, 0x9A, 0x66, 0xB7, 0x8D, 0x73, 0x9C, 0xD5,
0x0F, 0x39, 0x09, 0x21, 0x5A, 0x62, 0xD6, 0x0A, 0x45, 0xAA, 0xDD, 0x38, 0xD1, 0xB9, 0x81, 0xC8,
0x00, 0xE7, 0x35, 0xBF, 0xA5, 0xB3, 0x01, 0x0C, 0x76, 0xBD, 0xAB, 0x9D, 0x59, 0x10, 0x08, 0x5B,
0x5C, 0x4A, 0xE0, 0x40, 0xA8, 0x84, 0x43, 0x8C, 0xCB, 0x13, 0xD7, 0xBA, 0x02, 0xC1, 0xA9, 0x25,
0x31, 0x3C, 0xF3, 0x72, 0xAF, 0x22, 0x51, 0xDA, 0xFE, 0x24, 0x0E, 0xAC, 0x3F, 0x17, 0xE3, 0xD2,
0xE4, 0xFA, 0x07, 0x6F, 0xB5, 0xCE, 0xD0, 0xFC, 0x06, 0x68, 0xA4, 0xF8, 0xDE, 0x11, 0xA3, 0x6D,
0x32, 0xA1, 0xFB, 0x95, 0xC9, 0xFD, 0x74, 0x47, 0x0B, 0xB2, 0xF4, 0xEC, 0x6A, 0x7F, 0xE8, 0x50,
0xED, 0x7D, 0x69, 0x7A, 0x98, 0x70, 0xA0, 0x60, 0xCD, 0x9B, 0x85, 0x71, 0xEF, 0x6E, 0x5D, 0x27,
0x91, 0xD3, 0xD9, 0x1F, 0x1C, 0x64, 0x97, 0x7B, 0x83, 0xFF, 0xBE, 0x5F, 0x77, 0x54, 0xEB, 0x3E,
0x44, 0xF6, 0xA7, 0xF1, 0xB0, 0x46, 0xDB, 0x29, 0xBC, 0x78, 0x63, 0x28, 0x33, 0x30, 0x7C, 0x82
};

uint8_t data[] =
{
0x32, 0x9A, 0x93, 0x60, 0x80, 0x36, 0x66, 0x39, 0x3E, 0x63, 0xF0, 0x7D, 0x24, 0x27, 0x75, 0x37,
0x37, 0x00, 0x8D, 0x8A, 0xF6, 0x21, 0x9E, 0x91, 0x62, 0x73, 0x1D, 0xAC, 0x40, 0xAF, 0x05, 0x7E,
0x2B, 0x72, 0xFC, 0x74, 0x68, 0x00, 0x67, 0x87, 0xE8, 0x08
};

int i = 0,j = 0;

for(int k = 0; k < 42; k++)
{
i = (i + 1) % 256;
j = (j + sbox[i]) % 256;
int temp = sbox[i];
sbox[i] = sbox[j];
sbox[j] = temp;
int key = sbox[(sbox[i] + sbox[j]) % 256];
data[k] ^= key;
}

printf("%s",data);

}
// flag{2c7c093b-f648-11ed-a716-701ab8caaafe}