PE檔案格式詳細解析(五)-- 除錯UPX壓縮的notepad程式

有毒發表於2020-03-08

PE檔案格式詳細解析(五)-- 除錯UPX壓縮的notepad程式

一、未經過UPX壓縮的notepad的EP程式碼

首先看一下未經過UPX壓縮的notepad的相關資訊:

  1. PEView檢視基本結構資訊:

    upx4

    RVA = 1000,且SizeOfRawData是有大小的。

  2. OD檢視EP程式碼:

    首先簡單看一下彙編程式碼,程式在010073b2處呼叫kernel32.dll中的GetModuleHandleA()函式,然後可以得到程式的ImageBase,存放在EAX中:

upx8

 

然後,進行PE檔案格式的驗證,比較MZ和PE簽名。

 

upx7

 

以上程式碼可以簡單記錄一下,方便後續與經過UPX壓縮的程式進行比較。

二、經過UPX壓縮的notepad_upx.exe的EP程式碼

  1. PEView檢視下資訊(上一節已經介紹過):

    第一個圖為第一個節區UPX0的資訊,第二個圖為第二個節區UPX1的資訊。

    upx5

    upx6

  2. OD進行EP程式碼檢視:

    upx9

    可以發現經過UPX壓縮的EP程式碼發生了明顯的改變,入口地址變為了01014410,該地址其實為第二個節區UPX1的末尾地址(使用PEView可以確認),實際壓縮的原始碼位於該地址的上方。

    然後我們看一下程式碼開始部分:

    01014410        60                        pushad
    01014411        BE 00000101        mov esi, notepad_.01010000
    01014416        8DBE 0010FFFF    lea esi, dword ptr ds:[esi-0xf000]
    

    首先看第一句,pushad,其主要作用將eax~edi暫存器的值儲存到棧中:

upx10

 

結合上面的圖,發現在執行完pushad指令後,eax~edi的值確實都儲存到了棧中。

 

後面兩句分別把第二個節區的起始地址(01010000)與第一個節區的起始地址(01001000)存放到esi與edi暫存器中。UPX檔案第一節區僅存在於記憶體中,該處即是解壓縮後儲存原始檔程式碼的地方。

 

需要注意的是,在除錯時同時設定esi與edi,大機率是發生了esi所指緩衝區到edi所指緩衝區的記憶體複製。此時從Source(esi)讀取資料,解壓縮後儲存到Destination(edi)。

三、跟蹤UPX檔案

掌握基本資訊後,開始正式跟蹤UPX檔案,需要遵循的一個原則是,遇到迴圈(loop)時,先了解作用再跳出,然後決定是否需要再迴圈內部單步除錯。

 

備註:此處開始使用書上的例子,因為我個人的反彙編的程式碼會跟書上不一致,不建議新手使用。

1. 第一個迴圈

在EP程式碼處執行Animate Over(Ctrl+F8)命令,開始跟蹤程式碼:

 

upx11

 

跟蹤到這裡後發現第一個關鍵迴圈,涉及到edi的反覆變化,迴圈次數為36b,主要作用是從edx(01001000)中讀取一個位元組寫入edi(01001001)。edi所指的地址即是第一個節區UPX0的起始地址(PEView已經驗證過),僅存於記憶體中,資料全部被填充為NULL,主要是清空區域,防止有其他資料。這樣的迴圈我們跳出即可,在010153e6處下斷點,然後F9跳出。

2. 第二個迴圈

在斷點處繼續Animate Over跟蹤程式碼,遇到下圖的迴圈結構:

 

upx12

 

該村換是正式的解壓縮迴圈。

 

先從esi所指的第二個節區(UPX1)地址中依次讀取資料,然後經過一系列運算解壓縮後,將資料放入edi所指的第一個節區(UPX0)地址。關鍵指令解釋:

0101534B   .  8807          mov byte ptr ds:[edi],al
0101534D   .  47            inc edi                                  ;  notepad_.0100136C
...
010153E0   .  8807          mov byte ptr ds:[edi],al
010153E2   .  47            inc edi                                  ;  notepad_.0100136C
...
010153F1   .  8907          mov dword ptr ds:[edi],eax
010153F3   .  83C7 04       add edi,0x4
* 解壓縮後的資料放在AL(eax)中,edi指向第一個節區的地址

在01015402地址處下斷,跳出迴圈(暫不考慮內部壓縮過程)。在轉儲視窗檢視解壓縮後的程式碼:

 

upx13

3. 第三個迴圈

重新跟蹤程式碼,遇到如下迴圈:

 

upx14

 

這部分程式碼主要是恢復原始碼的CALL/JMP指令(機器碼:E8/E9)的destination地址。

 

到此為止,基本恢復了所有的壓縮的原始碼,最後設定下IAT即可成功。

4. 第四個迴圈

01015436處下斷:

 

upx15

 

此處edi被設定為01014000,指向第二個節區(UPX1)區域,該區域中儲存著原程呼叫的API函式名稱的字串。

 

upx16

 

UPX在進行壓縮時,會分析IAT,提取出原程式中呼叫的額API名稱列表,形成api函式名稱字串。

 

使用這些API名稱字串呼叫01015467地址處的GetProcAddress()函式,獲取API的起始地址,然後把API地址輸入ebx暫存器所指的原程式的IAT區域,迴圈進行,直到完全恢復IAT。

 

然後,到01054bb的jmp指令處,跳轉到OEP(原始EP)程式碼處:

 

upx17

 

至此,UPX的解壓縮全部完成,後續進行notepad.exe的正常執行。

五、快速查詢UPX OEP的方法

1. 在POPAD指令後的JMP指令處設定斷點

UPX壓縮的特徵之一是其EP程式碼被包含在PUSHAD/POPAD指令之間,並且在POPAD指令之後緊跟著的JMP指令會跳轉到OEP程式碼處,所以可以在此處下斷點,直接跳轉到OEP地址處。

2. 在棧中設定硬體斷點

本質上也是利用 PUSHAD/POPAD指令的特點。因為eax~edi的值依次被儲存到棧中,不管中間做了什麼操作,想要執行OEP的程式碼就需要從棧中讀取這些暫存器的值來恢復程式的原始執行狀態,所以我們只要設定硬體斷點監視棧中暫存器的值的變化就可以快速定位到OEP。

 

F8執行完pushad後,在od的dump視窗進入棧地址:

 

upx18

 

然後選中下硬體讀斷點:

 

upx19

 

直接F9,你會發現很快就來到PUSHAD後的JMP指令處。

 

最後,補充硬體斷點的幾個知識:硬體斷點是CPU支援的斷點,最多設定4個;執行完指令後再停止。

相關文章