首先,我們需要知道如何定位到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
上面的紅圈部分的值需要着重熟悉一下,改完後再測試一下是否可以運行。