VMP分析-去除无用指令
在VMProtect混淆中,经常可以看到对一个寄存器进行了一堆操作,最后却直接把这个寄存器覆盖了。例如下面代码,刚给给ebx
赋上0xabcd
然后做了一系列运算,但是最后直接把他清空了,因此前三句就是无用指令。
mov ebx, 0xabcd
add ebx, 0xcdef
not ebx
xor ebx, ebx
参考了B站大佬的视频里的算法,通过一个简单的原则——对同一个寄存器的连续两次写入,前一次写入是无用的,就可以处理大部分情况。然后我们再假设对内存的操作都是有效的,因为在VMP中,用内存进行局部混淆的代码几乎没有。还有一些代码如xor rax,rax
看起来是对rax既有读、又有写,但实际上我们知道,这行汇编等价于mov rax,0
,因此只有对rax
的写入,这类指令需要我们做特殊处理。
数据结构设计
在这个项目中,我把类似于rax
、eax
、ah
、al
的寄存器当做不同的寄存器,但是对大的寄存器进行读写的操作时,也会同时对小的寄存器进行读写。EFLAGS
中每个标志位都把他看成单独的寄存器,一次提高匹配粒度。
每条指令都要记录对寄存器的读写,以及对EFLAGS
中每个标志位的读写,因此需要四个变量,每个变量的对应位都代表了某个特定寄存器。除此之外,还有2个属性keep
、useless
用于手动标记该条指令需要保留或者是无用指令。deleted
为识别输出的结果,表示识别出来这条指令是无用的。因此有下面的结构体。
class InstInfo{
public:
cs_insn *inst;
uint64_t regs_read{0}, regs_write{0};
uint16_t eflags_read{0}, eflags_write{0};
bool keep{false}, useless{false}, deleted{false};
}
初始化
在构造上面结构体的时候,要识别出这条指令的寄存器读写操作、以及EFLAGS
的读写操作。如果这条指令是要对内存进行操作,那么就要标记keep
。 好在现在的反汇编引擎(这里用的是capstone)已经足够强大,可以直接获得对哪些寄存器以及标志位进行了读写。 只需要对特殊指令进行特殊处理,如xor rax,rax
,可以世纪中边用边完善。
算法实现
对代码段进行逆序遍历,因为如果是正序遍历,遍历到某行代码时并不知道这行代码写入的寄存器会不会被二次写入;而逆序遍历从后往前,可以明确知道每个寄存器是否需要使用到。
在代码中建立了last_reg_write
和last_eflags_write
两个变量,分别用于记录对哪些寄存器的写入操作是无效的,如果该位为1,则对该位的写入为无效。因此他们的初始值为0,表示最后执行完所有寄存器的值都是有用的,也可以根据实际情况进行修改。
然后如果当前指令被删除了就跳过这行指令,如果没有标记keep
那么就把这条指令regs_write
中可以删除的操作进行删除——即把last_reg_write
中为1的位在regs_write
中设为0。然后这样操作之后,如果这条指令没有任何写操作,就可以放心的把这条指令删除了。
接着更新last_reg_write
,由于汇编读写同一个寄存器肯定是先读再写,那么逆序就是先处理写、再处理读。如果这条汇编写入了一个寄存器,那么就可以标记未来对这个寄存器的写是可以删除的,即将last_reg_write
对应位置1。 如果这条汇编读了一个寄存器,那么就需要把可以删除的标记去掉,即将last_reg_write
对应位置0。
在这样的一轮循环后,就可以完成所有无用指令的识别,通过位运算,大大提高了执行效率。代码如下
void InstManager::simplify() {
uint64_t last_reg_write = 0;
uint16_t last_eflags_write = 0;
for (auto inst_ptr = insts->rbegin(); inst_ptr != insts->rend(); inst_ptr++) {
InstInfo *inst = *inst_ptr;
if (inst->isDeleted() || inst->isUseless()) {
continue;
}
if (!inst->isKeep()) {
bool t = (inst->regs_write & last_reg_write) || (inst->eflags_write & last_eflags_write);
inst->regs_write &= ~last_reg_write;
inst->eflags_write &= ~last_eflags_write;
if (t && inst->regs_write == 0 && inst->eflags_write == 0) {
inst->setDeleted(true);
continue;
}
}
last_reg_write |= inst->regs_write;
last_reg_write &= ~inst->regs_read;
last_eflags_write |= inst->eflags_write;
last_eflags_write &= ~inst->eflags_read;
}
}
效果
从vmp中抽取了一小段代码,识别结果如下。红色背景的指令是可以删除的指令,黄色文字的指令是标记需要保留的指令。
总结
通过上面的算法,可以让对vmp的分析更加方便。还可以将它写成IDA、x96dbg的插件,这样可以在动态调试中辅助人工分析。
项目链接:ri-char/devmp