首页
论坛
课程
招聘
[原创]对VM逆向的分析(CTF)(比较经典的一个虚拟机逆向题目)
2021-5-18 21:02 13090

[原创]对VM逆向的分析(CTF)(比较经典的一个虚拟机逆向题目)

2021-5-18 21:02
13090

2020GKCTF-EzMachine(很典型的一个VMRe)

简单的vm-re框架
图片描述

 

虚拟机就是要去模仿一个机器,让机器去执行一个文件(类似用win10去执行一个文件)

 

首先它需要一些在CPU中的寄存器和内存中的堆栈,这样去模拟一个CPU不断的去读取指令

 

主要是以循环的形式进行读取

 

1.在全局变量中分配如下内容:
图片描述
这里就构成了CPU+内存

 

2.模拟一个CPU读取指令的形式(dispatcher)去写这样一个主程序

 

主程序:其实就是一个循环,这个循环不断的去读取指令(伪机器码opcode)这个会存在于内存中或文件中,然后执行指令opcode所对应的一些函数(其实这些函数可以理解为伪汇编的执行过程,比如就像一些add,sub,pop,push,jmp等操作),这样下来就可以与真实的程序执行相差无几

WriteUp

拿到程序之后先查壳,发现无壳之后运行一下查看,只有一个plz input:

 

我们将其直接拖入ida中进行查看

 

左侧函数窗口CTRL+F查找main函数

 

跳转到main函数之后发现有个花指令

 

图片描述

 

将地址0x401594处的0xB8换成0x90(nop的硬编码)即可

 

然后P一下main函数按Tab进行反汇编

 

如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int i; // [esp+10h] [ebp-8h]
 
  sub_6914F0();
  while ( 1 )
  {
LABEL_2:
    for ( i = 0; ; i += 2 )
    {
      if ( i >= 0x58 )
        goto LABEL_2;
      if ( dword_6D48F0[4 * i] == opcode_team[ei_p] )// opcode_team存在于内存中的伪机器码(指令)
        break;
    }
    dispatcher[i]();    // 模拟一个CPU不断的去执行指令(其实就是根据opcode去执行一些模拟汇编的一些函数)
  }
}

dispatcher就是去实现模拟CPU读取指令

 

opcode_team就是存在于内存中(也可存在于文件中)的opcode(伪机器码)

 

点开dispatcher,如下(这里就是一些模拟汇编的伪汇编函数),依次进行分析:

 

分析之后的:

 

图片描述
图片描述

 

分析过程:(不一一举例,举几个例子)
图片描述

 

什么也没做,就只是++了eip,说明该函数模拟的是NOP

 

图片描述
这里点击过去发现了连续的一个数组和一些字符串数据,说明模拟的是内存和CPU中的寄存器
图片描述
这里模拟的就是mov reg, data

 

又比如sub_691070

 

图片描述

 

这个模拟的就是push data,同时我们也在内存中找到了模拟栈

 

