[筆記]CTF逆向入門教程


CTF Reverse——從入門到入土

(入門教程第一部分)
本教程適用於完全剛剛入門CTF的同學,路過的有經驗的大佬輕噴orz

  1. Reverse 簡介
    逆向工程(Reverse Engineering),又稱反向工程,是一種技術過程,即對一項目標產品進行逆向分析及研究,從而演繹並得出該產品的處理流程、組織結構、功能性能規格等設計要素,以制作出功能相近,但又不完全一樣的產品。逆向工程源於商業及軍事領域中的硬件分析。其主要目的是,在無法輕易獲得必要的生產信息下,直接從成品的分析,推導產品的設計原理。
    逆向工程可能會被誤認為是對知識產權的嚴重侵害,但是在實際應用上,反而可能會保護知識產權所有者。例如在集成電路領域,如果懷疑某公司侵犯知識產權,可以用逆向工程技術來查找證據。
    (引自Wikipedia:逆向工程條目)

  2. 對CTF 逆向工程的基本介紹

    1. 形式
      CTF競賽中,reverse的題目囊括廣泛,一切可執行代碼均可以用於進行隱藏flag並要求進行逆向。常見的有:
      a. Win32程序,包括32位、64位應用程序,特征為PE格式
      b. Linux程序,特征為ELF文件
      c. Android程序,特征為Apk安裝包
      d. MacOS程序
      e. 工程代碼源文件,例如.c文件、.py文件、.class文件。
      f. 可執行代碼片段,包括已編譯但是無法正確運行的代碼片段、未編譯或中途編譯文件等
      值得一提的是,Linux、Android、MacOS可執行文件都基於Linux內核,因而在分析時具有較為相似的特征和語法結構。
      上面列出的“程序”並不一定可以執行,某些情況下因為系統差異、運行時庫缺失、架構不符等原因,這些可執行程序並不一定能夠執行。

    2. 語言要求
      對於逆向工程,由於編譯后的應用程序多由字節碼寫成,因此從字節碼回編譯到可讀文本時不能完全恢復工程源代碼文件,因此需要掌握多門技術語言。
      a. 匯編語言
      匯編語言(assembly language)是任何一種用於電子計算機、微處理器、微控制器,或其他可編程器件的低級語言。
      在不同的設備中,匯編語言對應着不同的機器語言指令集。
      一種匯編語言專用於某種計算機系統結構。
      x86/amd64匯編指令的兩大風格分別是Intel匯編AT&T匯編,分別被Microsoft Windows/Visual C++與GNU/Gas采用(Gas也可使用Intel匯編風格)
      關於兩種匯編風格的區別,可以訪問匯編語言的Wikipedia介紹 獲取相關信息
      image

      並不需要完全學習所有匯編指令和所有匯編語言,從一種匯編語言風格可以很方便的類推另一種風格,只需要注意其區別
      b. C語言
      作為一種高效的一種高級語言,C語言是大多數底層API實現的基本語言。另外,在IDA中反匯編生成的pseudocode也是C語言風格,熟悉C語言可以對逆向生成的一些結果有很好的快速察覺。
      c. Python
      Python是一種方便的、易於學習的高級語言,在解密、爆破中經常使用Python進行腳本編程。由於大部分問題的答案求解並不復雜,因此對代碼執行的效率要求並不高,使用Python作為求解器是可能的
      另一方面,Python豐富的包為大多數求解器的開發提供了大量的便利。

  3. 技術和流程

  4. 逆向基本流程

    這里主要講三種文件:PE/ELF/APK文件的逆向分析,介紹多種應用程序的使用

    1. 文件分析
      文件根據其首字節的不同,區分不同格式。這些格式也同時被刻意的表達在拓展名之上。一般而言按照下面的順序確認文件的格式:
      a. 根據拓展名確認
      b. 使用頭字節識別(利用命令file或其他識別程序)
      c. 對於加密、混淆的文件需要去混淆和解密之后再進行a,b
      關於識別的file指令見接下來的“技術”節

    2. 查詢程序的目標操作系統位數以及殼(對於PE/ELF文件而言)
      這里使用exeinfope進行查詢,也可以使用PEid進行查詢。

      image

      exeinfope:圖中“UPX 0.89 - 3.xx”代表了程序加殼是UPX殼,需要用對應的解壓軟件反混淆。而“Image is 32bit executable”代表程序是32位應用程序。

      image

      PEiD:圖中"PESniffer"欄對編譯器進行嗅探,"PEiDDSCAN"對殼進行嗅探。上面的子系統”Win32,GUI“告訴我們這是一個具有32位Windows圖形界面的應用程序。

    3. 分析APK包(對於APK包而言)

      APK包的分析較PE/ELF文件的分析要復雜一些。首先APK包事實上就是一個zip文件。其中含有代碼、資源文件。

      image

      一個示例apk解壓的文件體系

      其中一定存在

      1. AndroidManifest.xml

        該文件是每個應用都必須定義和包含的,它描述了應用的名字、版本、權限、引用的庫文件等等信息,如要把apk上傳到Google Market上,也要對這個xml做一些配置。在apk中的AndroidManifest.xml是經過壓縮的,可以通過AXMLPrinter2工具解開,具體命令為:java -jar AXMLPrinter2.jar AndroidManifest.xml

      2. META-INF目錄
        META-INF目錄下存放的是簽名信息,用來保證apk包的完整性和系統的安全。在eclipse編譯生成一個apk包時,會對所有要打包的文件做一個校驗計算,並把計算結果放在META-INF目錄下。這就保證了apk包里的文件不能被隨意替換。比如拿到一個apk包后,如果想要替換里面的一幅圖片, 一段代碼, 或一段版權信息,想直接解壓縮、替換再重新打包,基本是不可能的。如此一來就給病毒感染和惡意修改增加了難度,有助於保護系統的安全。

      3. res目錄
        res目錄存放資源文件。包括圖片,字符串等等。

      4. lib目錄

        lib目錄下的子目錄armeabi存放的是一些so文件。eclipse在打包的時候會根據文件名的命名規則(lib****.so)去打包so文件,開頭和結尾必須分別為“lib”和“.so”,否則是不會打包到apk文件中的。

      5. assets目錄

        assets目錄可以存放一些配置文件,這些文件的內容在程序運行過程中可以通過相關的API獲得。具體的方法可以參考SDK中的例子:

        在sdk的\SDK\1.6\android-sdk-windows-1.6_r1\platforms\android-1.6\samples\ApiDemos例子中,有個com.example..android.apis.content 的例子,在這個例子中他把一個text文件放到工程的asset目錄下,然后把這個txt當作普通文件處理。處理的過程在ReadAsset.java 中。同理,asset也可以放置其他文件。

      6. classes.dex文件
        classes.dex是java源碼編譯后生成的java字節碼文件(首先是java文件通過jdk編譯成字節碼文件然后經過dex編譯成classes.dex)。但由於Android使用的dalvik虛擬機與標准的java虛擬機是不兼容 的,dex文件與class文件相比,不論是文件結構還是opcode都不一樣。目前常見的java反編譯工具都不能處理dex文件。Android模擬 器中提供了一個dex文件的反編譯工具,dexdump。用法為首先啟動Android模擬器,把要查看的dex文件用adb push上傳的模擬器中,然后通過adb shell登錄,找到要查看的dex文件,執行dexdump xxx.dex。另,有人介紹到Dedexer是目前在網上能找到的唯一一個反編譯dex文件的開源工具,需要自己編譯源代碼。

      7. resources.arsc
        編譯后的二進制資源文件的索引(apk文件的資源表/索引)

      所以我們主要就要分析其中的classes.dex文件。有些時候該文件可能不止一個,我們都要進行分析。

    4. 反編譯對象文件獲取基本代碼

      1. 對於PE/ELF/Android文件都適用於將文件直接拖入IDA(Interactive Disassembler Professional)中進行分析。這樣的不執行代碼進行反匯編的過程叫做靜態分析。而對於WinPE文件可以使用OD(OllyDebug)進行動態調試。

        image

        一個經典的IDA32界面,其中包含了函數列表、反匯編窗口等消息。此處分析的是一個Windows文件(ucrtbased.dll)

        image

        IDA的偽代碼(Pseudocode)功能,可以很方便的將一些顯而易見的邏輯翻譯為C語言代碼,便於分析。然而其語法不一定嚴格遵照C語言,例如其中可能有未聲明的變量,有奇怪的類型(_BYTE)等。

        image

        Ollydbg界面,此處分析的是他自己(Ollydbg spec.exe)

      2. 對於Android文件,也可以使用其他更為方便的工具進行分析,例如jeb、APKIDE等

        image

        APKIDE界面,包括了反編譯生成的smali文件窗口、資源窗口等

    5. 分析基本代碼解其中的flag

  5. 技術詳解

    1. 前言

      至此為止,你已經學習了基本的reverse流程和如何對樣本進行分析的基本流程。這意味着可以從現在開始對各類基本樣本進行分析了。下面將給出一些常見的/不常見的技術的說明和解決方法。

      1. 字符串分析技術
        由於CTF的reverse賽題的答案提交形式為flag{xxx}(字段flag可能有所更改),故而大部分題目所做的工作就是花式隱藏這個字符串。我們要尋找的便是這樣的字符串: %name%{%code%}。其中標志為符號{}。要尋找這樣的字符串,首先需要介紹變量存儲在內存中不同的形式

        1. 全局變量
          全局變量被聲明在所有函數、過程的外面,是一個固定化的字符串。因而全局變量保存在內存的全局存儲區中,占用靜態的存儲單元

          #include <stdio.h>
          char flag[] = {'f','l','a','g','{','e','x','a','m','p','l','e','}','\0'};
          int main()
          {
          	printf("%s",flag);
          	return 0;
          }
          

          上面給出了一個最簡單的全局變量表達式,這樣的可執行程序在IDA32中分析的界面如下:

          image

          其中第.text:00411861(此處的.text代表某個節,后面代表地址)行顯示此時push offset aFlagExample ,示意將aFlagExample壓入棧中。此處的aFlagExample為:(IDA對反編譯識別的字符串都試圖進行命名,並冠以字母a
          image

          其中在數據節可以看到已經聲明的數據flag{example},這就是存儲在全局變量中的字符串。

          這樣的字符串可以很方便的找到,打開字符串子頁面
          image

          在其中可以很快找到這樣的字符串
          image

          此處可以按下CTRL+F鍵呼出“查找”,輸入大括號再進行篩選就可以找到字符串

          image

          由於這種加密方式過於簡單,所以這樣的題目基本都在簽到題

        2. 局部變量
          局部變量被聲明在函數、過程內部,因而只有在調用函數、引用過程時生成。局部變量保存在棧中,只有在所在函數被調用時才動態地為變量分配存儲單元。

          #include <stdio.h>
          char flag[8] = {0};
          int main()
          {
          	flag[0] = 'f';
          	flag[1] = 'l';
          	flag[2] = 'a';
          	flag[3] = 'g';
          	flag[4] = '{';
          	flag[5] = 'p';
          	flag[6] = 'i';
          	flag[7] = '}';
          
          	printf("%s", flag);
          	return 0;
          }
          

          對於上面這段程序,由於flag只在外面聲明了一個框架,他的賦值是在函數內進行的,因此不能在string子窗口或者.data節找到端倪。
          image

          因而我們打開ida的反匯編界面

          image

          其中的指令jmp _main_0指示了代碼的跳轉。在IDA中雙擊這個跳轉中的參數_main_0,IDA便自動定位到其函數位置並展示了如下的代碼:

          image

          這是一段匯編指令。要簡化匯編的分析,可以按下F5快捷鍵打開偽代碼(pseudocode)窗口
          image

          可以看到這段程序被方便的轉換為了以C語言為主要格式的偽代碼。
          image

          一個小技巧是:IDA中在偽代碼中使用/鍵可以進行注釋,而反匯編窗口則使用;進行注釋。使用N鍵可以將變量、函數重命名。

          上面給出了一個函數,sub_4110CD,這是標准輸入輸出中的printf函數。感興趣的讀者可以自行定位到其實現看看IDA是如何反匯編系統函數的。

          同時要提到的技巧就是,十分逆向三分猜。對於逆向的程序,由於其復雜龐大不可能完全搞清楚,在已經確認是有效的、真實的代碼結構之后,可以宏觀上先猜測某個函數、結構、代碼片段的功能,在根據上下文反推函數的功能,並不需要完全定位到函數的底層實現。上面我們看到函數sub_4110CD接受了一個很眼熟的參數"%s",這是printf函數接受的參數format中的一種,因此可以斷定這就是printf函數或者其衍生版本。

      2. 字符串加密、混淆技術

        1. 簡單異或加密
          上面講的都太簡單,畢竟字符串直接寫在了程序之內。下面我們介紹題目中的基本程序過程:驗證反推
          對於大部分reverse題目,他們都接受一個用戶輸入並進行比對。如果此輸入和程序內定義的flag相同則輸出正確輸出,否則就提示輸入錯誤。各種reverse題目大同小異都在隱藏flag、隱藏判定過程上做手腳。
          異或是指對於命題A, B, A XOR B的運算。當A、B兩兩數值相同為否,而數值不同時為真。這個運算具有一個性質:自反律,即A XOR B XOR B = A ,即將某明文對密碼異或,得到的密文再對密碼異或又可以得到明文。利用這個性質,可以將用戶的輸入與密碼異或,與內存中已經異或並存儲的flag字符串進行比對。(在C語言里,異或XOR表示為^
        #include <stdio.h>
        #include <string.h>
        char flag[10] = { 102,10,107,12,119,7,110,19,35 };
        int main()
        {
        	char input[256] = { 0 };
        	printf("input your flag:");
        
        	scanf("%s", input);
        	input[255] = '\0';
        	for (int i = strlen(flag)-1; i > 0 ; i--)
        	{
        		flag[i] = flag[i] ^ flag[i - 1];
        	}
        	flag[8] = '\0';
        	if (strcmp(flag, input))
        	{
        		printf("\nYou are correct!");
        	}
        	else
        	{
        		printf("\nYou failed");
        	}
        	return 0;
        }
        

        image

        上述程序在IDA的匯編分析下看不出端倪,並且神奇的是原程序里定義的全局變量也不見了。這就是IDA的缺陷之處,因為IDA必須按照一定的准則方能猜測字符串、全局變量的存在。但是這沒關系,我們檢查他的偽代碼:

        image

        乍一看毫無頭緒,但是仔細觀察一些循環、判斷結構就會發現IDA不過將一些本該寫道一行的代碼寫到了兩行。這並不影響我們對代碼邏輯進行分析

        image

        我們可以確定程序對這個byte_???變量進行了異或操作,跟蹤此變量:

        image

        對於.data斷,其中有指令align 8:align 偽指令將一個變量對齊到字節邊界、字邊界、雙字邊界或段落邊界。為了滿足對齊要求,匯編器會在變量前插入一個或多個空字節。為什么要對齊數據?因為,對於存儲於偶地址和奇地址的數據來說,CPU 處理偶地址數據的速度要快得多**。這里進行了數據對齊,並且接下來的數據都是db 0,因此可以斷定這是一個用特殊方法聲明的字符串。,也就對應了源文件里的

        char flag[10] = { 102,10,107,12,119,7,110,19,35 };
        

        將這段數據按照程序的邏輯異或就可以得到flag

        1. 凱撒密碼
          凱撒密碼和異或加密大同小異。凱撒密碼(Caesar cipher),或稱凱撒加密、凱撒變換、變換加密,是一種最簡單且最廣為人知的加密技術。凱撒密碼是一種替換加密技術,明文中的所有字母都在字母表上向后(或向前)按照一個固定數目進行偏移后被替換成密文。例如,當偏移量是3的時候,所有的字母A將被替換成D,B變成E,以此類推。這個加密方法是以羅馬共和時期凱撒的名字命名的,據稱當年凱撒曾用此方法與其將軍們進行聯系。

          凱撒密碼通常被作為其他更復雜的加密方法中的一個步驟,例如維吉尼亞密碼。凱撒密碼還在現代的ROT13系統中被應用。但是和所有的利用字母表進行替換的加密技術一樣,凱撒密碼非常容易被破解,而且在實際應用中也無法保證通信安全。

        2. base64混淆

          Base64(基底64)是一種基於64個可打印字符來表示二進制數據的表示方法。由於log(2,64)=6,所以每6個比特為一個單元,對應某個可打印字符。3個字節相當於24個比特,對應於4個Base64單元,即3個字節可由4個可打印字符來表示。在Base64中的可打印字符包括字母A-Z、a-z、數字0-9,這樣共有62個字符,此外兩個可打印符號在不同的系統中而不同。一些如uuencode的其他編碼方法,和之后BinHex的版本使用不同的64字符集來代表6個二進制數字,但是不被稱為Base64。

          Base64常用於在通常處理文本數據的場合,表示、傳輸、存儲一些二進制數據,包括MIME的電子郵件及XML的一些復雜數據。

          Base64的特征在於:

          • 字符串末尾存在"="或者"=="
          • 密文長度一定是4的倍數

          在程序中,Base64加密的特征在於:

          • 存在一個密文表,其形式為ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
            image

            (這里展示的是非常規Base64加密的密碼表)

          (拓展)Base64加解密過程介紹

          Base64的主要思想是將3個字節的數據映射為4個字節的數據.從二進制觀點看,3字節一共24bit,把24bit依次分組生成4個字節.根據Base64的映射表就可以確定密文.相對的,也就可以推出明文.

          因為6個bit最多能表示64種模式,因此Base64編碼出來的字符種類只有64個,這也是Base64名字的由來。

          從ASCII中0x20 ~ 0x7E(可打印字符)選出64個普通的ASCII字符建立映射表:

          image

          如果要編碼的字節數不能被3整除,最后會多出1個或2個字節,那么可以使用下面的方法進行處理:先使用0字節值在末尾補足,使其能夠被3整除,然后再進行Base64的編碼。在編碼后的Base64文本后加上一個或兩個=號,代表補足的字節數。也就是說,當最后剩余兩個八位(待補足)字節(2個byte)時,最后一個6位的Base64字節塊有四位是0值,最后附加上兩個等號;如果最后剩余一個八位(待補足)字節(1個byte)時,最后一個6位的base字節塊有兩位是0值,最后附加一個等號。 參考下表:

          image

      3. 爆破
        對於具有輸入的代碼,可以對應的寫一個自動化腳本不斷試錯。這樣的解題方法因為耗時長,因此作為下策在萬不得已時使用。

      4. 迷宮
        這類題目的flag字符串不存在程序內部,他們的flag字符串是一系列操作的組合。在程序內部生成一個迷宮,這個迷宮需要按照特定的方法,輸入字符,操作光標移動最終到達終點。輸入的字符組合就是flag。其特征為:存在一個switch判斷或者多個if判斷,並且其中含有一個包含大量重復字符和空格的字符串。例如#s###### # # # ## # # # ## # # ###### # # # #e######這樣的字符串一般可以組成一個矩陣。

    2. pyc文件
      使用uncompyle6進行字節碼反編譯。由於pyc文件是python源碼生成的一種字節碼,因此可以很方便的反編譯生成源碼。

      pip install uncompyle6
      uncompyle6 -o code.py target.pyc
      

      其中的code.py是保存到的位置,target.pyc為題目的pyc文件

  6. 常見問題

    1. 字符串逆序
    
         根據內存模型,對於小段(littile-ending)存儲方法,所有在內存里的數據都是倒序存儲的。因此從匯編代碼里讀出的數據可能需要進行反向,以便進一步分析。
    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM