前言
-
作为一个学习/练手的项目,自发写了一个保护x86的exe/dll的变异混淆器(Mutation),类似于CodeVirtualizer的那种(不过CV是VM保护,而且还支持sys)。分别用到了capstone、asmjit和cyxvc大佬的部分库。实现了代码多重变异,代码乱序,jcc指令转换,假分支干扰等功能。
-
相比VMP/TMD的变异保护,强度还可以。但是兼容性比较差,没有考虑程序的异常处理而且没经过大规模测试,可能会有很多bug,仅供参考学习。
-
本文仅分享一下我写Mutation混淆器的一些思路,希望能抛砖引玉。
正文
1.变异的基本思想(扫盲)
代码变异/代码混淆的根本思想都是**“等价替换”。在等价替换的前提下借用寄存器、栈、堆**等来膨胀代码,将1条指令替换成多条指令。
例如以下的mov指令
mov eax,ebx
可以借用ecx寄存器,将其变异成
mov ecx,ebx
mov eax,ecx
但是这样还不行,因为我们破坏了ecx的环境。还必须要先对ecx做一个保存,事后还原它。
push ecx
mov ecx,ebx
mov eax,ecx
pop ecx
这样一个最简单的代码变异就完成了。
2.指令分析处理的思路
要想写出自动化的变异工具,第一步就要先对目标指令进行逐步地拆解分析。
除了少部分没有Operand的指令(syscall等等),大部分指令都可以分为助记符(OP)+Operand的形式。即我们先在助记符这个方向上初步区分出指令来,再在此基础上从Operand的方向进一步拆分出指令的具体类型。
2.1先以助记符拆分
mov eax,ebx
以上面mov指令为例,我们借用capstone反汇编引擎,解析出这条指令的类别为“mov”。
//判断是不是mov指令
if (strcmp(insn.mnemonic, "mov") == 0)
return(_mov());
add eax,ebx
同理以add指令为例,也需先解析出他的指令类别为“add”
//判断是不是add指令
if (strcmp(insn.mnemonic, "add") == 0)
return(_add());
以此初步地区分出目标指令为mov指令还是add指令或是其他的指令类别。
2.2再以Operand拆分
Operand又分为3种情况,分别为:
reg(寄存器),imm(立即数,常数),mem(内存)。
以上文解析出的mov指令为例,搭配这3种Operand的组合,又可进一步分出5种指令类型:
mov reg,reg
mov reg,imm
mov reg,mem
mov mem,reg
mov mem,imm
我们只要针对这5种指令类型写混淆规则,就可以将x86mov指令的所有情况包括进来。
当然,Operand其实还可以再往下分:根据Operand的位数又可以继续分出8位、16位、32位、(64位)。
如果想针对性地写的更细一点,可以选择再分一次。
以下是判断Operand类型的代码
//mov reg,reg
if (x86->operands[0].type == X86_OP_REG && x86->operands[1].type == X86_OP_REG)
return(_mov_reg_reg(x86->operands[0].reg, x86->operands[1].reg));
//mov reg,imm
if (x86->operands[0].type == X86_OP_REG && x86->operands[1].type == X86_OP_IMM) {
imm.address = (DWORD)insn.address;
imm.imm_value = (DWORD)x86->operands[1].imm;
imm.imm_offset = x86->encoding.imm_offset;
imm.imm_size = x86->encoding.imm_size;
return(_mov_reg_imm(x86->operands[0].reg, &imm));
}
//mov_reg_mem
if (x86->operands[0].type == X86_OP_REG && x86->operands[1].type == X86_OP_MEM) {
mem.address = (DWORD)insn.address;
mem.disp_offset = x86->encoding.disp_offset;
mem.disp_size = x86->encoding.disp_size;
mem.base = x86->operands[1].mem.base;
mem.index = x86->operands[1].mem.index;
mem.scale = x86->operands[1].mem.scale;
mem.disp = x86->operands[1].mem.disp;
mem.mem_size = x86->operands[1].size;
return(_mov_reg_mem(x86->operands[0].reg, &mem));
}
//mov_mem_reg
if (x86->operands[0].type == X86_OP_MEM && x86->operands[1].type == X86_OP_REG) {
mem.address = (DWORD)insn.address;
mem.disp_offset = x86->encoding.disp_offset;
mem.disp_size = x86