最近看了不少關于花指令的博文,感覺大緻了解插入花指令的方法,此處加以總結記錄。
本文參考資料:
1).<Secrets of Reverse Engineering> Chap10_2 看雪論壇譯
2).<加密與解密 第三版>
3).<矛與盾的較量(1) -花指令> 作者 羅聰 http://www.luocong.com/articles/show_article.asp?Article_ID=14
4).還有一些無法記錄出處的博文
花指令屬于靜态反調試技術,隻是通過加入煙幕彈擾亂代碼可讀性進而磨光調試者的耐心,本身并不影響程式執行邏輯。下面以一個簡單的MessageBoxA程式為小白鼠,一步步與花指令相識相知~
#include <windows.h>
int main(int argc, char* argv[])
{
char* str1="message";
char* str2="caption";
HMODULE u32dll = LoadLibrary("user32.dll");
DWORD MsgBoxAddr = (DWORD)GetProcAddress(u32dll,"MessageBoxA");
__asm
{
push 0; //MB_OK
lea ebx,str2;
push [ebx];
lea ebx,str1;
push [ebx];
push NULL;
mov eax,MsgBoxAddr;
call eax;
}
return 0;
}
__asm{}塊中的指令将被加入花指令,影響調試器靜态的分析。柿子先撿軟的捏,先拿“linear sweep”式的反彙編器開刀,然後再考慮怎麼對付“Recursive traversal”式的反彙編器
1.首先祭出W32DASM分析, Linear Sweep反彙編算法。
W32DASM隻是用str2的棧位址:[ebp+FFFFFFF8]代替了lea ebx,str2。(ebp+FFFFFFF8=ebp-8,vc debug版的程式用[ebp-偏移N]的形式來擷取局部變量)。其他的輸出跟代碼一樣,簡直就是開源項目了,這當然不是想要的結果。來修改一下程式!
__asm
{
push 0; //MB_OK
lea ebx,str2;
push [ebx];
lea ebx,str1;
push [ebx];
push NULL;
mov eax,MsgBoxAddr;
/
jmp Lab1
_emit 0xB8 //mov的opcode
Lab1:
/
call eax;
}
分隔線之間的指令是新加的代碼,就是最簡單的花指令。jmp指令直接跳轉到call eax,看着好像沒什麼作用(極端份子會說影響執行效率),還是來看下W32DASM的輸出。
shit,沒被欺騙,看來更新版的W32DASM還是能識别這類花指令。換個調試器,就windbg了,也是Linear Sweep反彙編算法
00401037 6a00 push 0
00401039 8d9df8ffffff lea ebx,[ebp-8]
0040103f ff33 push dword ptr [ebx]
00401041 8d9dfcffffff lea ebx,[ebp-4]
00401047 ff33 push dword ptr [ebx]
00401049 6a00 push 0
0040104b 8b45f0 mov eax,dword ptr [ebp-10h]
0040104e eb01 jmp Msg!main+0x51 (00401051)
00401050 b8ffd033c0 mov eax,0C033D0FFh <=========================此處,跟W32DASM的輸出不同了
00401055 5f pop edi
00401056 5e pop esi
00401057 5b pop ebx
00401058 8be5 mov esp,ebp
0040105a 5d pop ebp
0040105b c3 ret
從windbg輸出看,00401050處被編譯成mov指令,而不是無效資料BYTE B8。同時,仔細看mov上一條指令,jmp 00401051,跳轉到mov指令中間,而不是一條指令的開始處,由此可見windbg被繞暈了。好吧,windbg主要用來動态調試,這樣的花指令意義也不大,改進改進先饒過W32DASM
__asm
{
push 0; //MB_OK
lea ebx,str2;
push [ebx];
lea ebx,str1;
push [ebx];
push NULL;
mov eax,MsgBoxAddr;
mov ecx,0;
//比較1==2,Lab2分支永遠不會達到
cmp ecx,1;
jne Lab1;
je Lab2;
Lab2:
_emit 0xB8
Lab1:
call eax;
}
這次,W32DASM真跪了:
順便看下Od的結果:
看來,在401056處和401058處的jnz/je指令沒有成功饒過Od,有點可惜。又要修改。
再繼續後面的嘗試前先打住一下,為什麼用cmp/jnz/je代替jmp指令能饒過W32DASM?
據說W32DASM是Linear Sweep反彙編算法,在這種算法下反彙編器隻是依次逐個地将整個子產品中的每一條指令都反彙編成彙編指令,如果代碼中的插入一個無效位元組Linear Sweep會将無效位元組和後續的位元組合在一起錯誤的解釋為錯誤的指令,于是産生了上梁不正下梁歪的連鎖反映。當然,我們的目的不是執行錯誤的流程,是以要用jmp跳過無效位元組,于是就有了jmp+thunkcode這種簡單的花指令組合方式。
可是,我的W32DASM目測是進擊版的,采用了Recursive traversal算法,這種算法預測每個分支的位址并繼續反彙編。對此用了所謂的“opaque predicate"方式欺騙反彙編器。這種方式就是在程式中安上一個僞分支。正如正常的分支語句那樣,“opaque predicate”的也分成兩個部分,一部分是跳轉到真正需要執行的代碼,另一部分則跳轉到能玩暈反彙編器的代碼,而能跳轉到這一部分的分支條件需要設成永遠為假。僞分支中的代碼類似欺騙Linear Sweep反彙編算法,在分支的入口處加如無效位元組,如0xB8/0xE9等。
就是這種“opaque predicate”方式成功的迷惑了W32DASM。
__asm
{
push 0; //MB_OK
lea ebx,str2;
push ebx;
lea ebx,str1;
push ebx;
push NULL;
mov eax,MsgBoxAddr;
xor ebx,0x5a;
cmp ebx,0x5a;
jz Mali;
jnz CallMsg;
Mali:
_emit 0xb8;
CallMsg:
call eax;
}