IDA反編譯器,正如IDA官網所說,雖然還原出來的代碼不能直接使用,但是其參考作用不容否認。這裡記錄一些反編譯時會用到的功能。
1.生成僞代碼:
view-Open subviews-Generate pesudocode (快捷鍵F5)

->
(F5前後對比圖)
2.僞代碼切換到對應的反彙編代碼:
前面說了,IDA生成的僞代碼不可全信。當懷疑僞代碼的正确性時,需要傳回到反彙編代碼對比。但是有别于IDA view和Hex view視窗,兩者是同步定位的關系,Pseudocode和IDA view視窗之間不存在這樣的關系。可以通過Jump-Jump to pseudocode (快捷鍵)跳回到對應的源碼
3.修改變量(函數)名:
除非有pdb檔案,否則反編譯器生成的變量名/函數名簡直慘不忍睹,這時候需要修改變量名。在變量上右鍵-Rename lvar-在Please enter a string對話框中輸入變量名 (快捷鍵N),修改函數名同理。修改後,僞代碼中所有使用該變量的地方都跟着被修正了。
->
(修改函數名/變量名前後)
4.修改變量類型:
如下面源碼中,main函數中定義了字元串數組,并進行了初始化。
int main(int /*argc*/, char * /*argv*/[])
{
int ret = 0;
unsigned char result[32] = { 0 };
...
}
根據反彙編經驗數組的初始化在反彙編代碼中會以為數組元素指派的形式實作。但是反編譯器不能區分是普通變量初始化還是數組元素指派,是以經常出錯。下面是反編譯器提供的僞代碼:
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned int j; // [esp+D0h] [ebp-4Ch]
unsigned int i; // [esp+DCh] [ebp-40h]
char Buf1; // [esp+E8h] [ebp-34h]
int v7; // [esp+E9h] [ebp-33h]
int v8; // [esp+EDh] [ebp-2Fh]
int v9; // [esp+F1h] [ebp-2Bh]
int v10; // [esp+F5h] [ebp-27h]
int v11; // [esp+F9h] [ebp-23h]
int v12; // [esp+FDh] [ebp-1Fh]
int v13; // [esp+101h] [ebp-1Bh]
__int16 v14; // [esp+105h] [ebp-17h]
char v15; // [esp+107h] [ebp-15h]
int ret; // [esp+110h] [ebp-Ch]
ret = 0;
Buf1 = 0;
v7 = 0;
v8 = 0;
v9 = 0;
v10 = 0;
v11 = 0;
v12 = 0;
v13 = 0;
v14 = 0;
v15 = 0;
這樣的僞代碼與真實代碼相去甚遠,是以,需要将字元型變量v15修改成數組。在變量上右鍵-set lvar type-please enter a string中輸入變量的新類型,下面是修正後的代碼:
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned int j; // [esp+D0h] [ebp-4Ch]
unsigned int i; // [esp+DCh] [ebp-40h]
char result[32]; // [esp+E8h] [ebp-34h]
int ret; // [esp+110h] [ebp-Ch]
ret = 0;
result[0] = 0;
*(_DWORD *)&result[1] = 0;
*(_DWORD *)&result[5] = 0;
*(_DWORD *)&result[9] = 0;
*(_DWORD *)&result[13] = 0;
*(_DWORD *)&result[17] = 0;
*(_DWORD *)&result[21] = 0;
*(_DWORD *)&result[25] = 0;
*(_WORD *)&result[29] = 0;
result[31] = 0;
5.轉換為指針/結構體指針:
在x86機器上,整形變量和(結構體)指針變量占用的位址空間相同,是以,反編譯器經常會混淆兩者。如下面源碼中,函數原型為:
void md5_final(md5_ctx *ctx, const unsigned char *buf, size_t size, unsigned char *result);
而反編譯器生成如此不倫不類的函數原型:
void *__cdecl md5_final(int a1, char* a2, size_t a3, char* *a4)
{
unsigned int v4; // STFC_4
int v5; // STF0_4
signed __int64 v6; // rax
unsigned int v8; // [esp+F0h] [ebp-54h]
int Dst[14]; // [esp+FCh] [ebp-48h]
int v10; // [esp+134h] [ebp-10h]
int v11; // [esp+138h] [ebp-Ch]
對于基本類型指針,做法同4.修改變量類型,不再贅述;
對于非基本類型指針,為了将int變量修正為結構體指針變量,可以在變量定義處右鍵-convert to struct*-Select a structure,(如果是自定義的結構體,需要在IDA Structures視窗中建立該結構體,否則該結構體不會出現在"Select a structure"清單中)
最終生成如下僞代碼:
void *__cdecl md5_final(md5_ctx *a1, char *a2, size_t a3, char *a4)
{
unsigned int v4; // STFC_4
int v5; // STF0_4
__int64 v6; // rax
unsigned int v8; // [esp+F0h] [ebp-54h]
int Dst[14]; // [esp+FCh] [ebp-48h]
int v10; // [esp+134h] [ebp-10h]
int v11; // [esp+138h] [ebp-Ch]
如果想撤回修改,将結構體指針變回整形變量,隻要在變量上右鍵-Reset pointer type即可。
6.變量映射:
IDA生成的僞代碼中變量滿天飛,特别是參與大量計算的代碼片。很多變量是中間變量,由反編譯器生成的,并不存在于真正的源碼中。這些中間變量的存在,多少會影響我們分析源碼,是以我們要把這些中間變量映射到其他變量上。
如下面的源碼:
static void md5_final(md5_ctx *ctx, const unsigned char *buf, size_t size, unsigned char *result)
{
...
uint32_t index = ((uint32_t)ctx->length_ & 63) >> 2;
uint32_t shift = ((uint32_t)ctx->length_ & 3) * 8;
//添加0x80進去,并且把餘下的空間補充0
message[index] &= ~(0xFFFFFFFF << shift);
message[index++] ^= 0x80 << shift;
//如果這個block還無法處理,其後面的長度無法容納長度64bit,那麼先處理這個block
if (index > 14)
{
while (index < 16)
{
message[index++] = 0;
}
zen_md5_process_block(ctx->hash_, message);
index = 0;
}
然而,經過IDA的反編譯,生成若幹中間變量:
void *__cdecl md5_final(md5_ctx *a1, char *a2, size_t a3, char *a4)
{
...
v4 = (unsigned __int64)(ctx->length & 0x3F) >> 2;
v5 = 8 * (ctx->length & 3);
message[v4] &= ~(-1 << v5);
message[v4] ^= 128 << v5;
v8 = v4 + 1; //<----------------v8是中間變量
if ( v8 > 0xE )
{
while ( v8 < 0x10 )
message[v8++] = 0;
sub_401540(ctx->hash_, message);
v8 = 0;
}
修正這個差異需要用到變量映射的功能。在變量v8處右鍵-Map to another variable-Map v8 to...清單框中選擇一個要被映射的變量。
最後上面這段代碼片變為:
7.進階技能:
有時,反編譯器會過度優化,并完全消除對易失性變量的引用。比如在.rodata節内定義有變量i,由于反編譯器覺得沒有人會修改變量i,而把部分分支優化沒了,如:
.text:00401799 cmp ds:dword_40E000, 0
.text:004017A0 jz short loc_4017E6
指令引用了變量dword_40E000,它定義在:
.r_only:0040E000 ; Segment type: Pure data
.r_only:0040E000 ; Segment permissions: Read
.r_only:0040E000 _r_only segment para public 'DATA' use32
.r_only:0040E000 assume cs:_r_only
.r_only:0040E000 ;org 40E000h
.r_only:0040E000 dword_40E000 dd 0 ; DATA XREF: sub_401770+29
反編譯器認為上面的彙編代碼等效于:
if (dword_40E000) {
// ...
}
由于dword_40E000看似為0,是以,反編譯器會優化掉這個分支。對此,我們需要修改.r_only段屬性:打開Program Segmentation視窗(view-Open subviews-Segment)-右鍵.rdata段-Edit segment-Segment permissions-勾選Write
再次F5,被IDA吞掉的分支就會出現。
方法2,對于全局變量,在IDA view視窗設定變量類型,為其添加volatile聲明;對于棧變量,輕按兩下變量,進入Stack視窗-右鍵-Set type-Please enter string
參考:
https://www.hex-rays.com/products/decompiler/manual/index.shtml