vm初窥

杂话

往昔之捷径终变未时之坑,不会的题型还是逃不掉啊zzzzzz

go out to mix, it’s never too late to return..

在这里感谢5m10v3师傅的分享,万分感谢 Orz Orz Orz

前言

vm逆向核心就是opcode的处理,包括对反编译结果的自定义寄存器和运算法则的分析,写脚本转化,可以定义结构体简化分析难度,但是具体基本功以及分析还是得多做题积累经验。

例题

[GKCTF2020]EzMachine

左边为对应opcode具体函数

此处func_switch为char*,off_A748F4为int*,4*2假设对应opcode为1,则00A745F0->00A745F8,对应sub_A31000

自定义寄存器初始化以及空间分配

22个自定义函数,vm_opcode–>asm

每个函数的汇编化

重温汇编基本功了

nop eip++

mov a2 , reg [ a1 ]

stack分配一个地址块,再加上偏移,特征明显

push常量

其余略,图太多了,具体可参考up✌文章

还原汇编+解密

偷懒,直接扒up✌脚本

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
277

opcode = [  0x01, 0x03, 0x03, 0x05, 0x00, 0x00, 0x11, 0x00, 0x00, 0x01,

  0x01, 0x11, 0x0C, 0x00, 0x01, 0x0D, 0x0A, 0x00, 0x01, 0x03,

  0x01, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x01, 0x02, 0x00,

  0x01, 0x00, 0x11, 0x0C, 0x00, 0x02, 0x0D, 0x2B, 0x00, 0x14,

  0x00, 0x02, 0x01, 0x01, 0x61, 0x0C, 0x00, 0x01, 0x10, 0x1A,

  0x00, 0x01, 0x01, 0x7A, 0x0C, 0x00, 0x01, 0x0F, 0x1A, 0x00,

  0x01, 0x01, 0x47, 0x0A, 0x00, 0x01, 0x01, 0x01, 0x01, 0x06,

  0x00, 0x01, 0x0B, 0x24, 0x00, 0x01, 0x01, 0x41, 0x0C, 0x00,

  0x01, 0x10, 0x24, 0x00, 0x01, 0x01, 0x5A, 0x0C, 0x00, 0x01,

  0x0F, 0x24, 0x00, 0x01, 0x01, 0x4B, 0x0A, 0x00, 0x01, 0x01,

  0x01, 0x01, 0x07, 0x00, 0x01, 0x01, 0x01, 0x10, 0x09, 0x00,

  0x01, 0x03, 0x01, 0x00, 0x03, 0x00, 0x00, 0x01, 0x01, 0x01,

  0x06, 0x02, 0x01, 0x0B, 0x0B, 0x00, 0x02, 0x07, 0x00, 0x02,

  0x0D, 0x00, 0x02, 0x00, 0x00, 0x02, 0x05, 0x00, 0x02, 0x01,

  0x00, 0x02, 0x0C, 0x00, 0x02, 0x01, 0x00, 0x02, 0x00, 0x00,

  0x02, 0x00, 0x00, 0x02, 0x0D, 0x00, 0x02, 0x05, 0x00, 0x02,

  0x0F, 0x00, 0x02, 0x00, 0x00, 0x02, 0x09, 0x00, 0x02, 0x05,

  0x00, 0x02, 0x0F, 0x00, 0x02, 0x03, 0x00, 0x02, 0x00, 0x00,

  0x02, 0x02, 0x00, 0x02, 0x05, 0x00, 0x02, 0x03, 0x00, 0x02,

  0x03, 0x00, 0x02, 0x01, 0x00, 0x02, 0x07, 0x00, 0x02, 0x07,

  0x00, 0x02, 0x0B, 0x00, 0x02, 0x02, 0x00, 0x02, 0x01, 0x00,

  0x02, 0x02, 0x00, 0x02, 0x07, 0x00, 0x02, 0x02, 0x00, 0x02,

  0x0C, 0x00, 0x02, 0x02, 0x00, 0x02, 0x02, 0x00, 0x01, 0x02,

  0x01, 0x13, 0x01, 0x02, 0x04, 0x00, 0x00, 0x0C, 0x00, 0x01,

  0x0E, 0x5B, 0x00, 0x01, 0x01, 0x22, 0x0C, 0x02, 0x01, 0x0D,

  0x59, 0x00, 0x01, 0x01, 0x01, 0x06, 0x02, 0x01, 0x0B, 0x4E,

  0x00, 0x01, 0x03, 0x00, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x00,

  0x01, 0x03, 0x01, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00]

#每个opcode对应的汇编代码

