對於逆向工程和大多數人同樣接觸始於看雪的《加密與解密》,但在至關長一段時間內對於逆向的認知都只停留在PE格式、OD下斷點動態調試、IDA各類窗口靜態調試這幾個名詞上。看了一遍又一遍的書和視頻,看的時候以爲頗有道理,過了以後則徹底沒懂到底說了些什麼。到後來老師引入CTF,其餘人津津有味地作了出來,而本身手足無措看網上的答案這麼操做一下那麼操做一下結果就出來了徹底不懂在作什麼爲何要這麼作,由此陷入了深深的惶恐。由於在我的看來凡事都是要有依據懂原理才能進行的,但本身成績不落後別人動手編程能力不落後別人,徹底不明白CTF這種本身感受沒頭沒腦的東西爲何別人卻能夠作得出來,世界觀幾要崩潰。時至今日也只能說按照本身的思路確實能夠進行逆向,而至於別人爲何感受沒有章法也能逆向則仍是沒有想清楚。html
在說明中提到「在至關長一段時間內對於逆向的認知都只停留在PE格式、OD下斷點動態調試、IDA各類窗口靜態調試這幾個名詞上」,這其中的問題是大多數教程都講清楚了怎麼作,而沒向初學者解答一個問題,何時這麼作。或者換一種說法就是,當咱們進行逆向前咱們要想清楚一個問題:當咱們逆向的時候,咱們究竟是在作什麼。python
所謂逆向,其實咱們就是在作如下三件事情:linux
第一步,藉助PE格式解析可執行文件。一是指olldbg和ida按PE格式解析出可執行文件的各組成部份包括數據段代碼段指令字符串等等;二是指咱們本身要能把od和ida解析出來的數據段代碼段指令字符串與PE格式對應上,對應上的意思首先是說咱們要知道各窗口的東相都在PE文件的什麼位置,而後是說咱們要知道咱們本身想要在的東西應該到哪一個窗口去找。android
第二步,經過各類手段逼近目標代碼。目標代碼,好比要去除nag窗口的目標代碼彈窗代碼,要去除次數限制的目標代碼就要計數代碼。逼近就是鎖定目標代碼的起始地址和/或結束地址。逼近手段,OD的逼近手段是各類窗口+斷點,IDA的逼近手段是各類窗口+交叉引用。編程
第三步,是分析目標代碼邏輯。就是指或是從鎖定的起始地址向結束地址一條條指令地追蹤,或是從結束地址往起始地址一條條指令地分析肯定目標代碼的邏輯,及決定跳過的對策。windows
在這三步中不少教程讓人感受逆向就是在操做od/ida,但其實od/ida複雜度頂多和burpsuite/wireshark一個級別也沒有不少操做,工具操做背後的PE格式理解、彙編掌握纔是逆向的真正關建點。下文三四五大點依次對應以上三步。api
首先介紹本文使用的示例程序,這是密碼學中用C++寫的一個SHA1加密工具,操做過程就是使用小組組員姓名登陸,認證經過和便可進入程序加密操做主界面。以下所示:tcp
可執行文件----windows平臺就是exe、dll等文件,linux平臺就是各類命令和so文件,android平臺就是dex文件。是磁盤狀態的文件而不是加載到內存後的內存中的文件。函數
可執行文件格式----就是可執行文件的組織格式。對操做系統而言可執行文件格式指示系統如何正確解析、加載和運行可執行文件;對逆向工具而言可執行文件格式指導逆向工具解析出可執行文件的代碼段數據段指令和字符串等各組成部份;對人而言,咱們本身也得要掌握可執行文件格式能將逆向工具只是解析出的各部分與可執行文件格式對應上理解各成分的含義做用,否則只是工具解析出來幾個窗口在那裏並無什麼用,另外加殼脫殼等防禦也須要咱們掌握可執行文件格式從可執行文件格式入手工具
windows平臺就是PE格式、linux平臺就是ELF格式、安卓的dex也有其格式。其實擴展開去不僅可執行文件,全部的文件都須要有格式都須要或多或少的一些頭部指示其解析器如何對其進行解析,諸如視頻、圖片、pdf等等。數據包也須要以太頭、ip頭、tcp頭等格式。
pe文件格式就是exe文件的格式(下邊是低地址上邊是高地址)
下邊是ultraedit以十六進制方式打開本文使用程序的exe文件的開頭部分。
在圖中能夠看到MZ、「This program...」、".text"等字眼,這樣讀者應該能夠將上圖的pe文件格式與下圖的文件內容對應起來。
使用od就是使用od的各類窗口(好比字符串)和斷點(好比內存斷點)逼近目標代碼,咱們這裏只點出這個關鍵點而後簡單總結一下od的各類窗口和斷點,更詳細學習推薦本身帶上這一關鍵點去看《加密與解密》和小甲魚的解密系列。
下邊幾個圖是小甲魚調試課程ppt的截圖,其餘窗口及OD操做就很少介紹了。
寄存器 | 歸類 | 名稱 | 初始設定做用 | 當前做用 |
EAX | 通用寄存器 | 擴展累加寄存器 | 函數調用時放回值放裏邊。STOS會將EAX內容複製到EDI指向的位置。其餘時間隨便使用 | |
EBX | 通用寄存器 | 擴展基址寄存器 | 開始用於存放基地址 | 如今因爲能夠本身完成尋址,因此ebx平時能夠隨便用 |
ECX | 通用寄存器 | 擴展計數寄存器 | MOVS等字符串處理指令、REP等循環指令的循環次數,每執行一輪自動減1.其餘時間隨便使用 | |
EDX | 通用寄存器 | 擴展數據寄存器 | 乘法存放高位(EAX存低位),除法存餘數(EAX存商),其餘時間隨便用 | |
ESI | 通用寄存器 | 擴展來源寄存器 | MOVS、CMPS、LODS等字符串處理指令的源地址,其餘時間也可隨便用 | |
EDI | 通用寄存器 | 擴展目標寄存器 | MOVS、CMPS、SCAS、STOS等字符串處理指令的目的地址,其餘時間也可隨便用 | |
EBP | 通用寄存器 | 擴展基址指針寄存器 | 保存當前函數當前棧底地址 | |
ESP | 通用寄存器 | 擴展堆棧指針寄存器 | 保存當前函數當前棧頂地址 | |
EIP | 通用寄存器 | 擴展的指令指針寄存器 | 保存下條將要執行的指令的地址 |
說明:
RAX共64位----EAX是RAX的低32位----AX是EAX的低16位----而AH是AX的高8位,AL是AX的低8位;其餘寄存器與EAX相似。
序號 | 斷點名稱 | 原理 | 適用範圍 |
1 | INT3斷點 | 將下斷處的指令改成INT3指令(CC),INT3會觸發一個異常,OD捕獲異常而後再將INT3修改回原指令 | 代碼段(包括DLL的代碼段) |
2 | 硬件斷點 | 將下斷的指令地址存入DR0-DR3,CPU執行指令時會比較指令地址是不是DR0-DR3中的地址,若是是會主動給od發送異常 | 代碼段 |
3 | 內存斷點 | 將下斷地址修改成不可讀/寫,當該地址被試圖進行讀/寫時會觸發異常 | 代碼段、數據段 |
4 | 內存訪問一次性斷點 | ||
5 | 消息斷點 | 一個消息的發送中包含句柄和消息編號兩個參數 | 代碼段 |
6 | 條件斷點 | 當某個寄存器或堆棧段某處等於某值時中斷 | 寄存器、堆棧段 |
7 | 條件記錄斷點 |
函數斷點:
函數斷點指對調用某個win32 api函數(好比USER32.GetWindowTextA)的位置下斷點,操做步驟是使用ctrl+n快捷鍵調出Names窗口裏邊便是程序全部從dll導入的函數。找到要下斷點的函數(不支持搜索只能本身滾動查找,還算好的是按字母排序),而後在其上右鍵,點擊「Set breakpiont on every reference」此時用戶內存空間中全部call該函數(本質上是該函數的地址)的指令都會被設成int3斷點。刪除這些斷點同樣在函數上右鍵,選擇「delete all breakpoints」便可,固然因爲本質是int3斷點,因此也能夠直接打開斷點窗口進行管理。
在本文使用的示例程序中只有輸入給定的用戶名登陸才能使用,咱們這裏想無論輸入什麼都能進入使用。這裏就鎖定咱們的目標:逼近輸入文字被獲取並進行比較附近的代碼。
獲取文本框內容的函數是GetWindowTextA,ctrl+n呼出函數窗口,找到GetWindowTextA,右鍵選擇「Set breakpiont on every reference」
而後隨便在窗口輸入一些內容點擊登陸,好比我這裏輸「test」
而後程序就斷在了GetWindowTextA調用處
此時有不少call和jmp,比較代碼是哪不是很明顯,但比較代碼必定在從這個GetWindowText到彈出錯誤窗口的這段範圍內,咱們一邊按F8步過直到錯誤窗口彈出。最後看到以下
這樣咱們就鎖定了比較代碼應應該在00420185和0042021A2之間,不過因爲函數調用並非說比較代碼就在這段連續的區間內
咱們在0042021A2處下斷點,而後不斷go to Previous往前走看是從哪跳到0042021A2這邊來的(或者也能夠從新到00420185一步步日後走,看是哪跳到0042021A2這邊來的),通過步步查看後定位到是004020EB的jnz跳過去的,能夠猜想若是不跳那麼就正常進入程序
在該條語句上右鍵,選擇Binary----「Fill wtih NOPs」
再次使用test登陸,果然可進入程序主界面
IDA常常被稱爲用於靜態調試,所謂靜態調試就是不能運行代碼,解析你們都是能夠的因此不能運行代碼簡單能夠更解爲ida比od少了指令對應程序表現、棧變化、寄存器變化這幾項展示能力。
od使用各類窗口+斷點逼近目標代碼,ida使用同類窗口+交叉引用逼近目標代碼。ida力圖經過強化交叉引用能力來彌補斷點缺失形成的能力損失,但畢竟交叉引用是斷點的弱化版,因此動態調試效果應該來講會比靜態調試好,至於還須要靜態調試多是好比病毒等很差動態調試正如老虎不宜隨便放出籠來觀察同樣。
1----導航帶。整條就是0x00000000-0xffffffff線性地址定間,點哪主視圖窗口就顯示哪。
2-----函數窗口。程序本身的函數列表,雙擊函數就能夠去到其實義處。因爲編譯時不會保存原始函數名,因此若是沒有源代碼本程序的函數是列不出原始函數名的,只會用sub_xxxxxx形式表示(xxxxxx表示其地址,注意這裏說的是「若是沒有源代碼」,若是有源代碼好比你打開的是vc項目debug文件夾下的exe他就能列出原始函數名);至於ida能識別出系統函數名也不是由於保存有原始函數名而是由於ida能把系統函數彙編代碼與系統函數名對應上。另外注意這裏只是用戶自寫的或靜態連接入的系統函數,動態連接庫的函數在import窗口中。
3----反彙編窗口。和od的反彙編窗口相似,不過這裏能顯示非代碼段部分。
4----其餘窗口。包括十六進制窗口、導出窗口、導入窗口、結構體窗口、枚舉窗口、字符串窗口、名稱窗口、段窗口等,和od相比少了寄存器窗口和堆棧窗口其餘基本都是同樣。全部窗口均可以經過菜單----view----Open subviews來打開。
5---輸出窗口。ida輸出日誌的窗口。
咱們前面說過交叉引用裏斷點的弱化版,咱們這裏具體來看一下交叉引用長什麼樣。不過首先要再次說明動態調試也能夠有交叉引用,只是動態調試自己可運行程序,沒有很大必要去搞交叉引用而已。
引用----或者叫調用、使用,好比jmp、call等
交叉引用---既有正向從調用者地址跳轉到被調用者地址的連接,也具備反向從被調用者地址跳轉回調用者地址連接的引用。也叫交叉參考。
交叉引用又分爲代碼交叉引用和數據交叉引用,代碼交叉引用指的是代碼之間的調用與被調用,數據交叉引用指代碼與數據間的調用與被調用,使用上其實沒有什麼區別都是雙擊交叉引用連接就跳轉到其調用或被調用處。這裏只介紹交叉引用的各成分,具體長什麼樣見下節演示。
交叉引用會被用「;CODE XREF: .text:004020EB↑j」的格式標出,各成分解析以下:
;----註釋符號
CODE----表示是代碼交叉引用,若是是DATA則表示數據交叉引用。
XREF----交叉引用(Cross references)的縮寫
.text----表示其引用處在代碼段
004020EB----引用處的的地址,另外有可能會是_main+4等相對寫法都一個意思
↑----表示引用地址在當前地址上方,相應的↓就表示在當前地址的下方
j----在代碼交叉引用中能夠是j或p;j表示跳轉流jump,即jmp等跳轉指令的引用,在高級語言即if;p表示調用流procedure,即call指令的引用,在高級語言就是函數調用
在數據交叉引用中能夠是r或w,r表示讀引用,w表示寫引用
假設咱們使用和od同樣的思路,從GetWindowTextA函數入手,首先說由於不少地方均可能調用GetWindowTextA因此肯定哪一個調用GetWindowTextA的指令纔是登陸處調用的GetWindowTextA的指令是困難的。其次仍是因爲沒法運行咱們得逐條指令分析才能知道哪裏是彈出報錯窗口。因此函數入手思路肯定目標代碼的起始地址和結束地址都是困難的。
報錯時彈出了「好意思,和你不熟」語句咱們不防從該句入手。首先菜單----Search----text查找該字符串,找到其在0042f24c處,而後經過雙擊數據交叉引用,跳到到數據調用處。
再次經過代碼交叉引用跳轉到,代碼調用處。
觀察上下文,能夠推斷,只要該處不跳轉便可進入程序主界面
分析目標代碼關鍵是且只是本身知道怎麼寫代碼。逆向學彙編建議直接學32位彙編,不用學16位彙編也不用學win 32位彙編(固然也可能我能力不足經驗尚淺不解深意讀者本身判斷)。
雖說實模式是老祖宗,16位彙編的道理到32位仍是可用的,但32位彙編和16位彙編在尋址待方面指令寫法畢竟有區別,折騰半天學了16位彙編看32位彙編不必定很順,相似你掌握了c語言寫python也不必定很順。逆向出來的都是32位彙編因此感受能夠直接學32位彙編。
另外關於win32彙編,win32彙編就是可調用windows api可使用.if等僞指令的32位彙編。可是一是windows api彙編本就能夠調用;二是彙編/反彙編的難點就在棧操做、條件判斷跳轉和循環,如今彙編使用.invoke/.if/.while等僞指令把push、jnz和loop等指令隱藏起來而彙編是容易了,反彙編卻反彙編不出僞指令。因此我不能理解羅雲彬說的「win32彙編是windows彙編的不二選擇」是什麼意思,反而以爲win32彙編在給反彙編增長難度幫倒忙,至少我照着《Windows環境下32位彙編語言程序設計》寫了個記事本並無感受能更好讀懂32位彙編代碼(不過理解VC倒確實是頗有幫助)。