反汇编算法介绍和应用——递归下降算法分析
? ? ? ??上一篇博文我介紹了Windbg使用的線性掃描(linear sweep)反匯編算法。本文我將介紹IDA使用的遞歸下降(recursive descent)反匯編算法。(轉載請指明來源于breaksoftware的csdn博客)
? ? ? ? 遞歸(recursive)可能大家都很清楚,說白了就是自己調用自己。那么什么是recursive descent呢?似乎很難理解。recursive還是有循環和回歸的意思,那么recursive descent就可以理解為“不停減少的循環”和“不停減少的回歸”。或許這么說還是不是很好理解,那我們來研究下這個算法的思路的來源,這樣可以容易理解這個算法的精髓。
? ? ? ? 回顧《反匯編算法介紹和應用——線性掃描算法分析》,我們知道線性掃描一個很大的缺點是:因為其不知道程序執行流而導致將數據識別為代碼。我們可能會罵這個算法不智能,那么如何才能智能起來呢?想想我們的二進制文件在系統中正常運行時是不會出錯的,因為CPU總是可以找到真正的指令起始地址,那么我們反匯編算法只要能模擬CPU執行指令就可以得到正確的反匯編結果了。OK!沒錯,遞歸下降算法一個主要的思路就是源于這樣的思考結果。但是我們反匯編是靜態的,而CPU執行指令是動態的,靜態分析無法得知動態執行的結果,這個嚴重的缺陷會導致我們想完全模擬CPU執行去反匯編的思路變得不現實。但是不要退卻,沒有完美的方案,只有最可以接受的方案。那我們開始研究下怎么修改我們的思路,讓我們的算法變得“最令人可以接受”。
? ? ? ? 研究修改的方法之前我們要了解CPU執行指令“順序”的一些基礎知識,知己知彼百戰百勝。
? ? ? ? A 順序流指令
? ? ? ? 熟悉匯編的朋友,應該對add、sub、mov、push和pop等指令很熟悉,這類指令執行后,會執行與其地址緊接著的下一條指令。CPU識別這類指令如線性掃描一般簡單,那么我們的遞歸下降算法也就如線性掃描方式去識別這樣的指令就行了。
? ? ? ? B 無條件跳轉指令
? ? ? ? jmp是無條件跳轉指令。CPU執行這條指令后會跳轉到jmp指令參數所指向的地址。這個操作對CPU來說,和順序流指令沒什么區別,只是將EIP改成要跳轉的地址。但是這個動態的過程卻害慘了靜態分析的線性掃描算法,那我們遞歸下降算法要吸取教訓:我們從jmp到的地址開始分析下一條指令。貌似這個想法天衣無縫,但是現實往往是殘酷的。請問你一定能得到jmp的地址么?對于jmp 00401010這類的指令我們當然可以得到下條指令的地址,即0x00401010。那么jmp eax呢?eax是多少?CPU知道,我們不知道。這個缺陷我們Mark下。
? ? ? ? C 有條件跳轉指令
? ? ? ? ja和je等是有條件跳轉指令,即符合某些要求后才執行跳轉,不符合要求則執行其緊接著的那條指令。這些指令的執行順序如同A、B兩種指令的靈魂附體。即條件為真,則走A流程分支;條件為假,則走B流程分支。這么一拆解,我想遞歸下降算法怎么去分析有條件跳轉指令就清楚了。
? ? ? ? 但是有個問題需要說下,CPU執行這類指令時是知道要走A流程分支還是要走B流程分支的,它不會同時一起走這兩條流程。而且可能整個程序運行完了,這個指令的一個分支還沒走過(比如if(1){}else{},else永遠進不去的)。而我們的遞歸下降算法是要分析出所有分支的!
? ? ? ? 那怎么辦呢?那我們就將A和B分支的地址中的某一個優先分析,另一個延后分析。可是手心手背都是肉,我們如何取舍?這個時候,我們就要學習國羽和國乒的做法——不惜“讓球”,也要選擇出最有利于目前流程順利進行的方法。那么A、B這兩個孩子誰有缺陷呢?如上所述,A流程分支沒缺陷,而B流程分支存在一定的隱患。那我們就將要執行跳轉的B流程分支保存到一個延后分析的列表中。
? ? ? ? 最后說一句:C有B的靈魂,C有B的缺陷。
? ? ? ? D 函數調用指令
? ? ? ? call指令是函數調用指令,但是目前,我們可以將其看成B流程。或許有人會說call指令怎么會和jmp混為一談呢?我們看一個call例子
0x0040177f call 0040209C
0x00401785 mov ecx,eax
? ? ? ?其執行等效于
push 00401785 // call指令結束的位置,注意該位置不一定是call完后下條指令開始的位置
jmp 0040177F // 跳轉到函數地址
? ? ? ? 可能有人疑惑為什么push進入堆棧的00401785不一定是call完后下條指令的位置?比如 我們在0040209C的代碼如下
pop eax
jmp 00401788
? ? ? ? 那么,我們程序執行完將會進入00401788,從而過掉了00401785開始的指令。
? ? ? ? 是不是可以將call簡單的看成jmp呢?是吧。
? ? ? ? 最后說一句:D也有B的缺陷。
? ? ? ? E 函數返回指令
? ? ? ? ret和retn等是函數返回指令,同call一樣,我們可以將其看成是B流程分支。為什么這么說呢?我們接著以D中的例子為例。假如0x0040209C的代碼最后是ret,則該ret等效于
pop EIP
? ? ? ? ?因為EIP是下條指令的起始地址,則這步操作可以看成
jmp EIP // 當然不能這么寫,這兒只是為了說明這是個跳轉的過程
? ? ? ? ?這是動態執行的流程,但是我們是靜態分析,怎么知道EIP是啥呢?是的,一般情況下,我們無法知道。那么這個時候,該次遞歸流程就走完了,我們將會去C流程中產生的延時反匯編隊列中取出地址來開始再次的遞歸操作……這就是遞歸下降算法名稱的由來。
? ? ? ? 是否還記得我們在B中說的那個場景?如果我們jmp eax了而不知eax是啥時,或者call、ret不知跳轉地址時,本次遞歸下降都會結束,并在延時反匯編列表中尋找新的起始反匯編地址。
? ? ? ? 貌似我們的遞歸反匯編思路都講完了。但是還存在很大的缺陷!為什么?還記得我在《反匯編算法介紹和應用——線性掃描算法分析》所說的遞歸下降算法缺陷么?它可能無法覆蓋全部代碼。我們舉個例子
0x0040177f call 0040209C
0x00401785 mov ecx,eax
.
.
.
0x0040209C ret
? ? ? ? 如果依我們之上的流程,那么0x00401785將可能分析不到,因為我們將call看成了jmp,我們該分支分析將在0x0040209C處結束,而0x00401785沒出現在延遲反匯編隊列中。想想,這是多么可怕!于是比較嚴謹的將call看成jmp將要做必要修改。
? ? ? ? D 函數調用指令(修正后)
? ? ? ? 我們將call看成C流程,即有條件跳轉。那么如上那段匯編,我們將產生兩個分支:一個是00401785,一個是0040209C。雖然我們將00401785看成一個分支是非常不嚴謹的(因為下條指令完全由0040209C里的邏輯決定的),但是為了能盡量多的反匯編出代碼,我們還是要做這個妥協!因為這個妥協,也將導致遞歸下降算法產生一個致命的缺陷——將call指令后數據當成指令去反匯編。
? ? ? ? 這兒有個小細節需要注意,對于Call指令,我們會將跳轉分支地址優先分析,緊跟著call指令的分支延遲分析。因為存在一種可能,即跳轉分支中或許可以確定返回的地址。如果返回地址和緊跟著call指令的分支地址相同,則照舊進行;如果不相同,則以返回地址為準。舉個例子
void TestFun(void* lpfun)
{__asm{mov esp,ebppop ebppop eaxret}
}int _tmain(int argc, _TCHAR* argv[])
{__asm{push xxxcall TestFun_emit 0xE8
xxx:}return 0;
}
? ? ? ? 在TestFun中,我最后拋出返回地址到eax中。這樣堆棧頂部就是lpfun,ret后,EIP變成xxx處地址,并將執行到xxx處,而不是緊跟在call后面的0xE8。我們遞歸下降算法,優先分析TestFun地址的指令,然后可以通過一些判斷,判斷出最后返回的地址是我們傳入的數據,那么我們傳入的數據就是正確的下條指令地址,而0xE8處只是個數據。IDA的反匯編結果是
? ? ? ? 想想,如果我們將緊跟call指令的分支優先分析,將會出現將0xE8當成call來解析的情況。那么或許之后得靠跳轉分支的分析結果再來糾正,這樣還不如優先反匯編跳轉分支。
? ? ? ? 說了這么多,再說說上面所說的如何利用call指令分析的缺陷。通過以上例子,我們發現,如果讓遞歸下降算法不知道其call后跳轉分支的返回地址,然后在緊跟call指令的位置插入一些廢信息,那就造成IDA分析失敗了。看例子
void Fun( void* p )
{__asm{add p,3push ppop eaxmov esp,ebppop ebppush eaxret 4}
}int _tmain(int argc, _TCHAR* argv[])
{int i = 0;__asm {push yyycall Fun_emit 0xE8yyy:_emit 0xE8mov eax,ebp}return 0;
}
? ? ? ? 我在Fun函數一開始處將地址指向了return 0,然后將這個指針通過push和pop放入eax,讓push eax到棧頂,從而在ret時讓程序從return 0;開始執行。那么IDA反匯編結果呢?
? ? ? ? 看!已經錯了,當然windbg也是分析錯的。
? ? ? ? 到此,關于反匯編算法的兩篇博文寫完了。僅供大家參考。
附上測試的工程
?
總結
以上是生活随笔為你收集整理的反汇编算法介绍和应用——递归下降算法分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 反汇编算法介绍和应用——线性扫描算法分析
- 下一篇: PE文件和COFF文件格式分析——签名、