vmp3.3.1 虚拟机分析

虚拟机概述

1564389415873

一个正常的二进制文件,假设为x,那么执行他的是Windows操作系统,也就是解释器,假设为A,

那么vmp加壳的时候,会根据加壳文件的代码,转化为自己的代码,但行为不变,假设push eax,的字节码为0x03,那么vmp就会修改这个值,假设为0x04,但实际执行的时候还是回执行push eax,现在出现了一个新的问题,push eax的字节码被修改了,解释器A,无法将0x04解释为push eax。在vmp将原字节码转换为vmp的字节码之后,vmp还会生成一个解释器,假设为B,解释器B就是来解释,经过vmp修改过后的代码,也就是上文中0x04这个字节码,实际的执行顺序如下

解释器A – 执行 – 解释器B – 执行 – vmp修改过后的代码。

当我们分析vmp代码的时候,实际上分析的是解释器B。

既然解释器B是在执行虚拟机的代码,而虚拟机代码是模拟的vmp加壳之前的代码,加壳之前的代码是需要寄存器来执行的,而我们在调试中看到的寄存器已经被解释器B使用了。

那么在堆栈中会出现一个结构体,我们称之为VMContext,下文会详细介绍。

还有就是vmcode有自己的堆栈地址,并不是保存在esp中。

加密源码

1564401836257

开启变态加密

未开启反调试

生成后文件

1564401897119

源文件3KB,加密后551KB,一个惊人的增长

代码分析

初始化

1564398341996156439835665315643983713471564398418945

上图为vmp开始的代码,我们只关心push指令,它将所有的寄存器压入栈,最后将0压入堆栈,

此时堆栈保存的数据顺序如下

1
2
3
4
5
6
7
8
9
10
0
edx
flags
esi
ecx
ebp
edi
ebx
eax
key

继续F7单步

1564398935939

上面这段代码,主要的作用,取出vmcontext中的key,进行计算,而这个key计算结束的值就是vmcode的代码位置。

还有两行代码我们需要注意

1
2
3
4
5
6
00429382    8BFC            mov edi,esp 
;将当前esp的值赋值给edi,这个edi就是vmcode的堆栈的栈顶
;通过上文我们可以看出了栈顶的顺序就是上述堆栈的顺序
;那么取出的值则为0
00429384 8DA424 40FFFFFF lea esp,dword ptr ss:[esp-0xC0]
;在堆栈开辟0xC0个字节的空间,这个空间就是vmcontext,它的首地址保存在esp中

F7单步继续

1564399065949

上面这段代码中最重要的就是

1
lea esi, dword ptr ds :[0x462ABD]

这个地址是第一个handler的地址,这个handler指的是执行vmcode的代码,也就是在vmcode中取出代码,在handler中执行,在上图中jmp之前的代码就是在vmcode的地址中取出下一行要执行的代码。

继续单步

1564399507137

我们看到ebp进行了加4,可以看出虚拟机的代码是倒着走的。

单步一直走,走到带有ret的语句。

1564399608000

上图中的ecx是经过计算后的偏移,和esi相加就是第一个handler的地址。

在以前的版本中会有一个VMDispatcher,来决定下一次handler跳转的位置,而在新版本中,去掉了这个功能,很多脱壳神器也就都失效了,换而取代的是 push .. ret或者jmp esi 等等。

跳到第一个handler地址,继续单步。

1564400047871

上图为第一个handler的代码,执行到此,我们还需要说一下vmcode中的堆栈问题,vmcode的堆栈地址并不是固定保存在一个寄存器中的,vmcode的地址,和handler的地址也是一样,并不是固定在一个寄存器中的,它可能在某一个handler中就保存到其他的寄存器中了。

上图中的寄存器对应如下

  • edi保存了vmcode的堆栈首地址

  • ebp保存了vmcode的首地址

  • handler的地址在esi中。

  • esp保存了vmcontext的首地址,vmcontext的地址将永远保存在esp中不会改变

上图代码主要工作如下

1
2
3
4
5
6
mov edx,dword ptr ds:[edi]
lea edi,dword ptr ds:[edi+0x4]
;上面两行代码是一个标准的pop指令,将栈顶的值临时保存在edx中
movzx ecx,byte ptr ss:[ebp]
lea ebp,dword ptr ss:[ebp+0x1]
;将vmcode的下一个指令取出来

