UPack的PE文件頭分析與OEP查找

UPack(Ultimate PE壓縮器),是一種PE文件的運行時壓縮器,特點是使用獨特的方法對PE頭進行變形。UPack會使許多的PE分析程序無法正常運行,因此很多惡意代碼都是通過UPack進行壓縮。

 

UPack PE文件頭分析

使用UPack壓縮notepad.exe:

UPack會直接壓縮源文件而不會進行備份。壓縮後的notepad文件更名爲notepad_upack。

使用PEView嘗試能否正常查看:

 

可以看到,新版的PEView也無法正常讀取PE頭信息,沒有讀取到NT頭和節區頭等信息。

 

使用Stud_PE工具查看PE頭信息:

Stud_PE可以讀取UPack壓縮後的PE文件信息。

 

 

比較PE文件頭:

用Win Hex打開正常的notepad.exe文件:

 

可以看到,這是典型的PE頭格式,依次的順序爲IMAGE_DOS_HEADER、DOS Stub、IMAGE_NT_HEADERS、IMAGE_SECTION_HEADER。

用Win Hex打開使用UPack壓縮後的notepad文件:

 

可以看到,MZ與PE簽名離得很近,並且沒有了DOS存根,出現了大量字符串,中間夾雜着一些代碼。

 

分析UPack的PE文件頭:

1、重疊文件頭:

重疊文件頭是壓縮器常用的技法,可以把MZ文件頭(IMAGE_DOS_HEADER)與PE文件頭(IMAGE_NT_HEADERS)重疊在一起,可有效節約文件頭的空間。

使用Stud_PE查看MZ文件頭(Headers選項卡的Basic HEADERS tree view in hexeditor):

 

可以看到,該DOS頭確實含有e_magic和e_lfanew兩個重要成員,其餘的成員對程序運行並不會產生影響。

根據PE文件格式規範,IMAGE_NT_HEADERS的起始位置是可變的,即由e_lfanew的值確定,在一般的PE文件中,e_lfanew = DOS頭大小(40) + DOS存根大小(可變,VC++的爲A0) = E0。這裏可看到UPack中e_lfanew的值爲10,其並不違反PE規範,使得DOS頭與NT頭得以整合到一起。

 

2、IMAGE_FILE_HEADER.SizeOfOptionalHeader

修改IMAGE_FILE_HEADER.SizeOfOptionalHeader的值,可以向文件頭插入解碼代碼。

SizeOfOptionalHeader表示PE文件頭中IMAGE_OPTIONAL_HEADER結構體的長度(E0)。UPack將該值修改爲148:

 

增大SizeOfOptionalHeader的值後,就在IMAGE_OPTIONAL_HEADER和IMAGE_SECTION_HEADER之間添加了額外的空間,從而UPack可在此區域添加解碼代碼。

觀察IMAGE_OPTIONAL_HEADER的起始地址:

 

發現IMAGE_OPTIONAL_HEADER也是緊跟在IMAGE_FILE_HEADER之後,其起始地址爲28。

由此可以推算出IMAGE_SECTION_HEADER的起始地址爲:28 + 148 = 170。

因此,偏移地址D7~170之間的區域即爲額外添加的空間(IMAGE_OPTIONAL_HEADER結尾的位置爲D7,具體的分析由下一部分得出):

 

 

3、IMAGE_OPTIONAL_HEADER.NumberOfRvaAndSizes

整個IMAGE_DATA_DIRECTORY結構體數組如圖:

NumberOfRvaAndSizes用來指出緊接在後面的IMAGE_DATA_DIRECTORY結構體數組的元素個數,正常文件中其值爲10,這裏UPack修改爲了A個:

改變了NumberOfRvaAndSizes的值後,使用Ollydbg打開該文件會彈出如下框,主要是Ollydbg會檢查PE文件的NumberOfRvaAndSizes值是否爲10:

IMAGE_DATA_DIRECTORY結構體數組的元素個數已經被確定爲10,但PE規範將NumberOfRvaAndSizes值作爲數組元素的個數,也就是說,IMAGE_DATA_DIRECTORY結構體數組的後6個元素被忽略,即從LOAD_CONFIG項開始(文件偏移D8以後)不再使用,UPack就將這塊被忽視的IMAGE_DATA_DIRECTORY區域覆寫自己的代碼:

 

由此可以確定IMAGE_OPTIONAL_HEADER結尾的位置爲D7。

 

4、IMAGE_SECTION_HEADER

IMAGE_SECTION_HEADER結構體中,UPack會將自身數據記錄到程序運行不需要的項目。

由上述知,IMAGE_SECTION_HEADER的起始地址爲170,節區數爲3個即有3個IMAGE_SECTION_HEADER結構體,則整個節區頭大小爲3 * 28 = 78,則IMAGE_SECTION_HEADER結束的偏移爲170 + 78 - 1 = 1E7,因此IMAGE_SECTION_HEADER結構體區域爲偏移170~1E7:

 

其中一些結構體成員對程序運行無意義。

 

5、重疊節區

UPack的主要特徵之一是可以任意重疊PE節區與文件頭。

通過Stud_PE查看UPack的IMAGE_SECTION_HEADER:

 

可以看到,第一節區和第三節區的文件偏移都是10,且大小都是一樣的。UPack會對PE文件頭、第一節區、第三節區進行重疊。

文件頭(包括第一節區和第三節區)區域的大小爲200(由第二節區的起始地址得出),第二節區的大小爲AE28,佔據了文件的大部分區域,原文件壓縮於此。

內存中的第一節區的大小爲1000,與原文件的Size of Image具有相同的值,即壓縮在第二節區中的文件映像會被原樣解壓縮到第一節區。原notepad.exe擁有3個節區,都被解壓到第一節區。

 

6、導入表(IMAGE_IMPORT_DESCRIPTOR數組)

先查看Data Directory Table中的IDT(IMAGE_IMPORT_DESCRIPTOR結構體數組)的地址:

 

如上,前4個字節爲導入表的地址(RVA),後4個字節爲導入表的大小(Size)。

在使用Win Hex查看之前,需要進行RVA到RAW的變換。首先需要確定該RVA是屬於哪個節區,內存地址271EE在內存中是第三個節區:

 

則RAW = RVA - VirtualAddress + PointerToRawData = 271EE - 27000 + 0 = 1EE

注意:第三節區的PointerToRawData 的值不是10而是會被強制變換爲0。

這裏說一下RVA轉換爲RAW的問題。

各種PE實用程序對UPack無法正常處理的原因在於無法正確地進行RVA到RAW的轉換。

以計算EP的文件偏移量RAW爲例,UPack的EP是RVA 1018:

 

由上述知,RVA 1018位於第一節區,第一節區的VirtualAddress 爲1000、PointerToRawData爲10,則有公式計算RAW如下:

RAW = RVA - VirtualAddress + PointerToRawData = 1018 - 1000 + 10 = 28

用Win Hex到RAW 28處查看:

 

可以看到,該區域保存的是API名稱而並非是代碼區域。

出現上述情況的根源在於PointerToRawData。一般而言,指向節區起始地址的文件偏移PointerToRawData值應該是FileAlignment的整數倍。UPack的FileAlignment值爲200,其PointerToRawData值應該爲0、200、400、600等200的整數倍的值。當PE裝載器發現第一節區的PointerToRawData值(10)並非FileAlignment(200)的整數倍時,會強制將其識別爲整數倍,即設置PointerToRawData值爲0,從而使得UPack得以正常運行。

因而,正確的計算公式應該如下:

RAW = RVA - VirtualAddress + PointerToRawData = 1018 - 1000 + 0 =18

查看該RAW地址的內容:

再到Ollydbg中的EP代碼中確認是否一致:

 

可以看到,是一致的。

 

接着使用Win Hex查看文件偏移1EE處的內容:

 

根據PE規範,導入表是由一系列IMAGE_IMPORT_DESCRIPTOR結構體組成的數組,最後由一個內容爲NULL的結構體結束。上述區域爲IMAGE_IMPORT_DESCRIPTOR結構體數組,1EE~201位第一個結構體,後面既無第二個結構體也無NULL結構體,這明顯是違反PE規範的。但可以看到在偏移200上方的劃線,其表示文件中第三節區的結束,因此運行時偏移在200以下的部分不會映射到第三節區內存中。

