PE結構-Nt頭部

首先,我們需要知道如何定位到Nt頭部,這個在上篇博客中Dos頭部的e_lfanew字段中,該字段可定位到Nt頭部

先來看一下這個32位的Nt結構

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature; //50 45 00 00 PE標誌
    IMAGE_FILE_HEADER FileHeader; //文件頭
    IMAGE_OPTIONAL_HEADER32 OptionalHeader; //選項頭
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

第一個4字節的PE標誌,不可改,沒啥好多說的了,看下面這個文件頭結構

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine; //在什麼硬件環境中運行
    WORD    NumberOfSections; //節的數量
    DWORD   TimeDateStamp; //時間
    DWORD   PointerToSymbolTable; //符號表位置
    DWORD   NumberOfSymbols; //符號個數
    WORD    SizeOfOptionalHeader; //選項頭大小
    WORD    Characteristics; //可執行文件屬性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

首先,我們需要重點關注前兩個和最後兩個(也就是首四字節和尾四字節)

第一個Word,表示在什麼硬件環境中執行,上面圖片的二進制位爲014C,表示Intel 386

#define IMAGE_FILE_MACHINE_I386              0x014c  // Intel 386.
#define IMAGE_FILE_MACHINE_IA64              0x0200  // Intel 64

第二個Word,表示節的數量,啥是節,在上一篇博客中有提及過,具體分析節在下一篇博客中,這裏我們需要知道的是,當我們需要遍歷循環節時,需要該字段用於條件判斷,切不可以全零爲節的結束。

第三個字段爲時間,表示可執行文件產生的時間,該時間可隨意修改,所以在一些數據取證的場合,該字段可做參考,不可信

第四和五字段爲符號表和符號表條目的總數,這個在後面的很多結構中都會有描述符號的字段,不過微軟並未使用(使用符號文件-pdb),爲何到處都有但又不使用呢? 當初微軟設計結構試想跨平臺,如蘋果,它的可執行文件和符號表是合二爲一的,所以需要考慮其他系統特性。

下一個選項頭總大小,在上一篇博客中提到過,該選項頭是不固定的,所以算是變長,那麼我們通過該字段知道了選項頭的大小,自然而然的就能定位到節表的位置了

IMAGE_DOS_HEADER.e_lfanew + sizeof DWORD (PE標誌) + 
        sizeof IMAGE_FILE_HEADER + IMAGE_FILE_HEADER.SizeOfOptionalHeader

最後一個是可執行文件的屬性,對照上面二進制,該值爲0x10F

該值是按位解析的,WORD也說明該值具有16種狀態,這裏就說一下10F的情況

#define IMAGE_FILE_RELOCS_STRIPPED           0x0001  // 無重定位信息
#define IMAGE_FILE_EXECUTABLE_IMAGE          0x0002  // 可執行文件
#define IMAGE_FILE_LINE_NUMS_STRIPPED        0x0004  // 無行信息
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED       0x0008  // 無符號信息
#define IMAGE_FILE_32BIT_MACHINE             0x0100  // 32位

//以下幾個還有常見的
#define IMAGE_FILE_LARGE_ADDRESS_AWARE       0x0020  // 用戶空間可 > 2g,可優化
#define IMAGE_FILE_SYSTEM                    0x1000  // 是否爲驅動(內核文件)
#define IMAGE_FILE_DLL                       0x2000  // 是否DLL

其餘的字段一般爲其他平臺準備的,可忽略。

OK,下面我們可以將一些可利用的抹爲0xCC,最後測試下能不能正常運行

看完文件頭後,我們下面來分析選項頭,爲何叫選項頭,這裏主要有三種情況


IMAGE_OPTIONAL_HEADER64 OptionalHeader; //32位
IMAGE_OPTIONAL_HEADER32 OptionalHeader; //64位
IMAGE_ROM_OPTIONAL_HEADER OptionalHeader; //嵌入式

這裏的話,我們就按照32位的來解析吧

在上圖中,選中藍色部分爲整個NT頭部結構,而用紅色框起來的部分則就是32位的選項頭了

