经不完全统计,该程序供使用了如下四种混淆:
1.花指令
2.常量展开
3.模式替换
4.打乱代码顺序局部性
花指令可以被使用递归下降反汇编算法的IDA识别。如果要去除,则只需搜索E9 01 00 00 00 ??并替换为90 90 90 90 90 90即可。
打乱代码顺序局部性对分析的影响不大,解混淆时就不单独处理了。其实有许多解混淆插件对乱序的处理效果非常好。
模式替换和常量展开在该程序中被结合使用,以我随便选的一段代码为例。
.ZP1:00423023 sub esp, 4 .ZP1:00423026 mov [esp+114h+var_114], esi .ZP1:00423035 mov esi, 403539h .ZP1:0042303A sub esi, offset loc_40363D .ZP1:00423046 not esi .ZP1:0042304E xchg esi, [esp+114h+var_114] = > push 0x103 //这可以看成是模式替换与常量展开的嵌套使用 push imm => //模式匹配 sub esp,4 mov [esp],imm => //常量展开 sub esp,4 mov [esp],reg mov reg,xxx ..... //对reg的解密 xchg [esp],reg解除模式替换时,需要搜集程序使用的模式进行逆变换,对付常量展开则进行常量折叠优化。
对代码进行混淆时,一般都会涉及到一个不精确语义问题。以push imm32为例,Wszzy的混淆器对它混淆时,引入了sub指令,也就是说混淆前后代码的语义并不是完全等价的,因为混淆后的代码会影响标志位。我在(一)中介绍常量展开时,为了偷懒一笔带过了这个问题(现在我仍然打算一笔带过)……该问题的解决办法是使用活跃分析,分析出每条指令上寄存器和标志位的活跃状态,死状态的寄存器和标志位可以随意使用。当然,混淆前后语义不完全相等的混淆过程也可以是合法的,依据Collberg对混淆转换的定义,只要确保混淆后程序的可被用户观测到的行为相同即可。
void CodeObfs::CleanOPCode(void) { PBYTE pBuf = (PBYTE)malloc(0x10000); PBYTE p = pBuf; BYTE b1[] = {0xE9, 0x01, 0, 0, 0}; BYTE b2[] = {0x90, 0x90, 0x90, 0x90, 0x90, 0x90}; GetData(0x423000, pBuf, 0xF00); for (int i = 0; i < 0xF00 - 5; i++) { if (!memcmp(pBuf, b1, sizeof(b1))) { memcpy(pBuf, b2, sizeof(b2)); } pBuf++; } SetData(0x423000, p, 0xF00); free(p); return; }
1)CALL IMM32
call func label: push label add [esp],0 //重定位 jmp func call func jmp label
2)PUSH IMM32
//使用该模式时,需先解常量展开 push imm32 lea esp,[esp-4] | sub esp,4 mov [esp],reg32 mov reg32,imm32 xchg [esp],reg32 push imm32
3)PUSH REG32
push reg32 xchg reg32,Xreg32 sub esp,4 | lea esp,[esp-4] mov [esp],Xreg32 mov Xreg32,reg32 mov reg32,[esp] push reg32
4)MOV REG32,IMM32
mov reg32,imm32 push imm32 pop reg32 mov reg32,imm32
5)SUB REG32,IMM32
//需先解除MOV REG32,IMM32的混淆及常量折叠 sub reg32,imm32 push Xreg32 mov Xreg32,imm32 push Xreg32 cmp reg32,[esp] pushf //保存sub运算结果标志位 not [esp+4] inc [esp+4] //neg [esp+4] add reg32,[esp+4] //将减法转为除法 popf //取出运算标志位 lea esp,[esp+4] xchg Xreg32,[esp] add esp,4 sub reg32,imm32
void CodeObfs::CleanPattern(std::vector<cs_insn*> &vIns) { char szAsm[64]; BYTE bCode[15]; cs_insn* pIns; for (unsigned int i = 0; i < vIns.size(); i++) { if (vIns[i] == NULL) continue; //CALL IMM32 if (!strcmp(vIns[i]->mnemonic, "push") && vIns[i]->detail->x86.operands[0].type == X86_OP_IMM && i - vIns.size() >= 3) { if (vIns[i + 1] != NULL && vIns[i + 1] != NULL && !strcmp(vIns[i + 1]->mnemonic, "add") && !strcmp(vIns[i + 2]->mnemonic, "jmp")) { cs_insn* pI1; /* push retaddr add [esp],0 jmp calladdr => call calladdr jmp retaddr */ wsprintfA(szAsm, "call 0x%X", vIns[i + 2]->detail->x86.operands[0].imm); Asm(DWORD(vIns[i]->address), szAsm, bCode); cs_disasm(_handle, bCode, 15, vIns[i]->address, 1, &pIns); pI1 = vIns[i]; vIns[i] = pIns; wsprintfA(szAsm, "jmp 0x%X", pI1->detail->x86.operands[0].imm); Asm(DWORD(vIns[i + 1]->address), szAsm, bCode); cs_disasm(_handle, bCode, 15, vIns[i + 1]->address, 1, &pIns); cs_free(vIns[i + 1], 1); cs_free(vIns[i + 2], 1); vIns[i + 2] = NULL; vIns[i + 1] = pIns; cs_free(pI1, 1); } } ...................... //省略 }
void CodeObfs::CleanImm(std::vector<cs_insn*> &vIns) { if (vIns.empty()) return; std::vector<ContextInfo> v; ContextInfo ct; for (int i = X86_REG_INVALID; i <= X86_REG_ENDING; i++) ct[i] = -1; v.push_back(ct); for (unsigned int i = 0; i < vIns.size() - 1; i++) { for (int x = 0; x < vIns[i]->detail->regs_write_count; x++) ct[vIns[i]->detail->regs_write[x]] = i; for (int x = 0; x < vIns[i]->detail->x86.op_count; x++) { if (vIns[i]->detail->x86.operands[x].type == X86_OP_REG && vIns[i]->detail->x86.operands[x].access & CS_AC_WRITE) ct[vIns[i]->detail->x86.operands[x].reg] = i; } v.push_back(ct); } //我偷懒了,忽略了一些问题 /* 这是我偷懒忽略掉的情况 mov ax,2010 mov dl,al */ std::vector<int> vReg; for (unsigned int i = 0; i < v.size(); i++) { if (!(vIns[i]->detail->x86.op_count != 0 && vIns[i]->detail->x86.operands[0].type == X86_OP_REG && vIns[i]->detail->x86.operands[0].access & CS_AC_WRITE)) continue; if (vIns[i]->detail->x86.op_count != 1 && vIns[i]->detail->x86.operands[1].type != X86_OP_IMM) continue; int k = v[i][vIns[i]->detail->x86.operands[0].reg]; if (k == -1 || vIns[k] == NULL) continue; if (strcmp(vIns[k]->mnemonic, "mov") || vIns[k]->detail->x86.operands[1].type != CS_OP_IMM) continue; DWORD dwImm1 = DWORD(vIns[k]->detail->x86.operands[1].imm); DWORD dwImm2 = DWORD(vIns[i]->detail->x86.operands[1].imm); if (!strcmp(vIns[i]->mnemonic, "ror")) { __asm { push ecx mov ecx,dwImm2 ror dwImm1,cl pop ecx } } else if (!strcmp(vIns[i]->mnemonic, "rol")) { __asm { push ecx mov ecx,dwImm2 rol dwImm1,cl pop ecx } } else if (!strcmp(vIns[i]->mnemonic, "add")) { __asm { push edx mov edx,dwImm2 add dwImm1,edx pop edx } } else if (!strcmp(vIns[i]->mnemonic, "sub")) { __asm { push edx mov edx,dwImm2 sub dwImm1,edx pop edx } } else if (!strcmp(vIns[i]->mnemonic, "xor")) { __asm { push edx mov edx,dwImm2 xor dwImm1,edx pop edx } } else if (!strcmp(vIns[i]->mnemonic, "not")) { __asm { push edx mov edx,dwImm2 not dwImm1 pop edx } } else if (!strcmp(vIns[i]->mnemonic, "inc")) { __asm { push edx mov edx,dwImm2 inc dwImm1 pop edx } } else if (!strcmp(vIns[i]->mnemonic, "dec")) { __asm { push edx mov edx,dwImm2 dec dwImm1 pop edx } } else if (!strcmp(vIns[i]->mnemonic, "and")) { __asm { push edx mov edx,dwImm2 and dwImm1,edx pop edx } } else if (!strcmp(vIns[i]->mnemonic, "or")) { __asm { push edx mov edx,dwImm2 or dwImm1,edx pop edx } } else continue; char szIns[255]; char szFmt[255] = {0}; strcat_s(szFmt, sizeof(szFmt), "mov "); strcat_s(szFmt, sizeof(szFmt), cs_reg_name(_handle, vIns[k]->detail->x86.operands[0].reg)); strcat_s(szFmt, sizeof(szFmt), ",0x%X"); wsprintfA(szIns, szFmt, dwImm1); BYTE bCode[15]; Asm(DWORD(vIns[k]->address), szIns, bCode); vIns[i] = NULL; cs_insn* insn; cs_disasm(_handle, bCode, 15, vIns[k]->address, 1, &insn); cs_free(vIns[k], 1); vIns[k] = insn; } return; };
CleanOPCode(); //去除花指令 std::vector<DWORD> vAccess; //未访问的代码块的起始地址 std::vector<cs_insn*> vIns; //块代码 DWORD pIns = 0x423002; BYTE bCode[15] = {0}; cs_insn* pDasm; while (true) { GetData(pIns, bCode, 15); cs_disasm(_handle, bCode, 15, pIns, 1, &pDasm); pIns += pDasm->size; vIns.push_back(pDasm); if (IsJxInsn(pDasm)) { unsigned int k; do { k = vIns.size(); CleanNop(vIns); CleanImm(vIns); CleanNop(vIns); CleanPattern(vIns); } while (k != vIns.size()); break; } } for (unsigned int i = 0; i < vIns.size(); i++) printf("%s %s \r\n", vIns[i]->mnemonic, vIns[i]->op_str); system("pause"); return;
对比给出的原版CrackMe,去混淆结果较为可观。
发生了灵异事件,附件一直上传不成功,我发到百度网盘了。
链接: https://pan.baidu.com/s/1PHoMlbP47mg6DjY9T6rzhA 提取码: hhqu