當第三節區加載到內存時,文件偏移0~1FF的區域映射到內存的27000~271FF區域,而27200~28000區域(第三節區其餘的內存區域)全部填充爲NULL。

 

7、導入地址表

分析IMAGE_IMPORT_DESCRIPTOR結構體的內容,可知OriginalFirstThunk(INT)爲0(RVA),Name爲2(RVA),FirstThunk(IAT)爲11E8(RVA):

查看Name,可知其屬於Header區域,在該區域RAW即RVA,在偏移爲2的區域看到KERNEL32.DLL,原本該區域爲DOS頭不使用的區域,UPack將Import DLL名稱寫入了該處:

 

接着查看INT。一般而言,跟蹤OriginalFirstThunk(INT)能夠查看得到API名稱字符串,但此時UPack的OriginalFirstThunk(INT)爲0,轉而跟蹤查看FirstThunk(IAT)也可以查看得到API名稱字符串。

IAT的RVA值爲11E8,則其RAW = RVA - VirtualAddress + PointerToRawData = 11E8 - 1000 + 0 = 1E8

注意:第一節區的PointerToRawData被強制轉換爲0而不是10。

用Win Hex打開到1E8處查看:

 

其中導入了兩個API(RVA 28和BE),結束是NULL。

查看這兩個API,分別爲LoadLibraryA()和getProcAddress():

 

調試查找UPack OEP

 

明確目標爲調試查找UPack壓縮的notepad_upx.exe文件的OEP。

由於UPack將IMAGE_OPTIONAL_HEADER中的NumberOfRvaAndSizes值設置爲了A(默認爲10),導致Ollydbg打開notepad_upx.exe文件時會彈出錯誤消息框:

上述這個錯誤導致Ollydbg無法轉到EP處,而是停留在其他區域:

 

這是由於Ollydbg的Bug造成的,此時需要強制設置EP。

用Stud_PE查看EP的VirtualAddress:

 

可以看到,ImageBase爲01000000,EP的RVA爲1018,經過計算可知EP的RVA爲01001018。

Ollydbg中轉到該地址,右鍵>New origin here,再點擊確定即可設置新的EP代碼:

 

 

1、解碼循環:

所有壓縮器中都存在解碼循環。在調試到這樣的解碼循環時,在恰當的時候應當跳出條件分支語句以跳出循環。

UPack將壓縮後的數據放在第二節區,然後運行解碼循環將這些數據解壓縮後放到第一節區。

從EP處開始調試:

 

前兩條指令將010011B0地址處的4個字節(0100739D)保存到EAX中,該值是原notepad.exe的OEP。若已經知道該值爲OEP,則可直接設置硬件斷點再F9運行至OEP處停止即可。

LODS指令是從DS:[ESI]讀取DWORD大小的數據放入EAX寄存器中。

接着調試至遇到如下CALL指令(該處函數爲decode函數,可從後續調試中不斷調用該函數來推斷):

此時ESI的值爲0101FCCB,進入該地址略微查看一下:

從這部分代碼還沒看出該函數是幹嘛的,接着F7運行進入該函數進行調試,直至遇到如下部分代碼:

 

框中的兩處指令爲向EDI寄存器所指位置寫入內容,其中REP指令將字節內容從ESI移到EDI中,STOS指令將EAX內容保存到EDI中。此時EDI指向第一節區中的地址。這些命令會執行解壓縮操作,然後寫入內存中。直至EDI的值爲01014B5A時,解碼循環結束,地址0101FE61處即使解碼循環的結束部分。

 

2、設置IAT:

一般而言,壓縮器執行完解碼循環後,會根據原文件重新組織IAT。

繼續向下調試一段時間後,遇到如下兩條CALL指令,分別爲調用LoadLibraryA() API和GetProcAddress() API,據此可推斷這段代碼爲重新設置IAT:

最後是RETN指令,返回到0100739D地址,即OEP:

至此,已成功調試查找到了UPack壓縮的notepad程序的OEP。

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