opcode_key = {

    0:"{} nop",

    1:"{} mov {},{}",

    2:"{} push {}",

    3:"{} push {}",

    4:"{} pop {}",

    5:"{} print",

    6:"{} add {},{}",

    7:"{} sub {},{}",

    8:"{} mul {},{}",

    9:"{} div {},{}",

    10:"{} xor {},{}",

    11:"{} jmp {}",

    12:"{} cmp {},{}",

    13:"{} je {}",

    14:"{} jne {}",

    15:"{} jg {}",

    16:"{} jl {}",

    17:"{} gets(input)\neax=strlen(input)",

    18: "{} memset(&input[{}], 0 , {})",

    19: "{} mov {},stack({})",

    20: "{} mov {},input({})",

    0xff:"{} exit()"}



#计算每条指令的地址

def get_addr(eip):

    return "_" + str(eip) + ":"

#反汇编器

def disasm():

    reg = ['eax', 'edx', 'ecx', 'eflag']

    eip = 0

    assemb = ""

    len_opcode = len(opcode)

    while eip < len_opcode:

        x = opcode[eip]

        if x == 0:

            assemb += opcode_key[x].format(get_addr(eip)) + '\n'

            eip += 1

        if x == 1:

            assemb += opcode_key[x].format(get_addr(eip),reg[opcode[eip+1]],opcode[eip+2]) + '\n'

            eip += 3

        if x == 2:

            assemb += opcode_key[x].format(get_addr(eip),opcode[eip+1]) + '\n'

            eip += 3

        if x == 3:

            assemb += opcode_key[x].format(get_addr(eip),reg[opcode[eip+1]]) + '\n'

            eip += 3

        if x == 4:

            assemb += opcode_key[x].format(get_addr(eip),reg[opcode[eip+1]]) + '\n'

            eip += 3

        if x == 5:

            assemb += opcode_key[x].format(get_addr(eip)) + '\n'

            eip += 3

        if x == 6:

            assemb += opcode_key[x].format(get_addr(eip),reg[opcode[eip+1]],reg[opcode[eip+2]]) + '\n'

            eip += 3

        if x == 7:

            assemb += opcode_key[x].format(get_addr(eip),reg[opcode[eip+1]],reg[opcode[eip+2]]) + '\n'

            eip += 3

        if x == 8:

            assemb += opcode_key[x].format(get_addr(eip),reg[opcode[eip+1]],reg[opcode[eip+2]]) + '\n'

            eip += 3

        if x == 9:

            assemb += opcode_key[x].format(get_addr(eip),reg[opcode[eip+1]],reg[opcode[eip+2]]) + '\n'

            eip += 3

        if x == 10:

            assemb += opcode_key[x].format(get_addr(eip),reg[opcode[eip+1]],reg[opcode[eip+2]]) + '\n'

            eip += 3

        if x == 11:

            tmp = 3 * opcode[eip + 1] - 3

            assemb += opcode_key[x].format(get_addr(eip),get_addr(tmp))+ '\n'

            eip += 3

        if x == 12:

            assemb += opcode_key[x].format(get_addr(eip), reg[opcode[eip + 1]], reg[opcode[eip + 2]]) + '\n'

            eip += 3

        if x == 13:

            tmp = 3 * opcode[eip + 1] - 3

            assemb += opcode_key[x].format(get_addr(eip),get_addr(tmp))+ '\n'

            eip += 3

        if x == 14:

            tmp = 3 * opcode[eip + 1] - 3

            assemb += opcode_key[x].format(get_addr(eip),get_addr(tmp))+ '\n'

            eip += 3

        if x == 15:

            tmp = 3 * opcode[eip + 1] - 3

            assemb += opcode_key[x].format(get_addr(eip),get_addr(tmp))+ '\n'

            eip += 3

        if x == 16:

            tmp = 3 * opcode[eip + 1] - 3

            assemb += opcode_key[x].format(get_addr(eip),get_addr(tmp))+ '\n'

            eip += 3

        if x == 17:

            assemb += opcode_key[x].format(get_addr(eip))+ '\n'

            eip += 3

        if x == 18:

            assemb += opcode_key[x].format(get_addr(eip),opcode[eip+1],opcode[eip+2])+ '\n'

            eip += 3

        if x == 19:

            assemb += opcode_key[x].format(get_addr(eip),reg[opcode[eip+1]],reg[opcode[eip+2]]) + '\n'

            eip += 3

        if x == 20:

            assemb += opcode_key[x].format(get_addr(eip),reg[opcode[eip+1]],reg[opcode[eip+2]]) + '\n'

            eip += 3

        if x == 0xff:

            assemb += opcode_key[x].format(get_addr(eip)) + '\n'

            eip += 3

    print(assemb)

