通过上面4个简单的例子其实我们已经可以得出来一些结论:
1. 如果一行代码对寄存器的值进行覆盖,那么之前对这个寄存器的值进行的任何运算、赋值的代码都是垃圾代码,直到遇到对这个寄存器进行访问操作、传递给其他寄存器、写入内存或者作为运算访问的代码为止(前提是这行代码不是垃圾代码)。
2.如果一行有效代码对某个寄存器进行访问操作、传递给其他寄存器、写入内存。那么之前对这个寄存器的值进行的任何运算、赋值的代码都是有效代码,直到对这个寄存器进行值覆盖的操作。
通过以上两个结论我们就可以自己写代码去除垃圾代码.
我们这里使用的是 capstone.lib 引擎,以逆向的思路从下往上分析代码。
比如下面这段代码我们从 mov edx,0x20 开始向上分析之前对edi的操作是否有效。
不知道大家是否这样,我手工去除混淆代码的时候都是从下往上分析的。
mov edi,0x10
mov ebx,0x30
push ebx
mov edi,0x20
其实在处理寄存器优化之前还有对 CALL/JMP 这些指令的处理。这里就不贴出来了,大家有兴趣可以下载代码。虽然写的比较丑陋 !-_-
// 寄存器优化
void AntiConfuse::reg_opt()
{
uint64_t address = 0x1000;
std::ofstream ofs2("clear_jump.asm", std::ios::out | std::ios::trunc);
auto count = cs_disasm(handle, process_code.data(), process_code.size(), address, 0, &insn);
init_reg_access_info();
for (int i = 0; i < count; ++i)
{
ofs2 << insn[i].mnemonic << "\t" << insn[i].op_str << std::endl;
}
for (int t = count - 1; t >= 0; t--) // 从下往上分析代码
{
process_one_code(handle, &insn[t], t);
}
cs_free(insn, count);
}
init_reg_access_info 函数初始化默认的寄存器访问权限。由于我们是从下往上开始分析的默认对寄存器的写入操作和修改操作都当作有效代码,因为他们都会影响这段代码最终的结果值。
void AntiConfuse::init_reg_access_info()
{
reg_access_info_.clear();
add_access_flag("eax", CS_AC_WRITE);
add_access_flag("eax", CS_AC_WRITE | CS_AC_READ);
add_access_flag("ebx", CS_AC_WRITE);
add_access_flag("ebx", CS_AC_WRITE | CS_AC_READ);
add_access_flag("ecx", CS_AC_WRITE);
add_access_flag("ecx", CS_AC_WRITE | CS_AC_READ);
add_access_flag("edx", CS_AC_WRITE);
add_access_flag("edx", CS_AC_WRITE | CS_AC_READ);
add_access_flag("edi", CS_AC_WRITE);
add_access_flag("edi", CS_AC_WRITE | CS_AC_READ);
add_access_flag("esi", CS_AC_WRITE);
add_access_flag("esi", CS_AC_WRITE | CS_AC_READ);
add_access_flag("ebp", CS_AC_WRITE);
add_access_flag("ebp", CS_AC_WRITE | CS_AC_READ);
std::cout << reg_access_info_.size() << std::endl;
}
去寄存器混淆的代码都在process_one_code这个函数中,相对来说代码是比较简单的。但是很有效果!
bool AntiConfuse::process_one_code(csh handle, cs_insn *insn, uint32_t line)
{
// 对一些特殊的指令进行特殊执行, 比如XCHG之类的...这里我偷懒还没写完,不过没遇到这个指令就不影响
switch (insn->id)
{
case X86_INS_RET:
line_valid_[line] = true;
return true;
}
line_valid_[line] = false; // line_valid_指定对应行数的代码是否有效 默认是无效的行
auto x86 = &(insn->detail->x86);
// 或者一条指令的操作数 比如 mov eax, [esp + 0x10]
// 就会又两个操作数 一个是 eax, 一个是[esp + 0x10]
// 这个循环主要根据 寄存器的写入权限来判断,这个指令是否有效
for (int i = 0; i < x86->op_count; i++)
{
cs_x86_op *op = &(x86->operands[i]);
switch (op->access) {
case CS_AC_WRITE:
case CS_AC_READ | CS_AC_WRITE:
// 如果是写入内存 我们判定为有效的 判定所有对内存写入值的代码都是有效的,关于栈的内存要写代码特殊处理一下
if (X86_OP_MEM == (int)op->type)
{
line_valid_[line] = true;
set_memory_access(handle, op);
break;
}
std::string reg_name = cs_reg_name(handle, op->reg);
reg_name = map_reg_name(reg_name);
if (has_access_flag(reg_name, op->access)) {
// 如果对应的寄存器有对应的权限,那么认为这行代码是有效的代码
// 才可以去分析这条指令所访问的一些寄存器,否则这条指令如果是垃圾指令继续分析也没有意义
line_valid_[line] = true;
// 如果说只有写入说明是SET操作,相当于 mov eax,0x20, pop eax这种对寄存器的值进行覆盖的操作
// 根据我们的上面总结的结论就是一旦对一个值进行赋值操作
// 那么前面所有对这个寄存器的操作都是无效的,直到对这个寄存器进行访问操作。
// 所以这里我们遇到了值覆盖操作,就清除这个寄存器的所有的权限
if (op->access == CS_AC_WRITE) {
clear_access_flag(reg_name);
}
}
break;
}
}
// 根据上面写入操作的结果来判断是否要继续分析 这条指令所访问的寄存器
// 如果这条指令的写入操作是无效的,那么就可以直接返回
if (false == line_valid_[line]) {
return false;
}
// 这里是处理这条这里访问的操作数
for (int i = 0; i < x86->op_count; i++)
{
cs_x86_op *op = &(x86->operands[i]);
if (CS_AC_READ != op->access)
{
continue;
}
// 如果是写入内存 我们判定为有效的
if (X86_OP_MEM == (int)op->type)
{
set_memory_access(handle, op);
continue;
}
else if (X86_OP_REG == (int)op->type)
{
// 这里代表访问了某个寄存器
// 在根据文章中说的第二个结论
// 只要对某个寄存器进行访问操作,那么之前所有的对这个寄存器进行写入或者修改的代码都是有效的代码
// 所以这里我们给寄存器加上了 修改和赋值的权限
std::string reg_name = cs_reg_name(handle, op->reg);
reg_name = map_reg_name(reg_name);
clear_access_flag(reg_name);
add_access_flag(reg_name, CS_AC_WRITE);
add_access_flag(reg_name, CS_AC_WRITE | CS_AC_READ);
continue;
}
}
return true;
}
就这几个简单的函数跑完,所有关于寄存器的垃圾代码就全部消失了。
可以运行一下程序 输入test.asm 这个文件在工程目录下。
这个文件就是汇编字节指令文件,里面的内容是VMP的虚拟机入口代码。
处理完毕后会生成
1. org_asm.asm 原始的汇编指令
2. clear_jump.asm 清除CALL/JMP之后的汇编指令
3. clear_reg.asm 清除了 寄存器混淆和堆栈混淆的代码
下面是VMP 虚拟机入口没去混淆之前代码在org_asm.asm中
push ebp
mov ebp, esp
sub esp, 0xc0
push ebx
push esi
push edi
lea edi, [ebp - 0xc0]
mov ecx, 0x30
mov eax, 0xcccccccc
rep stosd dword ptr es:[edi], eax
mov esi, esp
push 0x1276b30
jmp 0x7d212
call 0xfffce0ef
mov byte ptr [esp], 0x2e
pushfd
jmp 0x5c9e3
mov dword ptr [esp + 4], 0x470b69
jmp 0xfffd12c8
mov word ptr [esp], ax
jmp 0xfffc2111
mov dword ptr [esp], 0x574407c3
mov dword ptr [esp], 0x5bb80365
pushfd
lea esp, [esp + 4]
jmp 0xfffd7765
call 0xa17
mov dword ptr [esp], ebx
pushfd
call 0x2473
pushfd
mov dword ptr [esp + 8], ebp
jmp 0x13ff
pushfd
pop dword ptr [esp + 4]
pushal
mov dword ptr [esp + 0x20], eax
call 0x674
pushal
push esi
pushfd
mov dword ptr [esp + 0x48], esi
push 0x96946d46
bswap si
movsx si, al
mov dword ptr [esp + 0x48], edi
movzx si, al
call 0xffffffbe
movsx esi, cl
lea esi, [eax - 0x6bdf482a]
mov dword ptr [esp + 0x48], ecx
pushfd
mov dword ptr [esp + 0x48], edx
lea esi, [eax - 0x1dabd533]
lea esi, [ecx + 0x4a01d49e]
call 0xa8d
jmp 0x20e6
mov dword ptr [esp + 0x48], ecx
pop esi
mov word ptr [esp + 4], cx
push dword ptr [0x127cf79]
pop dword ptr [esp + 0x40]
bswap si
mov dword ptr [esp + 0x3c], 0xe60000
movsx si, al
pop esi
push edx
pop esi
mov esi, dword ptr [esp + 0x68]
pushfd
push 0x153008d3
push dword ptr [esp]
lea esp, [esp + 0x44]
jmp 0x2b5a
pushal
xchg al, dh
pushal
not esi
inc bl
jmp 0xbd
call 0x1a2d
pop ebx
dec esi
bswap bp
neg esi
sal bp, 0xf
lea ebp, [esp + 0x40]
rol edi, 1
cmp al, 0x9d
call 0x152e
sub esp, 0x7c
cmc
mov edi, esp
sar bh, cl
xadd dl, bh
or ebx, 0x2971f122
jmp 0xffffe91b
mov ebx, esi
lea edx, [ebx + 0x65526054]
setl al
rcr al, 1
bts edx, edx
add esi, dword ptr [ebp]
cmp ah, dh
movzx dx, al
mov al, byte ptr [esi - 1]
dec edx
lea edx, [ebx + 0x65476f3c]
xor al, bl
neg edx
neg al
cmc
add dl, cl
cmc
add al, 0xcb
ror dh, cl
call 0x1390
ror al, 6
shr dl, cl
xor bl, al
not dh
movzx dx, cl
call 0x1175
call 0x3877
movzx eax, al
bswap dx
mov edx, dword ptr [eax*4 + 0x1280f10]
bt di, 3
cmc
ror edx, 5
mov word ptr [esp], 0x905
jmp 0x1173
call 0x2de
jmp 0x601
dec esi
pushfd
clc
cmc
pushal
add edx, 0xe60000
pushfd
mov dword ptr [esp + 0x34], edx
mov byte ptr [esp + 4], bl
mov byte ptr [esp + 0x10], 0x39
mov word ptr [esp], ax
push dword ptr [esp + 0x34]
ret 0x38
这里是用程序处理过寄存器混淆、堆栈混淆的代码,清除之后的指令都是有效指令。
push ebp
mov ebp, esp
sub esp, 0xc0
push ebx
push esi
push edi
lea edi, [ebp - 0xc0]
mov ecx, 0x30
mov eax, 0xcccccccc
rep stosd dword ptr es:[edi], eax
mov esi, esp
push 0x1276b30
sub esp, 4
pushfd
mov dword ptr [esp + 4], 0x470b69
mov dword ptr [esp], 0x5bb80365
sub esp, 4
mov dword ptr [esp], ebx
pushfd
sub esp, 4
pushfd
mov dword ptr [esp + 8], ebp
pushfd
pop dword ptr [esp + 4]
pushal
mov dword ptr [esp + 0x20], eax
mov dword ptr [esp + 0x1c], esi
mov dword ptr [esp + 0x18], edi
mov dword ptr [esp + 0x14], ecx
mov dword ptr [esp + 0x10], edx
mov dword ptr [esp + 0xc], ecx
push dword ptr [0x127cf79]
pop dword ptr [esp + 8]
mov dword ptr [esp + 4], 0xe60000
mov esi, dword ptr [esp + 0x34]
push 0x153008d3
lea esp, [esp + 8]
pushal
pushal
not esi
dec esi
neg esi
lea ebp, [esp + 0x40]
sub esp, 4
sub esp, 0x7c
mov edi, esp
mov ebx, esi
add esi, dword ptr [ebp]
mov al, byte ptr [esi - 1]
xor al, bl
neg al
add al, 0xcb
sub esp, 4
ror al, 6
xor bl, al
movzx eax, al
mov edx, dword ptr [eax*4 + 0x1280f10]
ror edx, 5
dec esi
add edx, 0xe60000
mov dword ptr [esp], edx
push dword ptr [esp]
ret 4
虽然还有pushfd pushad这些指令,但是这些指令也都是有效的。不过也可以继续简化指令。
后续的简化其实很简单,不过这篇可能没时间说了。本来想在这篇也把堆栈混淆也说一下,时间有限只能放在下一篇去说了。
代码中很多细节没在意...
附件密码是 bbs.pediy.com