typedef struct _IMAGE_OPTIONAL_HEADER {
    //
    // Standard fields.
    //

    WORD    Magic; //類型
    BYTE    MajorLinkerVersion; //鏈接器高低版本號
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode; //代碼總大小 .code
    DWORD   SizeOfInitializedData; //已初始化大小 .data .const
    DWORD   SizeOfUninitializedData; //未初始化大小 .data?
    DWORD   AddressOfEntryPoint; //入口點
    DWORD   BaseOfCode; //代碼基地址
    DWORD   BaseOfData; //數據基地址

    //
    // NT additional fields.
    //

    DWORD   ImageBase; //裝載應用程序基地址
    DWORD   SectionAlignment; //內存對齊
    DWORD   FileAlignment; //文件對齊
    WORD    MajorOperatingSystemVersion; //操作系統高低版本號
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion; //應用程序高低版本號
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion; //子系統高低版本號
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue; //win32版本
    DWORD   SizeOfImage; //鏡像在內存中多大
    DWORD   SizeOfHeaders; //可執行文件中頭部多大
    DWORD   CheckSum; //檢驗和
    WORD    Subsystem; //子系統
    WORD    DllCharacteristics; //DLL特性 按位解析
    DWORD   SizeOfStackReserve; //棧保留和提交
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve; //堆保留和提交
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags; //保留,操作系統不使用該字段
    DWORD   NumberOfRvaAndSizes; //數據目錄項數
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

選擇頭部分的字段還是比較龐大的,這裏先把數據目錄拋一邊,剩餘的字段我們來着重講一下不可忽略的字段

Magic字段,這裏就是上面所說的選項頭的三種情況

#define IMAGE_NT_OPTIONAL_HDR32_MAGIC      0x10b
#define IMAGE_NT_OPTIONAL_HDR64_MAGIC      0x20b
#define IMAGE_ROM_OPTIONAL_HDR_MAGIC       0x107

AddressOfEntryPoint字段,入口點,該值會定位到代碼段數據,需理性修改。在某些掛鉤和加密的場合,可修改該值先跑自己的代碼,代碼跑完後在跳回去,也就是OEP(原始入口點)

ImageBase字段,裝載應用程序基地址,該地址爲建議加載地址(前提是這個地址沒有佔用)。可以修改,但是需要注意的是修改完該字段,可能會引發一些重定位的問題,下面我們來嘗試着修改爲0x500000

將4修改爲5,修改完我們來跑一下試試,運行失敗,載入OD看一下

00501000 > $  6A 00         push    0
00501002   .  68 1D204000   push    40201D
00501007   .  68 10204000   push    402010
0050100C   .  6A 00         push    0
0050100E   .  E8 07000000   call    0050101A
00501013   .  6A 00         push    0
00501015   .  E8 06000000   call    00501020
0050101A   $  FF25 08204000 jmp     dword ptr [402008]

我們可以發現裝載地址的確發生了變化,但是觀察彙編代碼,裏面4xxxxx的值是不是應該都錯了呢,這裏就是需要重定位,因爲這個程序的量比較小,總共有四處,我們來手工修改一下,首先需要定位到代碼段,這裏因爲涉及到後面的知識,所以先直接給出答案,大家對照着修改即可,修改完後就能正常運行了

後面的SectionAlignment和FileAlignment分別對應着內存對齊值和文件對齊值,這個如何理解呢?

首先你可以這個PE結構宏觀上來看,就是一塊一塊的數據塊,那麼這個對齊值也就是相應的數據塊間的對齊值,假如將PE頭看成是一塊,實際數據爲0x201,那麼該PE結構的文件對齊值爲0x200,則下一數據塊則需要從0x400開始,因爲0x201會佔用2塊,雖然其中的一塊只有1字節。

後面的16字節爲版本號,這裏注意下,只有MajorSubsystemVersion字段不可省略,其餘都可抹去

下面就是SizeOfImage字段,表示該鏡像文件加載入內存後佔多大,該字段操作系統依賴,需與上面的內存對齊值參考填寫

後面就是SizeOfHeaders字段,該字段是表示在文件中,PE頭部佔多大,需與上面的文件對齊值參考填寫

下面重要點的就是Subsytem字段了,子系統

#define IMAGE_SUBSYSTEM_NATIVE               1   // 驅動
#define IMAGE_SUBSYSTEM_WINDOWS_GUI          2   // 界面程序
#define IMAGE_SUBSYSTEM_WINDOWS_CUI          3   // 控制檯程序

剩餘的就是堆棧的保留和提交信息了,保留表示最大可用數,而提交表示程序運行時就需要使用的值,這裏我們可理性的修改。

最後面就是數據目錄項數量和數據目錄項了,這裏的話另起博客講吧,這裏先暫時忽略

描述完字段後,我們就可以來改造一下了,將這些無用的字段都改爲0xCC

上面的紅圈部分的值需要着重熟悉一下,改完後再測試一下是否可以運行。

發佈了80 篇原創文章 · 獲贊 34 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章