disasm()



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

import string



table = string.ascii_lowercase



table1 = string.ascii_uppercase



litter = [(ord(i) ^ 71) + 1 for i in table]

big = [(ord(i) ^ 75) - 1 for i in table1]



enc = [7,13,0,5,1,12,1,0,0,13,5,15,0,9,5,15,3,0,2,5,3,3,1,7,7,11,2,1,2,7,2,12,2,2]

flag = ""

for i in range(0,len(enc),2):

    tmp = enc[i] * 16

    tmp += enc[i+1]

    if tmp in litter:

        flag += chr((tmp - 1) ^ 71)

    elif tmp in big:

        flag += chr((tmp + 1) ^ 75)

    else:

        flag += chr(tmp)

print(flag[::-1])

# flag{Such_A_EZVM}

[第七届山东省大学生网络安全技能大赛]babyLoginPlus

1
2
3
4
5
6
7
8
9
10
11
12
13

int __cdecl main(int argc, const char **argv, const char **envp)

{

  sub_401510();

  sub_4013C0(&unk_40B0B0);

  return (int)sub_401540(&byte_408240, count_0, 1);

}

sub_4013C0跟进

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

int __cdecl sub_4013C0(int a1)

{

  int buf; // eax



  *(_DWORD *)a1 = 0;

  *(_DWORD *)(a1 + 4) = 0;

  *(_DWORD *)(a1 + 8) = 0;

  *(_DWORD *)(a1 + 12) = 0;

  *(_DWORD *)(a1 + 16) = 0;

  *(_BYTE *)(a1 + 24) = -48;

  *(_DWORD *)(a1 + 28) = sub_401000;

  *(_BYTE *)(a1 + 32) = -47;

  *(_DWORD *)(a1 + 36) = sub_401180;

  *(_BYTE *)(a1 + 40) = -46;

  *(_DWORD *)(a1 + 44) = sub_401210;

  *(_BYTE *)(a1 + 48) = -44;

  *(_DWORD *)(a1 + 52) = sub_401270;

  *(_BYTE *)(a1 + 56) = -43;

  *(_DWORD *)(a1 + 60) = sub_4012A0;

  *(_BYTE *)(a1 + 64) = -45;

  *(_DWORD *)(a1 + 68) = nullsub_1;

  *(_BYTE *)(a1 + 72) = -42;

  *(_DWORD *)(a1 + 76) = sub_4012E0;

  *(_BYTE *)(a1 + 80) = -41;

  *(_DWORD *)(a1 + 84) = sub_4011A0;

  *(_BYTE *)(a1 + 88) = -40;

  *(_DWORD *)(a1 + 92) = sub_4011F0;

  buf = (int)malloc(0x400u);

  memset((void *)buf, 0, 0x400u);

  qmemcpy((void *)(buf + 100), sub_401540(&::a1, count, 0), count);

  qmemcpy((void *)(buf + 200), sub_401540(&a1_0, count_1, 0), count_1);

  qmemcpy((void *)(buf + 300), &src_, count_2);

  buf = buf;

  qmemcpy((void *)(buf + 400), &src__0, count_3);

  return buf;

}

sub_401540跟进,有对unk_40B0B0的引用,发现opcode unk_40853C

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

char *__cdecl sub_401540(_BYTE *p_a1, signed int count, int a3)

{

  char *buf; // esi

  signed int i; // eax

  char *v6; // edx

  char v7; // cl



  buf = (char *)malloc(count + 2);

  memset(buf, 0, count);

  for ( i = 0; i < count; *v6 = v7 )

  {

    v6 = &buf[i];

    v7 = (*p_a1 ^ 0x66) - 1;

    ++i;

    p_a1 += 2;

  }

  *(_WORD *)&buf[i] = 0;

  if ( a3 )

  {

    sub_401600(buf);

    sub_401390(&unk_40B0B0, &unk_40853C);

    sub_401600(&buf_);

  }

  return buf;

}

对sub_4013C0函数进行结构体优化,变量类型按y修改

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

typedef struct _opcode {

    unsigned int opcode;  

    unsigned char  p_fun;    

} _opcode;



typedef struct _cpu {

    unsigned int _eax;      // 4 bytes

    unsigned int _ebx;      // 4 bytes

    unsigned int _ecx;      // 4 bytes

    unsigned int _edx;      // 4 bytes

    unsigned int _tmp;      // 4 bytes

    unsigned int _eip;      // 4 bytes

    _opcode oplist[10];    

} _cpu;


