啥是PE結構?簡單來說,就是將代碼和數據按照一定的約定進行存放,爲進程的創建做準備。
微軟當初在設計PE結構時,目標是設計成多平臺的,所以裏面很多字段考慮了跨平臺因數,當然現在來看,這個跨平臺...en..就不用多說啥了。
先來了解一下PE結構的大概情況
IMAGE_DOS_HEADER //DOS頭
IMAGE_NT_HEADERS //NT頭
DWORD Signature //PE 標誌
IMAGE_FILE_HEADER //文件頭
IMAGE_OPTIONAL_HEADER //選項頭
IMAGE_SECTION_HEADER //數據節區描述 多個
...
IMAGE_SECTION_HEADER
SECTION_DATA //節數據
...
SECTION_DATA
user data //附加數據 不屬於PE範疇
上面大致看看分佈即可
先來說說選項頭,有些視頻或者書籍會翻譯成可選頭,只是需要注意的是這裏的IMAGE_OPTION_HEADER是不能省略的,一個字段都不能,只是這個結構是有多種選項的
IMAGE_OPTIONAL_HEADER64 OptionalHeader; //32位
IMAGE_OPTIONAL_HEADER32 OptionalHeader; //64位
IMAGE_ROM_OPTIONAL_HEADER OptionalHeader; //嵌入式
然後再來說一說數據節區,位於NT頭後,有幾個節數據(理解爲數據塊即可),就會有對應多少個節區描述。那麼描述數據塊至少需要兩個信息,數據塊在哪裏(地址),多大(size),具體後面博客再寫吧。
最後面有個附加數據,這裏的附加數據相對於是用戶數據,也就是額外數據,使用vc6編譯一個debug版exe,就會發現該exe會附帶附加數據,該數據爲pdb文件位置
瞭解完大體的概貌後,下面可以動手來分析了,爲了簡單起見,這裏使用匯編來寫代碼,這樣編譯出來的exe相對學習較容易
.386
.model flat,stdcall
option casemap:none
include windows.inc
include user32.inc
include kernel32.inc
includelib user32.lib
includelib kernel32.lib
.const
g_szHello db 'Hello World!',0
g_szTitle db 'PE',0
.code
START:
invoke MessageBox,NULL,offset g_szHello,offset g_szTitle,MB_OK
invoke ExitProcess,0
end START
手動編譯
D:\masm32\bin\ml /c /coff hello.asm
D:\masm32\bin\link /subsystem:windows hello.obj
這樣子編譯出來的exe只有2kb大小。
這篇博客先來討論下Dos頭以及後面的Stub數據,使用WinHex打開
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
WORD e_magic; // Magic number
WORD e_cblp; // Bytes on last page of file
WORD e_cp; // Pages in file
WORD e_crlc; // Relocations
WORD e_cparhdr; // Size of header in paragraphs
WORD e_minalloc; // Minimum extra paragraphs needed
WORD e_maxalloc; // Maximum extra paragraphs needed
WORD e_ss; // Initial (relative) SS value
WORD e_sp; // Initial SP value
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value
WORD e_cs; // Initial (relative) CS value
WORD e_lfarlc; // File address of relocation table
WORD e_ovno; // Overlay number
WORD e_res[4]; // Reserved words
WORD e_oemid; // OEM identifier (for e_oeminfo)
WORD e_oeminfo; // OEM information; e_oemid specific
WORD e_res2[10]; // Reserved words
LONG e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
WORD e_magic; // MZ 標誌
//... 58字節
LONG e_lfanew; // 新結構(微軟定義結構)偏移地址 PE
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
上面是Dos結構,其中前面部分是完整的,總共佔0x40字節,我們只有重點關係下面部分即可(首尾),中間的是運行時基本環境中需要的一些初始化的信息(如寄存器初始值),因爲在dos程序,才需要關注配置這些相關信息,所以32位中忽略即可
剩餘的那部分數據就是stub數據,也就是Dos殘留數據,作用是當這個32位程序真的運行在Dos環境下時,那麼程序將會從40處開始執行(代碼數據混合)
seg000:0040 ; ---------------------------------------------------------------------------
seg000:0040 push cs
seg000:0041 pop ds
seg000:0042 mov dx, 0Eh
seg000:0045 mov ah, 9
seg000:0047 int 21h ; DOS - PRINT STRING
seg000:0047 ; DS:DX -> string terminated by "$"
seg000:0049 mov ax, 4C01h
seg000:004C int 21h ; DOS - 2+ - QUIT WITH EXIT CODE (EXIT)
seg000:004C ; AL = exit code
seg000:004C ; ---------------------------------------------------------------------------
所以這部分地方也是沒用的,這裏的沒用是針對程序而言,事實上這部分地方解釋爲可利用的更好,至於寫啥東西就隨意了。
OK,我們可以將上面可利用處都填寫爲0xCC,剩餘都是需要特別關注的點
修改完後重新跑一下程序,測試是否能正常運行!