1107 字
6 分钟
花指令
花指令原理
反编译器的线性反编译(理解花指令的重点)(https://bbs.kanxue.com/thread-279604.htm)
反编译器的工作原理是,从exe的入口AddressOfEntryPoint处开始,依序扫描字节码,并转换为汇编,比如第一个16进制字节码是0xE8,一般0xE8代表汇编里的CALL指令,且后面跟着的4个字节数据跟地址有关,那么反编译器就读取这一共5个字节,反编译为`CALL 0x地址` 。
对应的,有些字节码只需要一个字节就可以反编译为一条指令,例如0x55对应的是`push ebp`,这条语句每个函数开始都会有。同样,有些字节码又需要两个、三个、四个字节来反编译为一条指令。
也就是说,如果中间只要一个地方反编译出错,例如两条汇编指令中间突然多了一个字节0xE8,那反编译器就会将其跟着的4个字节处理为CALL指令地址相关数据给反编译成一条`CALL 0x地址`指令。但实际上0xE8后面的四个字节是单独的字节码指令。这大概就是**线性反编译**。
线性扫描和递归下降
线性扫描:线性扫描的特点:从入口开始,一次解析每一条指令,遇到分支指令不会递归进入分支。
递归下降:当使用线性扫描时,比如遇到call或者jmp的时候,不会跳转到对应地址进行反汇编,而是反汇编call指令的下一条指令,这就会导致出现很多问题。递归下降分析当遇到分支指令时,会递归进入分支进行反汇编。基础花指令类型
1.jmp(并不能骗过ida)
__asm{ jmp LABEL1; _emit 0xe8; // 无用操作数LABEL1:}2.jnz,jz一类的跳转(可以看作是互补的跳转)
__asm{ jz/jo/jc label1; jnz/jno/jnc label1; _emit 0x55; // 无用操作数 _emit 0xe8;label1:}3.永真永假跳转
__asm{ clc; // 清除进位标志 CF,设置 CF 为 0 jnc label1; // 如果 CF 为 1,跳转到 label1 _emit junkcode; // 垃圾数据,不会执行label1:}
/*__asm{ xor eax, eax; jz label1; _emit 0xe8;label1:}*/4.call、ret指令
__asm { call LABEL9; // 调用 LABEL9,实际上是将 LABEL9 的地址压入堆栈,并跳转到 LABEL9 执行 _emit 0x83; // 插入字节 0x83,作为垃圾数据,不会被执行到LABEL9: add dword ptr ss : [esp], 8; // 修改堆栈顶的返回地址,加上 8(根据操作数的个数来定) ret; // 从 LABEL9 返回,返回地址被修改过 __emit 0xF3; // 插入字节 0xF3,作为垃圾数据,不会被执行到}5.扩充
call指令会压入地址后并实现jmp跳转,ret指令会弹出栈顶地址并实现jmp到该地址,ret指令是用来维护堆栈平衡的。由于call指令会压入一个地址,可以用add esp, 4来实现堆栈平衡(也可以用pop来实现堆栈平衡),这样就相当是一个jmp指令,这种实现可以骗过ida。
__asm{ call label1; ...label1: add esp, 4; ...} // jmp还可以通过call指令还可以用来查看eip
__asm{ _emit 0xE8; _emit 0x00; _emit 0x00; _emit 0x00; _emit 0x00;}test.c
#include <stdio.h>int main(){
__asm { call LABEL9; _emit 0x83; LABEL9: add dword ptr ss : [esp] , 8; ret; _emit 0x55; } printf("hello world1\n");
__asm { jo LABEL10; jno LABEL10; _emit 0xE8; LABEL10: } printf("hello world2\n"); __asm { xor eax, eax; jz LABEL11; _emit 0xE8; LABEL11: } /* __asm { clc; jnc label11; _emit 0xF3; label11 : } */
printf("hello world3\n");
__asm { _emit 0xE8; _emit 0x01; _emit 0x00; _emit 0x00; _emit 0x00; _emit 0x55; pop eax; }
printf("hello world4\n");}将基本花指令组合形成比较的复杂花指令
#include <stdio.h>#include <stdlib.h>#include <string.h>void encr(char* input){ __asm { jo label1; jno label1; _emit 0xE8; } /********junk_code********/ /*************************/ __asm { label2: call label3; _emit 0x55; _emit 0xf3; }
/********junk_code********/ for (int i = 0; i < strlen(input); i++) { input[i] += 0x33; input[i] ^= 0x33; } /*************************/
__asm { label4: }
for (int i = 0; i < strlen(input); i++) { input[i] -= 0x10; } __asm { xor eax, eax; jz label5; } /********junk_code********/ /*************************/ __asm { label1: xor eax, eax; jz label2; _emit 0x55;
}
/********junk_code********/ for (int i = 0; i < strlen(input); i++) { input[i] += 0x22; input[i] ^= 0x22; } /*************************/ __asm { label3: add esp, 4; xor eax, eax; jz label4; } /********junk_code********/ /*************************/ __asm { label5: } /********junk_code********/ /*************************/ return;}
int main(int argc, const char** argv){
char cipher[33] = { 86, 92, 81, 87, 107, 33, 33, 34, 35, 29, 34, 35, 36, 37, 29, 33, 34, 35, 36, 37, 34, 35, 37, 34, 35, 34, 37, 34, 35, 33, 34, 109, 0}; encr(argv[1]); if (strcmp(cipher, argv[1]) == 0) { printf("right"); } else { printf("wrong"); }}部分信息可能已经过时