效果如下

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

int __cdecl sub_4013C0(_cpu *a1)

{

  int buf; // eax



  a1->_eax = 0;

  a1->_ebx = 0;

  a1->_ecx = 0;

  a1->_edx = 0;

  a1->_tmp = 0;

  LOBYTE(a1->oplist[0].opcode) = -48;

  *(_DWORD *)&a1->oplist[0].p_fun = sub_401000;

  LOBYTE(a1->oplist[1].opcode) = -47;

  *(_DWORD *)&a1->oplist[1].p_fun = sub_401180;

  LOBYTE(a1->oplist[2].opcode) = -46;

  *(_DWORD *)&a1->oplist[2].p_fun = sub_401210;

  LOBYTE(a1->oplist[3].opcode) = -44;

  *(_DWORD *)&a1->oplist[3].p_fun = sub_401270;

  LOBYTE(a1->oplist[4].opcode) = -43;

  *(_DWORD *)&a1->oplist[4].p_fun = sub_4012A0;

  LOBYTE(a1->oplist[5].opcode) = -45;

  *(_DWORD *)&a1->oplist[5].p_fun = nullsub_1;

  LOBYTE(a1->oplist[6].opcode) = -42;

  *(_DWORD *)&a1->oplist[6].p_fun = sub_4012E0;

  LOBYTE(a1->oplist[7].opcode) = -41;

  *(_DWORD *)&a1->oplist[7].p_fun = sub_4011A0;

  LOBYTE(a1->oplist[8].opcode) = -40;

  *(_DWORD *)&a1->oplist[8].p_fun = sub_4011F0;

  ::buf = (int)malloc(0x400u);

  memset((void *)::buf, 0, 0x400u);

  qmemcpy((void *)(::buf + 100), sub_401540(&p_a1, count, 0), count);

  qmemcpy((void *)(::buf + 200), sub_401540(&a1_0, count_1, 0), count_1);

  qmemcpy((void *)(::buf + 300), &src_, count_2);

  buf = ::buf;

  qmemcpy((void *)(::buf + 400), &src__0, count_3);

  return buf;

}

buf有smc处理,动调提一下

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

char *__cdecl sub_401540(_BYTE *p_a1, signed int count, int a3)

{

  char *buf; // esi

  signed int i; // eax

  char *v6; // edx

  char v7; // cl



  buf = (char *)malloc(count + 2);

  memset(buf, 0, count);

  for ( i = 0; i < count; *v6 = v7 )

  {

    v6 = &buf[i];

    v7 = (*p_a1 ^ 0x66) - 1;

    ++i;

    p_a1 += 2;

  }

  *(_WORD *)&buf[i] = 0;

  if ( a3 )

  {

    sub_401600(buf);

    sub_401390(&cpu, &opcode);

    sub_401600(&buf_);

  }

  return buf;

}

照着随便异或-1,对比try again能确定为两个反馈,应该还有其他还原处理,然后动调还能看出welcome,和大小写字母表,比较与处理判断在最开始的sub_401540,中间不断调用vm函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

Resulting ASCII string:

""--<""Gslkpavunavislw!"">

""--<""Vpy"akail!"">



  ::buf = (int)malloc(0x400u);

  memset((void *)::buf, 0, 0x400u);

  qmemcpy((void *)(::buf + 100), sub_401540(&p_a1, count, 0), count);// success

  qmemcpy((void *)(::buf + 200), sub_401540(&a1_0, count_1, 0), count_1);// wrong

  qmemcpy((void *)(::buf + 300), welcome, count_2);// welcome

  buf = ::buf;

  qmemcpy((void *)(::buf + 400), &src__0, count_3);

提取opcode,对相应处理函数重命名

D0_mv

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

_DWORD *__cdecl sub_401000(_DWORD *cpu)

