[系统安全] 六.逆向分析之条件语句和循环语句源码还原及流程控制
您可能之前看到過我寫的類似文章,為什么還要重復撰寫呢?只是想更好地幫助初學者了解病毒逆向分析和系統安全,更加成體系且不破壞之前的系列。因此,我重新開設了這個專欄,準備系統整理和深入學習系統安全、逆向分析和惡意代碼檢測,“系統安全”系列文章會更加聚焦,更加系統,更加深入,也是作者的慢慢成長史。換專業確實挺難的,逆向分析也是塊硬骨頭,但我也試試,看看自己未來四年究竟能將它學到什么程度,漫漫長征路,偏向虎山行。享受過程,一起加油~
系統安全系列作者將深入研究惡意樣本分析、逆向分析、攻防實戰和Windows漏洞利用等,通過在線筆記和實踐操作的形式分享與博友們學習,希望能與您一起進步。前文介紹了OllyDbg和Cheat Engine工具逆向分析用法,完成植物大戰僵尸的游戲輔助器,包括修改陽光值和自動拾取陽光兩個功能。這篇文章將帶領大家來學習科銳錢林松老師的視頻,詳細講解條件語句和循環語句源碼還原及流程控制逆向。希望對入門的同學有幫助。
話不多說,讓我們開始新的征程吧!您的點贊、評論、收藏將是對我最大的支持,感恩安全路上一路前行,如果有寫得不好的地方,可以聯系我修改?;A性文章,希望對您有所幫助,作者的目的是與安全人共同進步,加油~
文章目錄
- 一.C++逆向條件結構基礎入門
- 1.單分支結構分析
- 2.雙分支結構分析
- 二.C++逆向循環結構基礎入門
- 1.do-while結構分析
- 2.while結構分析
- 3.for結構分析
- 三.總結
作者的github資源:
- 系統安全:https://github.com/eastmountyxz/SystemSecurity-ReverseAnalysis
- 網絡安全:https://github.com/eastmountyxz/NetworkSecuritySelf-study
前文分析:
- [系統安全] 一.什么是逆向分析、逆向分析基礎及經典掃雷游戲逆向
- [系統安全] 二.如何學好逆向分析及呂布傳游戲逆向案例
- [系統安全] 三.IDA Pro反匯編工具初識及逆向工程解密實戰
- [系統安全] 四.OllyDbg動態分析工具基礎用法及Crakeme逆向破解
- [系統安全] 五.OllyDbg和Cheat Engine工具逆向分析植物大戰僵尸游戲
- [系統安全] 六.逆向分析之條件語句和循環語句源碼還原及流程控制
聲明:本人堅決反對利用教學方法進行犯罪的行為,一切犯罪行為必將受到嚴懲,綠色網絡需要我們共同維護,更推薦大家了解它們背后的原理,更好地進行防護。
一.C++逆向條件結構基礎入門
大家寫過相關的算法嗎?
加密代碼中會涉及循環和分支,你要識別算法,首先就是需要將它的算法處理流程識別出來。當我們還原出等價的高級代碼之后,就沒有逆向分析人員的事情了,因為接下來涉及到密碼學、數學相關人員的工作,逆向人員把加密的代碼還原出來后就應該扔給研究密碼學的數學家,他們負責玩數學對抗,而逆向關注的是編譯原理和代碼還原。同時,逆向還涵蓋了識別對象、識別算法、識別優化、識別虛函數對象的繼承關系等等,這里主要結合項目相關的加密和解密進行講解。接著作者準備穿插著VC++6.0和VS2019兩個版本進行講解。
1.單分支結構分析
第一步,通過VC++6.0編寫一個最簡單的程序,創建工程名稱為“RE_SF”。
運行結果如下圖所示,可以看到“Hello World”。
第二步,編寫單分支結構的相關代碼。
#include "stdafx.h"int main(int argc, char* argv[]) {if (argc > 8 ){printf("argc > 8\r\n");}return 0; }接著選擇“Win32 Release”編譯運行代碼。
第三步,接著用OD軟件打開EXE文件。
此時創建的工程目錄分布如下圖所示。
OllyDbg打開之后顯示如下圖所示的界面,程序入口地址是0x00401051。
- 程序入口:0x00401051
第四步,首先我們需要定位main函數,其方法非常簡單,找到有三個PUSH的下面就是main函數,因為main函數有三個參數(argc、argv[],、envp[])。接著按F2下斷點。
- 主函數:0x00401100
繼續按下F7跟進,會看到一個單分支結構。那么,單分支結構它有什么特點呢?
- 進入主函數:0x00401000
第五步,先給大家普及單分支語句的代碼定式基礎知識。
在高級語言中單分支代碼定式如下:
為什么需要記住這個代碼定式呢?
因為對于流程控制的識別,我們關鍵是要找到IF語句的作用域(上界和下界),上界在jxx的位置,稱之為IF_BEGIN。接著有個jxx條件跳轉,跳轉到目標且沒有其他的特征,這種就稱之為單分支的代碼定式。
回到我們的匯編代碼,拿到這個代碼之后,發現存在一個箭頭指向跳轉目標,這樣就出現了IF模塊的上界和下界,條件判斷作為IF的上界,條件跳轉的目標作為IF下界,通過這種套路方式來還原代碼。
第六步,分析嵌套的單分支語句。
假設我們的判斷中再嵌套一層或增加一個分支,又該怎么判斷呢?對于我們還原代碼的人來說,不用管它,你把上下界圈出來,然后遞歸解決。所有流程問題只要找到上下界,剩下的問題就變成了順序結構,再看著代碼一條條還原即可。
接著運行程序生成新的EXE程序,然后通過OD軟件打開分析,給主函數下個斷點然后進入主函數顯示如下圖所示的界面。
- 主函數內容:0x00401000
第七步,同樣的方法將兩層單分支語句的上下界圈出來。
我們發現這兩個判斷的下界重合了,都是跳轉到0x00401029位置,這就明顯是個嵌套。
總結下IF語句的特點:
- 觀察它的條件跳(上下界)
- 條件跳的目標上面的代碼沒有其他特征,即“ADD ESP, 4"
那么,怎么還原出高級代碼呢?
第八步,通過匯編代碼還原出高級代碼。
還原代碼需要進行反條節操作,并且學會查詢相關指令。比如JLE、JGE是什么意思呢?
- JLE(jump if less or equal,or not greater):匯編語言中的條件轉移指令,小于或等于則條件轉移
- JGE:大于或等于轉移指令
注意,在還原的時候需要做反條件操作。那么,什么叫反條件呢?具體解釋如下:
- JLE:小于等于跳轉 --> 代碼還原就是“不小于等于”,即:大于跳轉
- JGE:大于等于跳轉 --> 代碼還原就是“不大于等于”,即:小于跳轉
反條件
因為當我們滿足這個條件的時候它會跳轉到另一個地方(結束地方),它沒有執行具體的代碼;所以如果我們想要執行模塊中的代碼,就需要反條件處理。即匯編的語義和高級語言的語義是反的,高級語言的語義是滿足條件則執行語句塊,而匯編的語義是滿足條件不執行語句塊。
接著我們繼續看觸發跳轉的代碼,它是通過CMP比較來觸發的。
- CMP ESI, 8
ESI是通過參數傳遞過來的,然后和8進行不小于等于的比較 - CMP ESI, 50
ESI和50進行不大于等于的比較
此時,我們再將單分支步驟簡單歸納如下:
- (1) 通過反匯編代碼序列,匹配代碼定式;
- (2) 如果是單分支if結果,則將條件轉義指令jxx做反條件還原
第九步,接著我們換個工具用VS2019打開我們的代碼,生成新的Release版本。
然后刪除本地的Release資源,生成一個新的Release方案。
第十步,用IDA Pro打開可執行EXE程序進行分析。
同樣,使用IDA也是可以進行逆向分析的,打開新生成的逆向分析工具如下所示。
右鍵選擇“Text View”查看源代碼。
找到main函數,然后點擊“_main”位置高亮顯示。
按下“N”鍵可以對函數進行重命名,如下圖所示。
注意,前面分享的識別方法和編譯器版本、編程語言(C++、VB)等都沒有關系,它是編譯原理的問題。接著我們重點還是回歸到代碼上去,點擊“loc_401039”函數高亮,同樣的方法劃分出這個單分支的上界和下界,并且嵌套了一個單分支,最終還原出源代碼。
所以,不論使用VC++6.0或VS編譯工具,還是使用IDA或OD分析工具,它們還原代碼的原理及方法都是一樣的。在實際項目中,不論你用什么分析工具,最終能分析出結果就好。
2.雙分支結構分析
第一步,編寫雙分支代碼。
第二步,普及雙分支語句的代碼定式基礎知識。
在高級語言中雙分支代碼定式如下。該代碼序列關鍵是發現jxx后,需要檢查目標看看下面有沒有一個jmp指令,如果有個jmp且是往下跳的,if-else就成立了。
第三步,接著生成新的exe文件,用OD打開分析。
同樣的方法進入主函數,然后F7單步步入0x00401000位置,如下圖所示。
核心代碼及其跳轉如下圖所示。
- JLE --> 0x0040100E:PUSH操作
- JMP --> 0x00401013:CALL操作
雙分支結構特點:
- jxx的目標處上一行指令為jmp,而且是往高地址去的jmp(往下跳)。如果是循環,后面會講到它可能往上跳。
確定上下界之后,生成如下圖所示的if模塊和else模塊,同樣的反條件處理還原代碼。
注意,這里有一個小小的優化,編譯原理中的代碼外提。它是什么意思呢?
假設有個節點A,現在有了流程分支B1和B2,B1完成后執行C,B2完成后也會執行C。編譯器為了減小代碼的節點,因為代碼節點越多,代碼越長,就做了等價流程的代碼外提優化,從而匯總到C,少了一個節點。
- 編譯器會視情況減少節點的優化
- 編譯器也會增加節點來減小路徑的優化
第四步,采用同樣的方法用IDA工具分析還原代碼,其效果也一樣。
接著你可能會疑問這兩個PUSH是干啥呢?**
- push offset aArgc8 ; “argc > 8\r\n”
- push offset aArgc8_0 ; “argc <= 8\r\n”
C語言中沒有標準的高級語法對應匯編中的PUSH操作,說明它有代碼優化了,就是代碼外提操作。它們有個公共的函數調用被提到下面去了,就是下圖所示的兩行代碼,這個時候我們要把它放回去方便還原。
接著我們復制匯編代碼至C語言中進行還原,方便大家理解。代碼如下,此時增加了if和else的上下界,但發現兩個push無法還原。
同時將代碼外提部分分別放到if和else模塊中,就能實現最終代碼還原,如下圖所示。最后刪除掉多余的匯編注釋即可。
繼續還原條件判斷內容,JLE小于等于換成大于8就好。在真實環境中,還會遇到雙分支中有循環或條件嵌套的問題,不要擔心,找到上下界繼續分析即可。
#include "stdafx.h" #include <stdlib.h>int main(int argc, char* argv[]) {//.text:00401000 cmp [esp+argc], 8 //.text:00401005 jle short loc_40100Eif (argc > 8){ //.text:00401007 push offset aArgc8 ; "argc > 8\r\n" //.text:0040100C jmp short loc_401013 //.text:00401013 call sub_4010C6printf("argc > 8\r\n"); //.text:00401018 add esp, 4} //.text:0040100E ; --------------------------------------------------------------------------- //.text:0040100Eelse{ //.text:0040100E loc_40100E: ; CODE XREF: _main+5↑j //.text:0040100E push offset aArgc8_0 ; "argc <= 8\r\n" //.text:00401013 call sub_4010C6printf("argc <= 8\r\n"); //.text:00401018 add esp, 4} //.text:00401013 //.text:00401013 loc_401013: ; CODE XREF: _main+C↑j //.text:0040101B push offset aPause ; "pause"system("pause");return 0; }二.C++逆向循環結構基礎入門
1.do-while結構分析
循環包括do-while、while和for三種,你會覺得哪一種消息最高呢?do-while是三種循環中效率最高的,由于其無條件先執行一次,所以大家很少使用,但其效率很高。
基本語法
先執行,再判斷。先執行一遍循環操作,若符合條件,循環操作繼續執行,否則退出循環。
第一步,我們編寫一個1加到100的循環代碼,這次直接使用Debug版本。
#include "stdafx.h" #include <stdlib.h>int main(int argc, char* argv[]) {int n = 1;int nSum = 0;//do-while 執行一次do {nSum = nSum + n;n++;} while(n <= 100);printf("%d", nSum);system("pause");return 0; }第二步,通過OD打開運行的EXE程序“RE_XH.exe”。
- 程序入口地址:0x00401260
第三步,往下查找代碼,發現3個PUSH后(參數)就是主函數,然后F2添加斷點并F7步入主函數。
- 主函數:CALL RE_XH.00401005
第四步,分析匯編代碼。
這里存在一個JLE跳轉,如果條件跳往上跳就是do-while循環。
循環肯定會往上走,否則構成不了循環,它需要反復執行同一代碼段。如果跳轉的目標沒有檢查條件,就是do-while循環。簡單總結下識別do-while循環步驟:
- 識別代碼定式
- 如果是do循環,則按jxx同條件還原等價高級代碼
注意,同條件的就只有do-while結構。在do-while循環中,它跟匯編的語義是一樣的,只有當條件滿足則流程更新到循環的起始地點,所以它是正條件還原。而前面的if-else判斷都是反條件。
//程序語言 do {... }while(xxx);//代碼定式 DO_BEGIN:...jxx DO_BEGIN DO_END:...2.while結構分析
基本語法
先判斷,再執行。
第一步,我們編寫一個1加到100的循環代碼。
#include "stdafx.h" #include <stdlib.h>int main(int argc, char* argv[]) {int n = 1;int nSum = 0;while (n <= 100){nSum = nSum + n;n++;} printf("%d", nSum);system("pause");return 0; }第二步,分析while循環的代碼定式。
注意,該循環的Debug版本和Release版本存在差異,接下來會對比分析。我們先給出代碼定式,如下所示。
while循環的條件跳是往上跳的,它需要反復執行同一代碼段。
第三步,通過OD打開運行的EXE程序“RE_XH.exe”。
- 程序入口地址:0x00401260
第四步,往下查找代碼,發現3個PUSH后(參數)就是主函數,然后F2添加斷點并F7步入主函數。
- 主函數:CALL RE_XH.00401005
第五步,分析匯編代碼。
這里存在一個JG跳轉,它有點像if語句,下面還有一個JMP,有點像if-else指令,但是它的跳轉是地址減量跳或往上跳,所以它是循環。
這時會發現while循環比剛才的多了一個跳轉。我們會過計算機組成原理,當處理器執行跳轉指令時,流水線會暫時掛起失效,本來流水線在取指令時已經準備預讀后面的代碼了,結果在譯碼過程中是個跳轉,后面的代碼預讀就會出錯,然后做流水線清理工作。所以,while循環有兩跳對流水線的傷害比do-while大。
第六步,接著我們用高版本VS2019編譯一個Release版本,并用IDA進行分析,看看高版本有什么優化。
查看Text View,我們定位到main函數之后,看看它做的優化。它把循環的起點對齊到十六進制10的倍數地址,中間會用nop進行空指令填充。同時,它的匯編循環體變成了do-while循環。
注意,前面的VC++ Debug版本用IDA工具打開如下圖所示。上圖和下圖同樣都是while循環,但低版本可以看到JG(往下跳)和JMP(往上跳)兩個跳轉,典型的while循環;而高版本的卻修改成了do-while循環的形式。
問題1:由于do-while循環會執行一次循環體,難道它不擔心編譯器出錯嗎?
其實它比較的數值是常量,常量可以在編譯期間預置其結果的,其實編譯器在第一次的判斷時先進行了一次常量傳播,令n等于1,即判斷的是 while(1<=100),比較1和100的關系條件必成立。
問題2:那么,如果將100替換成變量,編譯器還能識別嗎?或者會報錯?
此時的編譯器會將其進行轉換,變成如下圖所示的形式再執行do-while循環。其中if(n<=argc)條件判斷嵌套一個循環。
對應的匯編代碼如下,其中“jl short loc_401016”往下跳,還原成一個單分支,循環里面還有一個跳轉“jle short loc_40100F”往上跳,滿足do-while循環,最終還原成if加do-while,或者你知道有這個優化,直接還原成帶變量的while循環也可以。
但需要注意,能不能把do-while直接還原成while循環,還需要看看這兩個條件有沒有相關性。如果有相關性才能還原,比如外層判斷是文件的打開狀態,while是迭代n值,這種情況不能還原。下圖可以看到if和循環都是EAX參數比較,所以具有相關性。
3.for結構分析
下面開始分析for循環結構。
for(表達式1;表達式2;表達式3;) {語句; }第一步,我們編寫一個for循環代碼。
#include "stdafx.h" #include <stdlib.h>int main(int argc, char* argv[]) {int nSum = 0;for (int n = 1; n<=argc; n++){nSum = nSum + n;} printf("%d", nSum);system("pause");return 0; }第二步,編譯生成新的Debug版可執行程序。
第三步,通過IDA打開運行的EXE程序“RE_XH.exe”。
產出了三個跳轉代碼,如下圖所示。
其代碼定式如下所示,可以看到JMP、JG和JMP三個跳轉。注意,for循環中FOR_STEP地址是低于BODY執行體的地址的。
第四步,分析匯編代碼。
首先MOV進行初始化賦值1,接著JMP跳轉到比較部分,比較不成立則JG直接跳出循環,否則執行循環體BODY內容,接著繼續JMP跳轉上去執行n++操作。
注意,由于Release版本都被編譯器優化成了do-while循環,所以我們需要在Debug版下進行對比。
第五步,通過VS2019生成Release版本,然后用IDA打開代碼對比。
IDA打開如下圖所示,發現和do-while一樣,高版本做了一點小處理,每次循環總次數增加了4(add eax,4),從而提升效率。
三.總結
寫到這里,這篇文章就介紹完畢,希望對您有所幫助,最后進行簡單的總結下。
- 條件語句逆向分析
- 循環語句逆向分析
學安全一年,認識了很多安全大佬和朋友,希望大家一起進步。這篇文章中如果存在一些不足,還請海涵。作者作為網絡安全初學者的慢慢成長路吧!希望未來能更透徹撰寫相關文章。同時非常感謝參考文獻中的安全大佬們的文章分享,深知自己很菜,得努力前行。
2020年8月18新開的“娜璋AI安全之家”,主要圍繞Python大數據分析、網絡空間安全、人工智能、Web滲透及攻防技術進行講解,同時分享CCF、SCI、南核北核論文的算法實現。娜璋之家會更加系統,并重構作者的所有文章,從零講解Python和安全,寫了近十年文章,真心想把自己所學所感所做分享出來,還請各位多多指教,真誠邀請您的關注!謝謝。
(By:Eastmount 2020-12-25 周五夜于武漢 https://blog.csdn.net/Eastmount)
參考資料:
真心推薦大家好好看看這些視頻和文章,感恩這些大佬!前非常推薦錢老師的視頻,感謝華科UP主。
[1] 科銳逆向的錢林松老師受華中科技大學邀請- “逆向分析計算引導”
[2] C語言逆向工程之游戲輔助開發 - C語言Plus
[3] https://www.bilibili.com/video/BV1J5411x7qz?p=1
總結
以上是生活随笔為你收集整理的[系统安全] 六.逆向分析之条件语句和循环语句源码还原及流程控制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [Python图像处理] 三十三.图像各
- 下一篇: [系统安全] 八.Windows漏洞利用