首页
社区
课程
招聘
[原创]通过编译优化进行VMP代码还原
发表于: 2017-5-13 14:40 23626

[原创]通过编译优化进行VMP代码还原

2017-5-13 14:40
23626

0x00 写在前面

如x86的一条指令如mov eax, [ebp+0x100],转化为VMP伪代码会变成类似如下的代码:

3. VMP版本更新,膨胀规则可能会稍有变化,工具通用性会非常差,必须持续跟踪不同版本。


对于CISC指令集虚拟机的还原难度会有一定的降低(但也很难),已有的工作可以参考DeathWay的Oreans UnVirtualizer OD插件(http://bbs.pediy.com/thread-192434.htm),对早期Themida\Code Virtualizer的CISC机可以做到很好的代码还原,还原出的指令基本和原始指令一致,非常优秀的工作!


其他还有Zeus、VMSweeper、OoWoodOne插件等等不多介绍。


VirtualDeobfuscator:某年blackhat上的议题 (https://github.com/jnraber/VirtualDeobfuscator   Blackhat视频:https://www.youtube.com/watch?v=hoda99l5y_g ) 这个工具是通过化简trace除去虚拟机的逻辑,然后留下指令的逻辑方便分析。思路很清奇。不过我测试对VMP效果一般。按文章描述对CISC指令集似乎效果会好一些。



好了,这就确定了我的目标:将VMP伪代码还原成语义等价易读的代码(如汇编或者C代码)。

同时因为自己代码能力有限且时间有限,在保证目标的前提下我希望尽量降低开发难度。


这就需要将VMP伪代码转化成常见编译器(gcc、clang)能处理的形式。首先想到的是LLVM,但开发过程用到LLVM相关的库,开发和调试都比较困难,后来直接利用C作为中间代码进行转化。


用我们的方法变成C语言,伪指令会变成C代码:

这是超级简化例子,实际C代码 a = b在编译的时候会涉及栈变量的读写,比如可能变成

用C语言的宏表示如下:

这个时候又能展示编译器的强大优化能力了,如果编译如下C代码

使用gcc -O3编译优化,得到的结果程序拖到IDA中可以得到如下的伪代码

使用clang -O3编译优化,效果如下。

如果采用传统的规则匹配来化简NOR逻辑,则需要加入很多对应的匹配规则。而如果某一天这些规则发生变化,则又需要修改匹配规则。当使用编译器来化简,问题就变得简单了许多。


0x02 实现

4. 结果文件放到IDA中还原回C代码。


提取伪代码这一块前面的工作已经比较成熟,不再花时间造轮子了。直接使用VMP分析插件v1.4进行提取,伪代码格式类似如下:

如vAdd4转化成如下代码

其他具体细节还有很多,这里不多介绍,有兴趣的可以留言联系进行深入交流。


all_op2.gcc在IDA中反编译的结果:

可以神奇的发现主要代码逻辑已经被清晰的还原出来了。

要注意到代码中是包含数组访问的、同时还有与、或等逻辑运算,都较好的还原了出来。

为了不占用页面不贴汇编代码了,汇编代码可以自己看附件中的程序。


可以看到前面的示例中是不含条件和循环的。因为VMP处理跳转和条件跳转的时候会将这些直接跳转全部转化为间接跳转。以条件跳转为例,VMP会先将两个跳转的地址压处栈中,再根据比如的flag计算值决定使用哪个地址作为跳转目标。这一部分很难直接利用编译器进行优化。 目前还没有想到太好的解决方法。

汇编上表示如下:

VMP表示如下:

比如


可以看到前面的例子中原始代码是不含局部变量的,也就是不含sub esp类的指令的。


由于虚拟栈作为了局部变量数组,这种方法编译生成的汇编码比较冗长,也难以阅读,不过不影响IDA的反编译。

IDA反编译时似乎也会进行简单的优化,因此显示效果还不错。

受方法本质的限制,是不能恢复出和原始代码一模一样的汇编的,也不能运行。但代码逻辑是基本一致的。


这里说一下私心,之所以敢于在方法和工具不成型的时候就提前把方法分享出来。是希望有感兴趣的和懂VMP的大牛指点一下。论坛上有很多前辈在这方面研究十分深入,经验丰富。这个方法算是拍脑袋想到的方法,存在不少问题。希望大牛们对提到的几个问题有什么改进的思路,或者发现了其他可能存在的问题,都麻烦留言不吝赐教。(尤其是前面提到间接跳转问题,让我十分头疼。如果大大们有好的想法,请一定留言指教)

测试程序放在附件里,如果有感兴趣的,请务必留言联系。


多谢各位。


VMProtect 虚拟机保护软件是业界公认的高强度软件保护工具。VMP除了具有常规的IAT保护、资源保护、反调试、完整性校验、运行时壳等保护手段以为,其最为人痛恨的是虚拟化保护。通过将原始的二进制汇编代码转化成语义等价的虚拟机字节码(也常称为PCODE,伪代码),并使用自定义虚拟机(或称字节码解释器)对字节码进行解释执行。想恢复原始的代码,必须分析虚拟机本身,大大提高了逆向分析的难度。
据笔者目前查到的资料,尚无公开的工具或方法可以进行VMP虚拟机字节码到原始二进制代码的还原。
个人觉得主要原因在于VMP虚拟机的RISC栈机体系结构与x86的CISC体系结构差异巨大。
(实际上ebp, efl, tmp, eax都会对应VMP的虚拟寄存器,这里为了方便表达暂时这么写)
一条CISC指令转化成RISC栈机指令时会发生巨大的指令膨胀。想将膨胀后的伪代码还原为一条CISC汇编指令是很困难的。
1. 如果单纯依靠模式规则的匹配,这么必须收集非常多的膨胀规则,需要付出巨大的人力代价,开发难度高,却不一定有好的效果。
2. 由于VMP的寄存器轮转问题,确定指令的真实寄存器是很困难的。(2009年中国软件安全峰值 Bughoho的PPT 《VMProtect的逆向分析与静态还原》中详细描述了寄存器轮转并提出了解决方案,但这种方案比较复杂,开发难度比较高。)
对于VMP则还没有见到有公开的可用的,可以直接还原成原始代码的工具开放出来。但也有很多前辈的研究成果。部分如下:
FKVMP:nooby&fengyue大佬开发的插件,应该耳熟能详。nooby是很早研究VMP插件的前辈,FKVMP可以进行handler的识别得到伪指令序列,早期神器
VMP分析插件v1.4:zdhysd大佬开发的(http://bbs.pediy.com/thread-154621.htm  )毫不过分的说这里目前市面的可以见到的最好用的VMP分析插件,没有之一。可以自行添加handler识别规则和化简规则。除了基础的识别handler之外,最酷的功能是表达式化简功能,可以将VMP伪代码转化成可读性更好的表达式,并进行了数据流分析进行表达式化简,内置近100条化简规则,化简后的表达式可以看到和原始代码相当接近的语义。非常强大!小问题是由于不是常见的汇编代码或C代码,阅读起来仍不是特别方便。
ZVM:zvrop 开发的工具,开放了源码(http://bbs.pediy.com/thread-155215.htm)根据文档说明该工具是可以直接将早期版本的VMP还原为汇编指令的,工作覆盖从handler认识到伪代码收缩转化,非常全面。系统非常复杂,6万行C++实现。不过我还没有编译运行过,不确定效果如何(有兴趣的同学可以试试,回帖说明,多谢)

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 2
支持
分享
打赏 + 1.00雪花
打赏次数 1 雪花 + 1.00
 
赞赏  CCkicker   +1.00 2017/05/22
最新回复 (39)
雪    币: 1696
活跃值: (2297)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
2017-5-13 16:52
0
雪    币: 3738
活跃值: (3872)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
3
顶!
2017-5-13 16:58
0
雪    币: 133
活跃值: (233)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
2017-5-13 18:05
0
雪    币: 248
活跃值: (3789)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
不会还原VMP的看过来
2017-5-13 18:38
0
雪    币: 153
活跃值: (723)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
怎么请教哦,没联系方式
2017-5-13 18:54
0
雪    币: 275
活跃值: (320)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
7
这想法有点意思,挺好玩的,那个VMP分析1.4也是做了与编译器差不多的操作的.
2017-5-13 19:40
0
雪    币: 152
活跃值: (20)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
写的不错...小菜鸟学习一下..........
2017-5-13 20:01
0
雪    币: 622
活跃值: (283)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
9
currwin 这想法有点意思,挺好玩的,那个VMP分析1.4也是做了与编译器差不多的操作的.
是的  我就是看到那个神器用这种方法效果很好,就想着能不能直接使用编译器将伪代码化简,也就有了这个贴子。  不知道VMP分析1.4的开发者现在还上不上看雪,很想和他交流一下
2017-5-13 20:23
0
雪    币: 47147
活跃值: (20450)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
10
感谢分享!
2017-5-13 21:10
0
雪    币: 15
活跃值: (393)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
厉害啊楼主!!!
2017-5-13 21:16
0
雪    币: 6112
活跃值: (1212)
能力值: (RANK:30 )
在线值:
发帖
回帖
粉丝
12
顶!!
2017-5-13 21:25
0
雪    币: 1535
活跃值: (695)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
顶!
2017-5-13 21:25
0
雪    币: 62
活跃值: (27)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
感谢!!!!!!
2017-5-13 21:37
0
雪    币: 1361
活跃值: (1121)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
15
2017-5-13 21:48
0
雪    币: 325
活跃值: (186)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
16
精彩的思路,可惜没接触过编译原理。。。
2017-5-13 23:55
0
雪    币: 4741
活跃值: (4291)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
看来随着时间的推移VMP还原的方法不在是密密了
2017-5-14 08:15
0
雪    币: 783
活跃值: (1121)
能力值: ( LV5,RANK:78 )
在线值:
发帖
回帖
粉丝
18

001B00BC: pushfd 
001B00C2: test efl, 0x00000100
001B0247: jnz 0x001B1E95
001B030F: popfd 
001B04E8: rvm_1C = 0x004025AC
001B0504: push rvm_1C
001B050A: eax = ebp + 0xFFFFFFFC
001B053C: pop [ebp + 0xFFFFFFFC]
001B0541: push eax
001B05C7: call 0x0040C7A5
001B05F4: rvm_38 = 0xFF0677A3
001B066D: [ebp + 0xFFFFFFF8] = eax
001B0683: ebx = [ebp + 0xFFFFFFFC]
001B068D: test ebx, ebx
001B0828: jz 0x001B0AE0
001B08F0: push ebx
001B0976: call 0x0040CCF1
001B0A15: push iEFL
001B0040: popfd 
001B0A1C: esp = {add 4, __$esp + 8}
001B0AE0: label_1B0AE0:
001B0BA2: eax = ebp + 0xFFFFFFF8
001B0BBE: push eax
001B0C44: call 0x0040C9AC
001B0CF0: [ebp + 0xFFFFFFF4] = eax
001B0D00: ebx = [ebp + 0xFFFFFFF8]
001B0D0A: test ebx, ebx
001B0EA7: jz 0x001B1161
001B0F6F: push ebx
001B0FF5: call 0x0040CCF1
001B1089: rvm_0C = iEFL
001B1094: push iEFL
001B0040: popfd 
001B109B: esp = {add 4, {add __$esp, 4}}
001B1161: label_1B1161:
001B123C: [ebp + 0xFFFFFFF0] = esp
001B1241: eax = 0
001B1262: push [ebp + 0xFFFFFFF4]
001B12E6: call 0x0040CD09
001B1382: cmp [ebp + 0xFFFFFFF0], esp
001B1586: jz 0x001B183E
001B164E: push 6
001B16D3: call 0x0040CD03
001B176E: esp = esp + 8
001B183E: label_1B183E:
001B1918: ebx = [ebp + 0xFFFFFFF4]
001B1922: test ebx, ebx
001B1AA7: jz 0x001B1D60
001B1B6F: push ebx
001B1BF5: call 0x0040CCF1
001B1C94: push iEFL
001B0040: popfd 
001B1C9B: esp = {add 4, __$esp + 8}
001B1D60: label_1B1D60:
001B1E90: jmp 0x0040C7A1
001B1E95: label_1B1E95:
001B1F58: push edi
001B0040: popfd 
001B1FEE: jmp 0xE22021D6

VMSweeper 插件已经能很大一部分还原了.基本优化之类的也做的很好..不过VM引擎内的一些数据处理需要自己 区分删除...原理应该是记录指令集 然后处理逻辑优化 化简公式 然后记录轮转表 反推寄存器....不过实际应用过程中有很多地方有问题.大体上应该也是够用的

  话说 师傅们都好厉害啊...这玩意构思到写完没个半年都够呛..

2017-5-14 10:31
0
雪    币: 2575
活跃值: (502)
能力值: ( LV2,RANK:85 )
在线值:
发帖
回帖
粉丝
19
现在论坛估计能写VMP和还原的应该有很多人,关键是投入值不值的问题
2017-5-14 11:12
0
雪    币: 622
活跃值: (283)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
20
bambooqj 001B00BC: pushfd  001B00C2: test efl, 0x000 ...
我用VMSweeper总是会失败  不知道什么情况。。  你是用的VMSweeper还原的哪个版本的VMP?  如果能提供下样本是最好了 
2017-5-14 12:22
0
雪    币: 783
活跃值: (1121)
能力值: ( LV5,RANK:78 )
在线值:
发帖
回帖
粉丝
21

VMSweeper    根据我测试    3.x  往下都能还原啊.特别早的版本应该也是不行的.还原的话  加壳的经常失败.还原需要先脱壳    不然崩溃很多.不知道为什么  个别不脱壳也能还原一些.他这个比较繁琐..需要先扫描一下全部的VM点  区分到自己能识别的部分.不过经常识别失败..问题很多.....

http://www.jmpoep.com/thread-3059-1-1.html        这里是我之前写的插件使用方法..

主要现在有模拟执行.只要确定分支然后模拟记录.我觉得现在轮转应该已经不是难点了.主要是代码的优化 和 膨胀还原 

2017-5-14 12:56
0
雪    币: 240
活跃值: (433)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
22
好文  ,支持
2017-5-14 15:51
0
雪    币: 240
活跃值: (433)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
23
现在Pc上就这块比较神秘了吧
2017-5-14 15:52
0
雪    币: 622
活跃值: (283)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
24
bambooqj VMSweeper    根据我测试    3.x  往下都能还原啊.特别早的版本应该也 ...
我尝试了1.7  2.04  2.09  的最快速度保护下的VMP(默认是不加壳的)  VMSweeper  都不能正常工作。。
层主能直接发个可以分析的VMP样本上来么    我对VMSweeper还原的方法还是挺感兴趣的   
之前看雪有贴子讲过工作原理  不过讲的不太详细  尤其是代码还原这块
层主发的链接因为没注册过这个论坛无权访问  方便复制点关键内容么
2017-5-14 18:53
0
雪    币: 783
活跃值: (1121)
能力值: ( LV5,RANK:78 )
在线值:
发帖
回帖
粉丝
25




穆恩

我尝试了1.7 2.04 2.09 的最快速度保护下的VMP(默认是不加壳的) VMSweeper 都不能正常工作。。
层主能直接发个可以分析的VMP样本上来么  我对VMSweeper还原的方法还 ...

不会吧..老板是不是插件用错了...    给我个样本  我试试..我就是自己随便写的EXE  然后用VMP  2.13.5加密的.VMP 2.09 我也用过.可以啊..没理由啊.

上传的附件:
2017-5-14 19:41
0
游客
登录 | 注册 方可回帖
返回
//