{

    // cpu[0] = _eax, cpu[1] = _ebx, cpu[2] = _ecx, cpu[3] = _edx, cpu[5] = _eip

    unsigned int eip = cpu[5];

    unsigned char op_type = *(unsigned char *)(eip + 1);

    unsigned int imm32 = *(unsigned int *)(eip + 2);



    switch (op_type) {

        case 0x20: // ' ': mov eax, imm32

            cpu[0] = imm32;

            break;

        case 0x21: // '!': mov ebx, imm32

            cpu[1] = imm32;

            break;

        case 0x22: // '"': mov ecx, imm32

            cpu[2] = imm32;

            break;

        case 0x23: // '#': mov edx, imm32

            cpu[3] = imm32;

            break;



        case 0x41: // 'A': mov eax, byte ptr [buf + imm32]

            cpu[0] = *(unsigned char *)(buf + imm32);

            break;

        case 0x42: // 'B': mov ebx, byte ptr [buf + imm32]

            cpu[1] = *(unsigned char *)(buf + imm32);

            break;



        case 0x51: // 'Q': mov dword ptr [buf + ecx], imm32

            *(unsigned int *)(buf + cpu[2]) = imm32;

            break;

        case 0x52: // 'R': mov dword ptr [buf + ecx], eax

            *(unsigned int *)(buf + cpu[2]) = cpu[0];

            break;



        case 0x61: // 'a': mov eax, byte ptr [buf + eax]

            cpu[0] = *(unsigned char *)(buf + cpu[0]);

            break;

        case 0x62: // 'b': mov ebx, byte ptr [buf + ebx]

            cpu[1] = *(unsigned char *)(buf + cpu[1]);

            break;



        default:

            // 未知指令,仅推进 eip

            break;

    }



    cpu[5] += 6;  // 所有指令长度为 6 字节

    return cpu;

}

D1_xor

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

_DWORD *__cdecl xor(_DWORD *a1)

{

  _DWORD *result; // eax

  int v2; // ecx



  result = a1;

  v2 = a1[5] + 1;

  *a1 ^= a1[1];

  a1[5] = v2;

  return result;

}

//优化后

_DWORD *__cdecl sub_401180(_DWORD *cpu)

{

    // 执行: EAX ^= EBX

    cpu[0] ^= cpu[1];



    // 指令长度为 1 字节,推进 EIP

    cpu[5] += 1;



    return cpu;

}

D2_cmp

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

_DWORD *__cdecl cmp(_DWORD *a1)

{

  _DWORD *result; // eax

  int v2; // esi    → a1[5] (_eip)

  int v3; // edi    → 用于保存左操作数

  int v4; // ecx    → 用于保存右操作数



  result = a1;

  v2 = a1[5];          // 当前指令地址

  v3 = *a1;            // 默认左操作数 = _eax

  v4 = a1[1];          // 默认右操作数 = _ebx



  if ( *(_BYTE *)(v2 + 1) )               // 第二个字节(sub-opcode)非零?

  {

    if ( *(_BYTE *)(v2 + 1) == 2 )

    {

      v3 = a1[1];     // 左 = _ebx

      v4 = a1[2];     // 右 = _ecx

    }

    else if ( *(_BYTE *)(v2 + 1) == 3 )

    {

      v3 = a1[2];     // 左 = _ecx

      v4 = a1[3];     // 右 = _edx

    }

    // 注意:如果 sub-opcode 是 1 或其他非0/2/3 值?→ 保持默认(eax vs ebx)

  }

  else

  {

    // sub-opcode == 0:右操作数是立即数(1字节)

    v4 = *(unsigned __int8 *)(v2 + 2);

  }



  a1[5] = v2 + 3;      // 指令长度 = 3 字节:[opcode][type][imm8?]

  a1[4] = (v3 == v4);  // _tmp = (left == right) ? 1 : 0

  return result;

}

其余略

opcode

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

opcode=[0xD0, 0x20, 0x0, 0x0, 0x0, 0x0,

        0xD0,0x21, 0x25, 0x0, 0x0, 0x0,

        0xD4,

        0xD0, 0x20, 0x0, 0x2, 0x0, 0x0,

        0xD0, 0x61, 0x0, 0x0, 0x0, 0x0,

        0xD0, 0x61, 0x0, 0x0, 0x0, 0x0,

        0xD0, 0x21, 0x9, 0x0, 0x0, 0x0,

        0xD8,

        0xD0, 0x21, 0x26, 0x0, 0x0, 0x0,

        0xD1,

        0xD0, 0x21, 0x0, 0x2, 0x0, 0x0,

        0xD0, 0x62, 0x0, 0x0, 0x0, 0x0,

        0xD0, 0x22, 0x2C, 0x1, 0x0, 0x0,

        0xD7, 0x2,

        0xD0, 0x62, 0x0, 0x0, 0x0, 0x0,

        0xD1,

        0xD0, 0x21, 0x6, 0x0, 0x0, 0x0,

        0xD7, 0x1,

        0xD0, 0x21, 0x0, 0x2, 0x0, 0x0,

        0xD0, 0x62, 0x0, 0x0, 0x0, 0x0,

        0xD0, 0x22, 0x90, 0x1, 0x0, 0x0,

        0xD7, 0x2,

        0xD0, 0x62, 0x0, 0x0, 0x0, 0x0,

        0xD2, 0x1, 0x0, 0xD6, 0x0, 0x20,

        0xD0, 0x20, 0x8, 0x2, 0x0, 0x0,

        0xD0, 0x61, 0x0, 0x0, 0x0, 0x0,

        0xD0, 0x21, 0x1, 0x0, 0x0, 0x0,

        0xD7, 0x1,

        0xD0, 0x22, 0x8, 0x2, 0x0, 0x0,

        0xD0, 0x52, 0x0, 0x0, 0x0, 0x0,

        0xD0, 0x20, 0x0, 0x2, 0x0, 0x0,

        0xD0, 0x61, 0x0, 0x0, 0x0, 0x0,

        0xD0, 0x21, 0x1, 0x0, 0x0, 0x0,

        0xD7, 0x1,

        0xD0, 0x22, 0x0, 0x2, 0x0, 0x0,

        0xD0, 0x52, 0x0, 0x0, 0x0, 0x0,

        0xD0, 0x20, 0x0, 0x2, 0x0, 0x0,

        0xD0, 0x61, 0x0, 0x0, 0x0, 0x0,

        0xD0, 0x21, 0x25, 0x0, 0x0, 0x0,

        0xD2, 0x1, 0x0, 0xD6, 0x1, 0xB8,

        0xD0, 0x20, 0x8, 0x2, 0x0, 0x0,

        0xD0, 0x61, 0x0, 0x0, 0x0, 0x0,

        0xD0, 0x21, 0x25, 0x0, 0x0, 0x0,

        0xD2, 0x1, 0x0, 0xD6, 0x0, 0xE,

        0xD0, 0x20, 0x64, 0x0, 0x0, 0x0,

        0xD0, 0x21, 0x1F, 0x0, 0x0, 0x0,

        0xD5, 0xD3,

        0xD0, 0x20, 0xC8, 0x0, 0x0, 0x0,

        0xD0, 0x21, 0x19, 0x0, 0x0, 0x0,

        0xD5, 0xD3,

        0xD0, 0x22, 0x0, 0x0, 0x0, 0x0,

        0xD0, 0x51, 0x61, 0x62, 0x63, 0x64,

        0xD0, 0x20, 0x0, 0x0, 0x0, 0x0,

        0xD0, 0x21, 0x4, 0x0, 0x0, 0x0,

        0xD5, 0xD3]

opcode->asm

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

def disasm_vm(opcode_bytes):

    i = 0

    asm_lines = []

    while i < len(opcode_bytes):

        op = opcode_bytes[i]

        if op == 0xD0:

            if i + 5 >= len(opcode_bytes):

                break

            typ = opcode_bytes[i + 1]

            imm32 = int.from_bytes(opcode_bytes[i+2:i+6], 'little')

            if typ == 0x20:

                asm = f"mov eax, 0x{imm32:X}"

            elif typ == 0x21:

                asm = f"mov ebx, 0x{imm32:X}"

            elif typ == 0x22:

                asm = f"mov ecx, 0x{imm32:X}"

            elif typ == 0x23:

                asm = f"mov edx, 0x{imm32:X}"

            elif typ == 0x41:

                asm = f"mov eax, byte [buf + 0x{imm32:X}]"

            elif typ == 0x42:

                asm = f"mov ebx, byte [buf + 0x{imm32:X}]"

            elif typ == 0x51:

                asm = f"mov dword [buf + ecx], 0x{imm32:X}"

            elif typ == 0x52:

                asm = f"mov dword [buf + ecx], eax"

            elif typ == 0x61:

                asm = "mov eax, byte [buf + eax]"

            elif typ == 0x62:

                asm = "mov ebx, byte [buf + ebx]"

            else:

                asm = f"mov ??? (type=0x{typ:02X}), 0x{imm32:X}"

            asm_lines.append(asm)

            i += 6



        elif op == 0xD1:

            asm_lines.append("xor eax, ebx")

            i += 1



        elif op == 0xD2:

            if i + 2 >= len(opcode_bytes):

                break

            mode = opcode_bytes[i + 1]

            imm8 = opcode_bytes[i + 2]

            if mode == 0:

                asm = f"cmp eax, 0x{imm8:02X}"

            elif mode == 1:

                asm = "cmp eax, ebx"

            elif mode == 2:

                asm = "cmp ebx, ecx"

            elif mode == 3:

                asm = "cmp ecx, edx"

            else:

                asm = f"cmp ??? (mode={mode})"

            asm_lines.append(asm)

            i += 3



        elif op == 0xD3:

            asm_lines.append("nop")

            i += 1



        elif op == 0xD4:

            asm_lines.append("read")  # read(_eax, _ebx)

            i += 1



        elif op == 0xD5:

            asm_lines.append("print")  # print(_ebx chars)

            i += 1



        elif op == 0xD6:

            if i + 2 >= len(opcode_bytes):

                break

            mode = opcode_bytes[i + 1]

            imm8 = opcode_bytes[i + 2]

            if mode == 1:

                asm = f"jz backward 0x{imm8:02X}"  # 实际是 eip -= imm8

            else:

                asm = f"jz forward 0x{imm8:02X}"   # 实际是 eip += imm8 + 3

            asm_lines.append(asm)

            i += 3



        elif op == 0xD7:

            if i + 1 >= len(opcode_bytes):

                break

            mode = opcode_bytes[i + 1]

            if mode == 1:

                asm = "add eax, ebx"

            elif mode == 2:

                asm = "add ebx, ecx"

            elif mode == 3:

                asm = "add ecx, edx"

            else:

                asm = f"add ??? (mode={mode})"

            asm_lines.append(asm)

            i += 2

        elif op == 0xD8:

            asm_lines.append("sub eax, ebx")

            i += 1

        else:

            asm_lines.append(f"unknown opcode 0x{op:02X}")

            i += 1

    return asm_lines

