跳转至

花指令

原理

花指令是企圖隱藏掉不想被逆向工程的代碼塊(或其它功能)的一種方法, 在真實代碼中插入一些垃圾代碼的同時還保證原有程序的正確執行, 而程序無法很好地反編譯, 難以理解程序內容, 達到混淆視聽的效果.

花指令通常用於加大靜態分析的難度。

編寫

最簡單的花指令使用了內聯彙編的方式進行,下面以 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