F7单步经过一个jmp之后的代码如下

1564401479169

上图中高亮代码,是将在栈顶取出来的值保存到vmcontext的偏移的位置中,我们刚有提过esp中保存的永远都是vmcontext的地址,上图中的ecx的值为0x10,edx为上一次在堆栈中取出的值为0。

而在这之后在ebp中取出4个字节的代码,并把ebp进行加4,然后edx解密得到偏移,加上esi就是下一个handler的地址。

在最后一行代码跳转过去的代码为jmp esi 跳转到下一个handler。

15644024446881564402703985

这个handler的代码作用与原理同上,在堆栈中取出内容保存到vmcontext,只是保存位置有变化,保存在0x4的偏移中,然后在ebp中取出代码经过计算通过jmp esi跳转到下一个handler。

后面的几个handler的都是在做这件事情(初始化vmcontext),避免篇幅过大,就不贴出代码了。

初始化结束后的vmcontext如下

1
2
3
4
5
6
7
8
9
10
11
[vmcontext + 0x10] = 0
[vmcontext + 0x04] = edx = 00401000
[vmcontext + 0x28] = FLAGS = 00000246
[vmcontext + 0x38] = esi = 00401000
[vmcontext + 0x24] = ecx = 00401000
[vmcontext + 0x0C] = ebp = 0019FF7B
[vmcontext + 0x20] = edi = 00401000
[vmcontext + 0x1C] = ebx = 0027D000
[vmcontext + 0x2C] = eax = 0019FFCC
[vmcontext + 0x08] = 返回地址 = 004650CB
[vmcontext + 0x14] = key = A6EC6D77

至此 vmcontext初始化完毕。

正式执行代码

1564403874592

在上一行代码高亮的地方

1
2
3
4
5
6
0044CAC5    8DBF FCFFFFFF   lea edi,dword ptr ds:[edi-0x4]
;edi保存了vmcode堆栈的地址,进行减4,开辟4字节空间
...
0044CACF 8907 mov dword ptr ds:[edi],eax
;eax的值为2019,将这个值放到edi中,也就是栈顶
;上述代码可还原为 push 2019 也就是我们源代码的第一行代码

继续F7单步走

15644041387451564404163486

每次压栈后,在跳转到下一个handler之前都会有一个当前vmcode栈空间的判断,如果当前栈顶超过了esp+60,就要进行栈空间分配。

分配的代码如下

15644043032751564404322967156440433806515644043532761564404365073

混肴代码太多

上图代码总结

  • 开辟空间
  • edi esi eflags 寄存器保存
  • 利用 esi 和 edi 寄存器,保存开辟空间之前,和开辟空间之后的位置
  • 通过ecx 和 rep mosb 指令将vmcontext进行恢复
  • 恢复 edi esi eglags寄存器
  • 跳转到下一个handler

到此基本上了解了虚拟机的执行流程。

接下来的代码就没那么幸运了,因为在vmcode中,包含了很多混肴代码,做一些无用的工作,大大浪费了分析的空间,我通过在栈地址中使用硬件断点跳过这些代码,直接看vmp调用call的过程。

1564407462138

将保存在vmcontext的值取出来赋值给原寄存器

此时的堆栈如下

1564407515176

执行完成API返回到401026继续虚拟机指令。

Good Job.

需要注意的地方

  • 同样的应用程序生成进行两次vmp加壳,所生成的两个EXE,那么这两个EXE的代码不论是在初始化还是在其他地方代码都会有很多不同,但是他们的目的是一致的,假设在初始化部分之前的寄存器压栈,两个不同的exe的jmp指令的多少或者位置都会不同,但它们都是在做同一件事情,就是将key、寄存器、0压入栈。
  • 在不同的handler中,除了vmcontext的地址是固定保存在esp中之外,其他的均不会固定保存在一个寄存器中,而是在不同的寄存器中进行轮询。
  • vmcontext的偏移位置在每次编译的时候也不同

1571159879653

Author: BarretGuy
Link: https://basicbit.cn/2019/01/01/2018-11-03-vmp3.3.1 虚拟机分析/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.