opcode = [
    0xD0, 0x20, 0x0, 0x0, 0x0, 0x0,
    0xD0,0x21, 0x25, 0x0, 0x0, 0x0,
    0xD4,
    0xD0, 0x20, 0x0, 0x2, 0x0, 0x0,
    0xD0, 0x61, 0x0, 0x0, 0x0, 0x0,
    0xD0, 0x61, 0x0, 0x0, 0x0, 0x0,
    0xD0, 0x21, 0x9, 0x0, 0x0, 0x0,
    0xD8,
    0xD0, 0x21, 0x26, 0x0, 0x0, 0x0,
    0xD1,
    0xD0, 0x21, 0x0, 0x2, 0x0, 0x0,
    0xD0, 0x62, 0x0, 0x0, 0x0, 0x0,
    0xD0, 0x22, 0x2C, 0x1, 0x0, 0x0,
    0xD7, 0x2,
    0xD0, 0x62, 0x0, 0x0, 0x0, 0x0,
    0xD1,
    0xD0, 0x21, 0x6, 0x0, 0x0, 0x0,
    0xD7, 0x1,
    0xD0, 0x21, 0x0, 0x2, 0x0, 0x0,
    0xD0, 0x62, 0x0, 0x0, 0x0, 0x0,
    0xD0, 0x22, 0x90, 0x1, 0x0, 0x0,
    0xD7, 0x2,
    0xD0, 0x62, 0x0, 0x0, 0x0, 0x0,
    0xD2, 0x1, 0x0, 0xD6, 0x0, 0x20,
    0xD0, 0x20, 0x8, 0x2, 0x0, 0x0,
    0xD0, 0x61, 0x0, 0x0, 0x0, 0x0,
    0xD0, 0x21, 0x1, 0x0, 0x0, 0x0,
    0xD7, 0x1,
    0xD0, 0x22, 0x8, 0x2, 0x0, 0x0,
    0xD0, 0x52, 0x0, 0x0, 0x0, 0x0,
    0xD0, 0x20, 0x0, 0x2, 0x0, 0x0,
    0xD0, 0x61, 0x0, 0x0, 0x0, 0x0,
    0xD0, 0x21, 0x1, 0x0, 0x0, 0x0,
    0xD7, 0x1,
    0xD0, 0x22, 0x0, 0x2, 0x0, 0x0,
    0xD0, 0x52, 0x0, 0x0, 0x0, 0x0,
    0xD0, 0x20, 0x0, 0x2, 0x0, 0x0,
    0xD0, 0x61, 0x0, 0x0, 0x0, 0x0,
    0xD0, 0x21, 0x25, 0x0, 0x0, 0x0,
    0xD2, 0x1, 0x0, 0xD6, 0x1, 0xB8,
    0xD0, 0x20, 0x8, 0x2, 0x0, 0x0,
    0xD0, 0x61, 0x0, 0x0, 0x0, 0x0,
    0xD0, 0x21, 0x25, 0x0, 0x0, 0x0,
    0xD2, 0x1, 0x0, 0xD6, 0x0, 0xE,
    0xD0, 0x20, 0x64, 0x0, 0x0, 0x0,
    0xD0, 0x21, 0x1F, 0x0, 0x0, 0x0,
    0xD5, 0xD3,
    0xD0, 0x20, 0xC8, 0x0, 0x0, 0x0,
    0xD0, 0x21, 0x19, 0x0, 0x0, 0x0,
    0xD5, 0xD3,
    0xD0, 0x22, 0x0, 0x0, 0x0, 0x0,
    0xD0, 0x51, 0x61, 0x62, 0x63, 0x64,
    0xD0, 0x20, 0x0, 0x0, 0x0, 0x0,
    0xD0, 0x21, 0x4, 0x0, 0x0, 0x0,
    0xD5, 0xD3,
]

