快速认识 VMP 3.x 第一部分 壳代码篇

查看 115|回复 9
作者:BeneficialWeb   
本帖翻译自r0da的博客 (链接地址)[https://whereisr0da.github.io/blog/posts/2021-01-05-vmp-1/]

大家好,这是我第一次研究VMProtect。VMP是一款非常有名的加壳保护软件,它具有很多功能,其中最主要的功能是代码变异和虚拟化,但是Part 1只涉及VMP中最简单的保护措施。其他保护措施将会在后面的帖子进行讨论。现在我们先关注壳代码和输入表混淆。

壳代码
壳代码主要是对可执行文件的节区进行压缩和加解密,从而防止逆向人员进行静态分析。考虑到这种方式在程序执行阶段代码和节区会被解密出来,因此保护方法一般不是很有成效。

针对这种情况,VMProtect不会将真实原始文件节区信息存储在PE文件头里。这种保护措施听上去很腻害。但是仍然有一些虚拟地址和大小应该始终要存在PE头里,这样Windows内核才能为可执行文件分配正确大小的空间。因此在不考虑地址空间布局随机化的情况下,我们仍然可以需要留心节区大小和一些可能地址。
关于原始文件的内容,几乎全部被放进了.vmp1节区,这个节区包括了被加密的节区内容,壳代码,节区信息等。
唯一没有被保护的节区是.rsrc,因为Windows需要读取它来提取程序图标和其他应用信息用以显示在属性菜单上。VMProctect有一个保护资源的选项,他通过把资源分成两部分,一部分是针对Windows需要而创建的;一部分是程序自身运行时所需的资源信息,只在程序执行过程中被加密和解密。
截图中除了.data节区,其他的节区都是不可写入的。所以VMP将会调用VirtualProtect或者类似的函数来改变节区的属性,然后修改节区内容。VMP 3.x以前,VMP使用VirtulProtect函数,之后它使用了未文档化的内核API函数ZwProtectVirtualMemory来达到同样的目的。
因为ZwProtectVirtualMemory会在PE加载时会被Windows内部被频繁调用,所以我们只在到达PE入口点后,才对其进行下断操作。

可以注意到vmp程序的入口点看起来像是一个VMProtect 虚拟化化后的函数。在VM 2.x以前,VMP的壳代码是未被虚拟化的。我们很容易在单步运行push,ret指令后跳转到OEP。

现在我们知道VMP会在解码中会修改两次节区属性,一次用于写入数据,一次是用于恢复节区属性。在一定量的断点命中后,我们应该可以得到ZwProtectVirtualMemory的调用次数。
n = 不可写入的节区数
n+1 (.vmp0): 改变写保护标志的次数
1: 移除拷贝标志位的次数
n+1 (.vmp0) 恢复原始标志位
因此vmp执行脱壳代码的次数为((n+1)*2)+1。这里有一张反映这种操作的动态图,注意观察右边的标志位变化。

在壳代码运行完后,我们就可以通过任意工具来dump 转储可执行文件,不过目前还没有修复导入表。
以我对vmp相关研究来看, vmp0节区包含了虚拟化和变异后的程序代码,以及IAT相关的代码。vmp1节区包含了所有与壳机制相关的东西,例如被加密的节区,虚拟化后的壳代码,节区信息等。
寻找OEP
由于壳代码被虚拟化了,因此想通过vmp代码来找到oep相当困难。我们需要学习一些技巧:
[ol]
  • 我们可以监视eip寄存器的值变化,尤其是当eip寄存器的值从vmp1切换到其他节区,而不是.vmp0时。这样应该对于寻找OEP来说能起作用。我尝试过编写Qiling脚本来实现这个功能,但是Qiling脚本并没有实现ZwProtectVirtualMemory这个函数,这导致我们的工作无法进展下去。后来我写了一个unicorn Python脚本来实现了这个任务。
  • 我们也可以通过对.text节区下硬件执行断点来达到同样的结果。在我的例子中,oep是.text节区上的第一个函数,但是这种情况比较少见,不可能所有受vmp保护的程序都满足这种情况。
  • 你还可以通过查看第一个函数堆栈的cookie来寻找oep。使用vc++编译的程序,他的栈cookie通常是0x2B992DDFA232。详细内容请参考这篇优秀文章
    4.你也可以手动寻找oep。通过尝试观察栈底来寻找第一个可能是OEP的返回地址。
    [/ol]
    IAT 混淆
    VMP的IAT混淆是一个可选项,并不是默认设置的。有时你也会遇到vmp保护的程序不会发生重建IAT的情况,原因是开发者不擅长使用vmp加壳。当然,我肯定懂加壳,帖子里说的情况也是开了IAT混淆保护的。
    首先,我们会看到原始IAT依旧被保存在了PE文件中,但却不会被使用。

    虽然你能看到程序使用了哪些API,却不能通过交叉引用来静态分析,原因是导入地址是运行时动态计算的。当然如果是我来实现IAT保护,我应该是通过导入一个dll的随机函数来实现,而不是保留原来的所有东西;或者干脆完全删除IAT的内容,用LoadLibrary加载每个DLL以后,再解决导入表问题。我们通过查看vmp程序调用API的完整流程,可以注意到相关API调用的代码只是进行了变异操作,而没有进行虚拟化保护。
    每一个IAT相关函数的调用都在.text节区,比如说call dword ptr ds:[], 是6个字节长度的指令。VMP将会把这种原始的API调用改变成这种类型的调用:
    push 随机寄存器
    call 变异后的api解析器
    为了保持代码对齐,这两条指令也是6字节大小的。每一个API 调用都有一个变异后的api解析器函数。这也解释了为什么脚本会有大量的输出,当然也有一部分原因是代码虚拟化。VMP使用一个随机寄存器来传递一个参数给API解析器。
    这里给出一个变异后的版本例子。随机寄存器选择为edi。
    注意:正如我所说,这个代码在.vmp0节区。
    nop
    not di
    bswap di
    jmp ...
    pop edi
    jmp ...
    xchg dword ptr ss:[esp], edi
    push edi
    not edi
    xchg di, di
    jmp ...
    mov edi, 0x401113
    mov edi, dword ptr ds:[edi + 0x2B21E]
    jmp ...
    lea edi, dword ptr ds:[edi + 0x724F2141]
    jmp ...
    xchg dword ptr ss:[esp], edi
    jmp ...  
    ret
    化简后的版本(reg代表随机寄存器)
    # reg 是被 push 的寄存器
    # 下面的代码是得到要调用的返回地址
    pop reg
    # reg与返回地址交换值
    # 因此返回的时候会调用到相应的API
    xchg dword ptr ss:[esp], reg
    # 设置未来的返回地址用来跳转到API函数
    push reg
    # 计算函数地址
    # magic number
    mov reg, 0x401113
    # 可理解为获取VMP针对 kernel32.dll 重构后的IAT地址
    mov reg, dword ptr ds:[reg + 0x2B21E]
    # 获取CreateProcessW地址
    lea reg, dword ptr ds:[reg + 0x724F2141]
    # put the API function address on stack top, using the "push reg" above
    # 将API函数地址放到栈顶
    xchg dword ptr ss:[esp], reg  
    # 跳转到API函数
    ret
    做个总结,vmp使用push建立了一个栈变量,然后通过ret跳转到API函数地址。
    地址的计算方式如下:
    reg = 0x401113        : magic number
    ds:[reg + 0x2B21E]    : VMP针对kernel32.dll重构后的IAT地址
    ds:[reg + 0x724F2141] : CreateProcessW的实际地址
    我没有足够的时间去编码实现一个导入表修复器,但是这确实可以通过模拟器来实现,比如说unicorn等。
    0xnobody, can1357 和 mrxodia 写了一些工具来脱壳和修复64位程序的导入表。
    (工具链接地址)[https://github.com/0xnobody/vmpdump]

    代码, 地址

  • BeneficialWeb
    OP
      


    Hmily 发表于 2021-12-21 14:41
    感谢翻译分享,BeneficialWeb可以把图片上传论坛本地,图床打开很慢。

    我这边打开挺快的
    gueijiaochen   

    感谢翻译分享,BeneficialWeb可以把图片上传论坛本地,图床打开很慢。
    ynboyinkm   

    来学习下!!
    plauger   

    vm是我的最爱!!!!
    aonima   

    还有第二部分,楼主加油
    sun12345   

    感谢翻译
    00hlz   

    感谢感谢
    py66   

    感谢分享,有脱壳工具吗?
    aonima   

    看也看不懂
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部