跳转至

花指令

原理

花指令是企图隐藏掉不想被逆向工程的代码块 (或其它功能) 的一种方法, 在真实代码中插入一些垃圾代码的同时还保证原有程序的正确执行, 而程序无法很好地反编译, 难以理解程序内容, 达到混淆视听的效果.

花指令通常用于加大静态分析的难度。

编写

最简单的花指令使用了内联汇编的方式进行,下面以 VC 添加花指令的方式举例,gnu 的编译器也可以采用类似的方式添加花指令,但是使用 AT&T 汇编:

// 正常的函数代码
int add(int a, int b){
  int c = 0;
  c = a + b;
  return c;
}
// 添加花指令的函数代码
int add_with_junk(int a, int b){
    int c = 0;
    __asm{
        jz label;
        jnz label;
        _emit 0xe8;    call 指令后面加4bytes的地址偏移因此导致反汇编器不能正常识别
label:
    }
    c = a + b;
    return c;
}

使用 ida 的反编译时,添加了花指令的函数不能正常识别,结果如下:

伪代码:

// 添加了花指令
.text:00401070 loc_401070:                             ; CODE XREF: sub_401005↑j
.text:00401070                 push    ebp
.text:00401071                 mov     ebp, esp
.text:00401073                 sub     esp, 44h
.text:00401076                 push    ebx
.text:00401077                 push    esi
.text:00401078                 push    edi
.text:00401079                 lea     edi, [ebp-44h]
.text:0040107C                 mov     ecx, 11h
.text:00401081                 mov     eax, 0CCCCCCCCh
.text:00401086                 rep stosd
.text:00401088                 mov     dword ptr [ebp-4], 0
.text:0040108F                 jz      short near ptr loc_401093+1
.text:00401091                 jnz     short near ptr loc_401093+1
.text:00401093
.text:00401093 loc_401093:                             ; CODE XREF: .text:0040108F↑j
.text:00401093                                         ; .text:00401091↑j
.text:00401093                 call    near ptr 3485623h
.text:00401098                 inc     ebp
.text:00401099                 or      al, 89h
.text:0040109B                 inc     ebp
.text:0040109C                 cld
.text:0040109D                 mov     eax, [ebp-4]
.text:004010A0                 pop     edi
.text:004010A1                 pop     esi
.text:004010A2                 pop     ebx
.text:004010A3                 add     esp, 44h
.text:004010A6                 cmp     ebp, esp
.text:004010A8                 call    __chkesp
.text:004010AD                 mov     esp, ebp
.text:004010AF                 pop     ebp
.text:004010B0                 retn

在上面这个例子中,把混淆视听的花指令 patch 成 nop 即可修复,然后正常分析。

值得注意的是,ida 对于栈的判定比较严格,因此 push,ret 一类的花指令会干扰反汇编器的正常运行,下面给出一个具体的例子,读者可以自己编译复现:

#include <stdio.h>
// 使用 gcc/g++ 进行编译
int main(){
    __asm__(".byte 0x55;");          // push rbp   保存栈 
    __asm__(".byte 0xe8,0,0,0,0;");  // call $5;    
    __asm__(".byte 0x5d;");          // pop rbp -> 获取rip的值 
    __asm__(".byte 0x48,0x83,0xc5,0x08;"); // add rbp, 8
    __asm__(".byte 0x55;");          // push rbp -> 相当于将call的返回值修改到下面去
    __asm__("ret;");
    __asm__(".byte 0xe8;");          // 这是混淆指令不执行
    __asm__(".byte 0x5d;");          // pop rbp 还原栈     
    printf("whoami \n");
    return 0;
} 

例题

这里以看雪.TSRC 2017CTF秋季赛第二题作为讲解. 题目下载链接: ctf2017_Fpc.exe

程序写了几个函数混淆视听, 将关键的验证逻辑加花指令防止了 IDA 的静态分析. 我们用 IDA 打开 Fpc 这道题, 程序会先打印一些提示信息, 然后获取用户的输入.

main.png

这里使用了不安全的scanf函数, 用户输入的缓冲区只有0xCh长, 我们双击v1进入栈帧视图

stack.png

因此我们可以通过溢出数据, 覆盖掉返回地址, 从而转移到任意地址继续执行.

这里我还需要解释一下, 就是scanf之前写的几个混淆视听的函数, 是一些简单的方程式但实际上是无解的. 程序将真正的验证逻辑加花混淆, 导致 IDA 无法很好的进行反编译. 所以我们这道题的思路就是, 通过溢出转到真正的验证代码处继续执行.

我们在分析时可以在代码不远处发现以下数据块.

block.png

因为 IDA 没能很好的识别数据, 因此我们可以将光标移到数据块的起始位置, 然后按下C键 (code) 将这块数据反汇编成代码

real_code.png

值得注意的是, 这段代码的位置是0x00413131, 0x41'A'的 ascii 码,而0x31'1'的 ascii 码. 由于看雪比赛的限制, 用户输入只能是字母和数字, 所以我们也完全可以利用溢出漏洞执行这段代码

用 OD 打开, 然后Ctrl+G到达0x413131处设下断点, 运行后输入12345612345611A回车, 程序成功地到达0x00413131处. 然后右键分析->从模块中删除分析识别出正确代码

entry.png

断在0x413131处后, 点击菜单栏的"查看", 选择"RUN跟踪", 然后再点击"调试", 选择"跟踪步入", 程序会记录这段花指令执行的过程, 如下图所示:

trace.png

这段花指令本来很长, 但是使用 OD 的跟踪功能后, 花指令的执行流程就非常清楚. 整个过程中进行了大量的跳转, 我们只要取其中的有效指令拿出来分析即可.

需要注意的是, 在有效指令中, 我们依旧要满足一些条件跳转, 这样程序才能在正确的逻辑上一直执行下去.

比如0x413420处的jnz ctf2017_.00413B03. 我们就要重新来过, 并在0x413420设下断点

jnz.png

通过修改标志寄存器来满足跳转. 继续跟踪步入 (之后还有0041362E jnz ctf2017_.00413B03需要满足). 保证逻辑正确后, 将有效指令取出继续分析就好了

register.png