asm = disasm_vm(opcode)

for idx, line in enumerate(asm):
    print(f"{idx+1:2d}: {line}")

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

 1: mov eax, 0x0

 2: mov ebx, 0x25

 3: read

 4: mov eax, 0x200

 5: mov eax, byte [buf + eax]

 6: mov eax, byte [buf + eax]

 7: mov ebx, 0x9

 8: sub eax, ebx

 9: mov ebx, 0x26

10: xor eax, ebx

11: mov ebx, 0x200

12: mov ebx, byte [buf + ebx]

13: mov ecx, 0x12C

14: add ebx, ecx

15: mov ebx, byte [buf + ebx]

16: xor eax, ebx

17: mov ebx, 0x6

18: add eax, ebx

19: mov ebx, 0x200

20: mov ebx, byte [buf + ebx]

21: mov ecx, 0x190

22: add ebx, ecx

23: mov ebx, byte [buf + ebx]

24: cmp eax, ebx

25: jz forward 0x20

26: mov eax, 0x208

27: mov eax, byte [buf + eax]

28: mov ebx, 0x1

29: add eax, ebx

30: mov ecx, 0x208

31: mov dword [buf + ecx], eax

32: mov eax, 0x200

33: mov eax, byte [buf + eax]

34: mov ebx, 0x1

35: add eax, ebx

36: mov ecx, 0x200

37: mov dword [buf + ecx], eax

38: mov eax, 0x200

39: mov eax, byte [buf + eax]

40: mov ebx, 0x25

41: cmp eax, ebx

42: jz backward 0xB8

43: mov eax, 0x208

44: mov eax, byte [buf + eax]

45: mov ebx, 0x25

46: cmp eax, ebx

47: jz forward 0x0E

48: mov eax, 0x64

49: mov ebx, 0x1F

50: print

51: nop

52: mov eax, 0xC8

53: mov ebx, 0x19

54: print

55: nop

56: mov ecx, 0x0

57: mov dword [buf + ecx], 0x64636261

58: mov eax, 0x0

59: mov ebx, 0x4

60: print

61: nop

解密

转成高级语言

1
2
3
4
5
6
7
8
L = 0x25
t1 = [...]  
t2 = [...]  

for i in range(1, L):
    flag[i] = (((t2[i] - 6) ^ t1[i]) ^ 0x26) + 9

print(flag.decode())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enc=[0x32, 0x26, 0x18, 0x21, 0x41, 0x23, 0x2A, 0x57, 0x44, 0x29,
     0x35, 0x12, 0x20, 0x17, 0x45, 0x1C, 0x68, 0x2D, 0x7A, 0x79,
     0x47, 0x7F, 0x44, 0x9, 0x1E, 0x75, 0x41, 0x2A, 0x19, 0x34,
     0x76, 0x47, 0x14, 0x50, 0x52, 0x76, 0x58]

key = [0x57, 0x65, 0x6C, 0x63, 0x6F, 0x6D, 0x65, 0x5F, 0x74, 0x6F,
       0x5F, 0x73, 0x64, 0x6E, 0x69, 0x73,0x63, 0x5F, 0x32, 0x30,
       0x31, 0x38, 0x5F, 0x42, 0x79, 0x2E, 0x5A, 0x65, 0x72, 0x6F,
       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]

flag=''
for i in range(len(enc)):
     tmp=((enc[i] - 6) ^ key[i] ^ 0x26) + 9
     flag+=chr(tmp)
print(flag)
# flag{_p1us_babyL0gin_pPpPpPpPp_p1us_}

参考链接

https://www.cnblogs.com/a1rhthm/p/18092363

https://mp.weixin.qq.com/s?__biz=Mzk2NDM2NTU1NQ==&mid=2247483666&idx=1&sn=7a480e284f9b51853e6da22e6790fdfb&scene=21#wechat_redirect


vm初窥
https://alenirving.github.io/2025/12/14/vm初窥/
作者
Ma5k
许可协议
CC-BY-NC-SA