(编辑:jimmy 日期: 2024/12/24 浏览:2)
这篇文章主要是分析vmp3.5对je、jne、jge和jl分支指令的模拟。因为识别出vmp的分支指令后,可以利用符号执行或者其它的trace工具得到程序的全部路径的执行trace,然后合并这些路径重建程序混淆前的控制流图CFG,最后对控制流图进行优化应该是可以还原出与原程序语义上等价的代码。
x86指令的cmp实际是执行的减法操作,只是不改变操作数的值,而是程序状态寄存器eflags的值。vmp模拟的减法指令如下:
x-y = ~(~x+y)
标志位还原如下:
eflags1 : (~x+y)elfags2 : ~(~x+y)eflags = (eflags1 & 0x815) + (elfags2 & ~0x815)
为什么vmp使用0x815可以参考《VMP学习笔记之万用门(七)》这篇文章。
这里先给出根据trace构建的eflags还原的DAG表示和jge指令模拟执行的DAG表示。
图片1.png
图片2.png
以上两个DAG图都是优化过后得到的,对MBA表达式进行了处理,不然会很难看。主要优化了以下运算:
a&a = aa|a = aa|b = ~(~a & ~b)a&b = ~(~a | ~b)b = ~a, c=~b --> c=aa-b = ~(~a+b)a^b = (a|b) & (~a | ~b)~(a^b) = (a & b) | (~a & ~b)
在eflags还原的DAG中,0xfffff7ea = ~0x815,还原后的eflags值分别与0x80和0x800进行and运算,0x80和0x800对应的是sf和of标志位。分别and运算后也会产生两个eflags值,对应的是第二个DAG图里的eflags10_0和eflags12_68。
令 y = (~(eflags10_0 ^ eflags12_68) & (~0xffffffbf)) 6
上面这个运算就是为了判断SF标志位是否等于OF标志位,如果等于则输出为1,否则为0。SF==OF刚好是jge指令的跳转条件,这里~0xffffffbf = 0x40是取zf标志位的值。
令dword_ss[0xffffcf14] = 0xffffffff + y,y等于0或1则vmbytecode读取指针vm_ip = (dword_ss[0xffffcf14] & 0x43db9e) + (~dword_ss[0xffffcf14] & 0x43dc22) + dword_ss[0xffffcfcc]
0x43dc22为满足跳转条件时的读取地址,否则为0x43db9e。这里加一个dword_ss[0xffffcfcc]是为了重定位,如果程序加载的是默认基址的话dword_ss[0xffffcfcc]为0。同理,vmp访问全局变量前也是要重定位处理。需要注意的是模拟执行的过程中出现的一些常量并不是固定的。比如0xffffffbf、0x815、0x80、0x800等,有时候会对这些常量取反。
图片3.png
首先给出Jne指令的模拟执行的DAG表示
图片4.png
jne指令和jge指令模拟执行的区别在于上图下面的异或运算被替换成了eflags的还原运算,直接获取ZF标志位进行判断,其它都差不多。而jne指令与je指令的区别在于eflags的还原运算后有没有取反。je指令模拟执行的DAG表示如下:
图片5.png
从以上几个分支指令的DAG图可以看出vmp模拟执行分支指令的一些特征如下:
1、eflags寄存器的值参与了运算,正常编写程序几乎不会用到eflags寄存器。
2、模拟分支指令的过程中会用到一些常量,比如0xffffffbf、0x815、0x80、0x800等。
3、vm_ip的两个跳转地址也参与了运算。
根据以上几个特征以及一些模拟运算的特征,可以判断vmp是否在模拟分支指令的执行。模拟过程中的常量比如0x40、0x800等可以确定可能使用了哪些分支指令,一般是两个相反跳转条件的分支指令。再根据eflags还原后的结果或者异或运算后的结果有没有取反来确定是哪一个分支指令。最后根据用于修改vm_ip的两个目的地址中,有兄弟节点是取反运算的则为满足跳转条件的地址,比如je指令的DAG图里的0x40add8就是满足跳转条件的目的地址。
根据分支指令确定各个执行路径,再利用这些路径的trace还原程序混淆前的CFG图,最后对CFG图进行全局优化达到虚拟化还原的目的是可行的。但是,这种方法会有一个路径爆炸问题。这里附上用于测试jge指令代码虚拟化混淆后还原的CFG图,由于没有做全局的赋值传播和公共子表达式删除,所以还原的代码还有些瑕疵。这次就不分享代码了,感兴趣的可以在之前分享的代码自行修改。
图片6.png