以此类推,将全部伪汇编的函数分析出来,然后写出解析脚本:

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
opcode_team = [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_key = {
    0: 'nop',
    1: 'mov reg data',
    2: 'push data',
    3: 'push_reg',
    4: 'pop_reg',
    5'printf',
    6'add_reg_reg1',
    7'sub_reg_reg1',
    8'mul',
    9'div',
    10: 'xor',
    11: 'jmp',
    12: 'cmp',
    13: 'je',
    14: 'jne',
    15: 'jg',
    16: 'jl',
    17: 'scanf_strlen',
    18: 'mem_init',
    19: 'stack_to_reg',
    20: 'load_input',
    0xff: 'exit'}
count = 0
code_index = 1
for x in opcode_team:   # 取每个opcode
    if count % 3 == 0# 每3个opcode是一条指令,每个指令的第一个是操作码
        print(str(code_index)+':', end='')
        print(opcode_key[x], end=' ')
        code_index += 1
    elif count % 3 == 1:
        print(str(x)+',', end=' ')
    else:
        print(str(x))
    count += 1
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
//打印出来的结果如下
1:mov reg data 3, 3
2:printf 0, 0
3:scanf_strlen 0, 0
4:mov reg data 1, 17
5:cmp 0, 1
6:je 10, 0
7:mov reg data 3, 1
8:printf 0, 0
9:exit 0, 0
10:mov reg data 2, 0
11:mov reg data 0, 17
12:cmp 0, 2
13:je 43, 0
14:load_input 0, 2
15:mov reg data 1, 97
16:cmp 0, 1
17:jl 26, 0
18:mov reg data 1, 122
19:cmp 0, 1
20:jg 26, 0
21:mov reg data 1, 71
22:xor 0, 1
23:mov reg data 1, 1
24:add_reg_reg1 0, 1
25:jmp 36, 0
26:mov reg data 1, 65
27:cmp 0, 1
28:jl 36, 0
29:mov reg data 1, 90
30:cmp 0, 1
31:jg 36, 0
32:mov reg data 1, 75
33:xor 0, 1
34:mov reg data 1, 1
35:sub_reg_reg1 0, 1
36:mov reg data 1, 16
37:div 0, 1
38:push_reg 1, 0
39:push_reg 0, 0
40:mov reg data 1, 1
41:add_reg_reg1 2, 1
42:jmp 11, 0
43:push data 7, 0
44:push data 13, 0
45:push data 0, 0
46:push data 5, 0
47:push data 1, 0
48:push data 12, 0
49:push data 1, 0
50:push data 0, 0
51:push data 0, 0
52:push data 13, 0
53:push data 5, 0
54:push data 15, 0
55:push data 0, 0
56:push data 9, 0
57:push data 5, 0
58:push data 15, 0
59:push data 3, 0
60:push data 0, 0
61:push data 2, 0
62:push data 5, 0
63:push data 3, 0
64:push data 3, 0
65:push data 1, 0
66:push data 7, 0
67:push data 7, 0
68:push data 11, 0
69:push data 2, 0
70:push data 1, 0
71:push data 2, 0
72:push data 7, 0
73:push data 2, 0
74:push data 12, 0
75:push data 2, 0
76:push data 2, 0
77:mov reg data 2, 1
78:stack_to_reg 1, 2
79:pop_reg 0, 0
80:cmp 0, 1
81:jne 91, 0
82:mov reg data 1, 34
83:cmp 2, 1
84:je 89, 0
85:mov reg data 1, 1
86:add_reg_reg1 2, 1
87:jmp 78, 0
88:mov reg data 3, 0
89:printf 0, 0
90:exit 0, 0
91:mov reg data 3, 1
92:printf 0, 0
93:exit 0, 0
94:nop

分析和注释:

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
mov edx, 3
printf(...)
scanf(our_input)
mov eax, strlen(our_input)
mov ebx, 17
cmp eax, ebx
je (10)     //相等才跳转到position0
mov edx, 1
printf(...)
exit(0)          //不相等就退出了
(10)mov ecx, 0
mov eax, 17
cmp eax, ecx
je (43)                   //直至计数器等于17
eax = load_input[ecx]     //循环读取我们的input
mov ebx, 'a'
cmp eax, ebx
jl (26)                   //该字符小于'a'则跳转到26
mov ebx, 'z'
cmp eax, ebx
jg (26)                  //该字符大于'z'则跳转到26
mov ebx, 71
xor eax, ebx            
mov ebx, 1
add eax, ebx             //否则将该字符(char^71+1)
jmp (36)
(26)mov ebx, 'A'
cmp eax, ebx
jl (36)                 //该字符比'A'小则跳转到36
mov ebx, 'Z'
cmp eax, ebx
jg (36)                //该字符比'Z'大则跳转到36
mov ebx, 75           
xor eax, ebx
mov ebx, 1
sub eax, ebx          //否则将该字符(char^75-1)
 
(36)mov ebx, 16
div eax, ebx          //将该字符%16
push ebx             
push eax              //先压入整除的数字,再压入余数
mov ebx, 1
mov ecx, 1
jmp(11)
(43)push         //压入一连串的数据,
...
...
(76)push 2
(77)mov ecx, 1
(78)stack_to_reg 1, 2
pop eax
cmp eax, ebx
jne (91)
mov ebx, 34       //17拆成34个(整数和余数)
cmp ecx, ebx
je (89)
mov ebx, 1
mov ecx, 1
jmp(78)
mov edx, 0        //设置返回值为0
(89)printf(..)    //打印失败字符串
exit(..)          //退出程序
(91)mov edx, 1    //设置返回值为1
printf(..)        //打印成功字符串
# exit(..)        //退出程序
nop

最后理解程序的逻辑之后写出解题脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
data = [0x7,0xd,0x0,0x5,0x1,0xc,0x1,0x0,0x0,0xd,0x5,0xf,0x0,0x9,0x5,0xf,0x3,0x0,0x2,0x5,0x3,0x3,0x1,0x7,0x7,0xb,0x2,0x1,0x2,0x7,0x2,0xc,0x2,0x2,]
data = data[::-1]
flag = ''
for i in range(0, 34, 2):
    temp = data[i] + data[i+1]*16
    x = ((temp+1) ^ 75)
    y = ((temp-1) ^ 71)
    if x>=65 and x<=90:    # 'A'-'Z'
        flag += chr(x)
    elif y>=97 and y<=122:   # 'a' - 'z'
        flag += chr(y)
    else:
        flag += chr(temp)    # 没有处于'a'-'z'或'A'-'Z'之间
print(flag)

得到flag值为:

 

图片描述

 

flag{Such_A_EZVM}

总结

再遇见vm-re的题目,先查找到opcode(伪机器码),然后找到dispatcher(就是模拟CPU读取指令的分发器),然后边分析那些伪汇编函数(就是模仿汇编指令的函数)边查找模拟的CPU的栈,寄存器,全局变量(多是字符串)等

 

参考资料:
https://www.bilibili.com/video/BV1Nv411k7HJ
https://blog.csdn.net/weixin_43876357/article/details/108488762


[2022夏季班]《安卓高级研修班(网课)》月薪两万班招生中~

最后于 2021-7-29 11:52 被SYJ-Re编辑 ,原因:
上传的附件:
收藏
点赞4
打赏
分享
最新回复 (4)
雪    币: 21
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
hello1 活跃值 2021-7-3 22:48
2
1

 为何将0xB8换成0x90?


雪    币: 7044
活跃值: 活跃值 (7158)
能力值: ( LV9,RANK:290 )
在线值:
发帖
回帖
粉丝
SYJ-Re 活跃值 3 2021-7-29 11:55
3
0

简单patch,这样就可以改变从你光标开始的字节,你还可以下载插件keypatch,放到plugins目录即可

最后于 2021-7-29 11:56 被SYJ-Re编辑 ,原因:
雪    币: 3203
活跃值: 活跃值 (1306)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
木志本柯 活跃值 2021-7-29 13:44
4
0
呦西get到了vm_nop
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_xxlwhnrm 活跃值 2021-10-22 07:44
5
0
终于更新文章了,呜呜
游客
登录 | 注册 方可回帖
返回