[系統安全] 六.逆向分析之條件語句和循環語句源碼還原及流程控制

您可能之前看到過我寫的類似文章,爲什麼還要重複撰寫呢?只是想更好地幫助初學者瞭解病毒逆向分析和系統安全,更加成體系且不破壞之前的系列。因此,我重新開設了這個專欄,準備系統整理和深入學習系統安全、逆向分析和惡意代碼檢測,“系統安全”系列文章會更加聚焦,更加系統,更加深入,也是作者的慢慢成長史。換專業確實挺難的,逆向分析也是塊硬骨頭,但我也試試,看看自己未來四年究竟能將它學到什麼程度,漫漫長征路,偏向虎山行。享受過程,一起加油~

系統安全系列作者將深入研究惡意樣本分析、逆向分析、攻防實戰和Windows漏洞利用等,通過在線筆記和實踐操作的形式分享與博友們學習,希望能與您一起進步。前文介紹了OllyDbg和Cheat Engine工具逆向分析用法,完成植物大戰殭屍的遊戲輔助器,包括修改陽光值和自動拾取陽光兩個功能。這篇文章將帶領大家來學習科銳錢林松老師的視頻,詳細講解條件語句和循環語句源碼還原及流程控制逆向。希望對入門的同學有幫助。

話不多說,讓我們開始新的征程吧!您的點贊、評論、收藏將是對我最大的支持,感恩安全路上一路前行,如果有寫得不好的地方,可以聯繫我修改。基礎性文章,希望對您有所幫助,作者的目的是與安全人共同進步,加油~

作者的github資源:

前文分析:


聲明:本人堅決反對利用教學方法進行犯罪的行爲,一切犯罪行爲必將受到嚴懲,綠色網絡需要我們共同維護,更推薦大家瞭解它們背後的原理,更好地進行防護。


一.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(...)
{
   
          
    ...
}

//代碼定式
IF_BEGIN:
    jxx IF_END
        ...
    IF_END:
        ...

爲什麼需要記住這個代碼定式呢?
因爲對於流程控制的識別,我們關鍵是要找到IF語句的作用域(上界和下界),上界在jxx的位置,稱之爲IF_BEGIN。接着有個jxx條件跳轉,跳轉到目標且沒有其他的特徵,這種就稱之爲單分支的代碼定式。

回到我們的彙編代碼,拿到這個代碼之後,發現存在一個箭頭指向跳轉目標,這樣就出現了IF模塊的上界和下界,條件判斷作爲IF的上界,條件跳轉的目標作爲IF下界,通過這種套路方式來還原代碼。

在這裏插入圖片描述


第六步,分析嵌套的單分支語句。
假設我們的判斷中再嵌套一層或增加一個分支,又該怎麼判斷呢?對於我們還原代碼的人來說,不用管它,你把上下界圈出來,然後遞歸解決。所有流程問題只要找到上下界,剩下的問題就變成了順序結構,再看着代碼一條條還原即可。

在這裏插入圖片描述

#include "stdafx.h"
#include <stdlib.h>

int main(int argc, char* argv[])
{
   
           
	if (argc > 8 )
	{
   
           
		printf("argc > 8\r\n");
		if (argc < 80)
		{
   
           
			printf("argc < 80\r\n");
		}
	}

	system("pause");
	return 0;
}

接着運行程序生成新的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就成立了。

//程序語言
if(...) 
{
   
                  
    ...
}
else 
{
   
                  
	...
}

//代碼定式
IF_BEGIN:
jxx ELSE_BEGIN
    ...
IF_END:
    jmp ELSE_END
ELSE_BEGIN:
    ...
ELSE_END:
    ...

第三步,接着生成新的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_40100E
	if (argc > 8)
	{
   
                    
//.text:00401007                 push    offset aArgc8   ; "argc > 8\r\n"
//.text:0040100C                 jmp     short loc_401013
//.text:00401013                 call    sub_4010C6
		printf("argc > 8\r\n");
//.text:00401018                 add     esp, 4
	}
//.text:0040100E ; ---------------------------------------------------------------------------
//.text:0040100E
	else
	{
   
                    
//.text:0040100E loc_40100E:                             ; CODE XREF: _main+5↑j
//.text:0040100E                 push    offset aArgc8_0 ; "argc <= 8\r\n"
//.text:00401013                 call    sub_4010C6
		printf("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是三種循環中效率最高的,由於其無條件先執行一次,所以大家很少使用,但其效率很高。

基本語法
先執行,再判斷。先執行一遍循環操作,若符合條件,循環操作繼續執行,否則退出循環。

do{
   
                      
    循環操作語句;
}whlie(循環條件);

在這裏插入圖片描述

第一步,我們編寫一個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結構分析

基本語法
先判斷,再執行。

whlie(循環條件){
   
                           
    循環操作語句;
}

在這裏插入圖片描述

第一步,我們編寫一個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(xxx)
{
   
                            
    ...
}

//代碼定式
WHILE_BEGIN:
    jxx WHILE_END
        ...
    jmp WHILE_BEGIN
WHILE_END:
    ...

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

































發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章