PE文件格式詳解,第二講,NT頭文件格式,以及文件頭格式
作者:IBinary
出處:http://www.cnblogs.com/iBinary/
版權所有,歡迎保留原文鏈接進行轉載:)
PS:本篇博客默認你已經有了匯編基礎,所以會使用32位匯編編寫最小PE進行講解
今天詳解NT 頭格式,以及文件頭格式,以及作用, 關於DOS頭文件格式,以及DOSStub昨天的博客已經寫過了.主要是分散講解.便於理解.
一丶最小PE的生成,以及標准PE的生成
ps: (如果直接學習NT頭,文件頭,請不用看這個生成PE,直接看下面講解即可)
1.標准PE的生成
為了便於學習PE文件格式,所以這里寫出一個最小PE,還有一個最小的標准PE,讓大家理解.
32位匯編編寫.(匯編是能編寫最小PE的)
首先我們先寫一段基本的匯編代碼,然后一層一層的優化
32位匯編代碼:
.386 .model flat,stdcall option casemap:none include windows.inc include user32.inc include kernel32.inc ;包含各種lib庫以及頭文件 includelib user32.lib includelib kernel32.lib .data g_szHello db "Hello",0dh,0ah,00 ;定義Hello字符串 .code start: invoke MessageBoxA,NULL,offset g_szHello ,NULL,MB_OK ;彈出信息框 invoke ExitProcess,0 ;退出程序 end start
很簡單的匯編代碼
看下EXE的大小,以及內容
2.50KB有點大了.
可以繼續優化,但是比如手動敲命令行了.注意,這里使用的masm32的link連接器
首先我們要去掉分區,因為這里的EXE主要是分區太多.所以去掉.
怎么去掉? 只需要把上面的匯編代碼修改一下即可.
修改為:
.386 .model flat,stdcall option casemap:none include windows.inc include user32.inc include kernel32.inc includelib user32.lib includelib kernel32.lib .code g_szHello db "Hello",0dh,0ah,00 ;將數據段的數據,放到代碼區中 start: invoke MessageBoxA,NULL,offset g_szHello ,NULL,MB_OK invoke ExitProcess,0 end start
很簡單,就是把.data去掉即可.
這個就是標准PE了,看下文件大小.
注意一下,這里我使用的是RadAsm集成開發環境,
編譯器是Masm32的link連接器. 如果使用VC6.0以及以上的,文件會變的很大,可能會有16.KB,28.KB,不利於大家學習.如果不會配置RadAsm集成開發環境,請參考以前的帖子.自己配置一下即可.
2.最小PE的生成
區合並和內存對齊優化,生成最小PE(不通用)
首先我們要知道PE中的區在哪里,以及怎么使內存對齊縮小,不至於讓PE很大.
首先看下我們的標准PE格式的二進制.(使用Winhex,或者010 Editor都可以)
可以看出,生成的時候默認會為我們生成.const常量區,那么我們可以讓它和代碼區合並嗎?
注意,如果是別的程序,是不可以合並的,因為常量區很有用,但是如果生成最小PE那么你需要合並,
最后一個Hello的位置,則是代碼區
手工連接,使其合並分區,變為最小PE
命令行參數
/ALIGN:內存對齊(2的倍數即可,默認是4096)
/MERGE: 區 = 區 (合並分區)
例如link加上 寫成下面這樣
/ALIGN:4 /MERGE:.rdata=.text
手工編譯連接看下.
不過這樣寫還要另外加選項,不能保證她是否是能運行,不通用,所以使用標准pe講解
二丶NT頭
首先看下NT頭和文件頭的結構體.
NT頭:
typedef struct _IMAGE_NT_HEADERS { DWORD Signature; //4個字節的PE標志 IMAGE_FILE_HEADER FileHeader; //文件頭 IMAGE_OPTIONAL_HEADER32 OptionalHeader;//可選頭 } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
NT 頭第一個成員,對應的是PE位置,4個字節.
內存分布圖:
在這里,建議大家使用010編輯器,可以使用自定義PE模版,解析PE各個位置內容.
下面模版自動點擊則可以解析
三丶文件頭
文件頭結構體:
typedef struct _IMAGE_FILE_HEADER { WORD Machine; //機器型號,作用是區別這個exe是哪個CPU可以跑的.重要. WORD NumberOfSections; //節的數量 (可以理解為匯編中區的個數)現在我們有兩個,一個.rdata 一個.text DWORD TimeDateStamp; //程序的編譯時間,參考用,沒有實際作用 DWORD PointerToSymbolTable; //符號表地址 我們使用的PDB文件(里面有函數嗎什么的)都存放在這個表中,不過微軟是單獨生成的PDB文件,所以這個字段沒用,主要是給別人用 DWORD NumberOfSymbols; //符號表大小 WORD SizeOfOptionalHeader; //可選頭大小,這個字段很重要.因為要通過這個字段,才知道可選頭是多大,而不懂PE的人求選項頭都是用sizeof()求出來的.所以真正的選項頭大小要靠這個字段 WORD Characteristics; //文件屬性,描述文件信息的. } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
上面只是簡單的寫了下各個成員的作用.
在這里需要注意的是 可選頭大小,文件屬性,以及機器型號. 其余的自己看看.
1.機器型號:
機器型號,在PE中的定義,在VC++6.0中已經給出了.
代碼:
#define IMAGE_FILE_MACHINE_UNKNOWN 0 #define IMAGE_FILE_MACHINE_I386 0x014c // Intel 386. #define IMAGE_FILE_MACHINE_R3000 0x0162 // MIPS little-endian, 0x160 big-endian #define IMAGE_FILE_MACHINE_R4000 0x0166 // MIPS little-endian #define IMAGE_FILE_MACHINE_R10000 0x0168 // MIPS little-endian #define IMAGE_FILE_MACHINE_WCEMIPSV2 0x0169 // MIPS little-endian WCE v2 #define IMAGE_FILE_MACHINE_ALPHA 0x0184 // Alpha_AXP #define IMAGE_FILE_MACHINE_POWERPC 0x01F0 // IBM PowerPC Little-Endian #define IMAGE_FILE_MACHINE_SH3 0x01a2 // SH3 little-endian #define IMAGE_FILE_MACHINE_SH3E 0x01a4 // SH3E little-endian #define IMAGE_FILE_MACHINE_SH4 0x01a6 // SH4 little-endian #define IMAGE_FILE_MACHINE_ARM 0x01c0 // ARM Little-Endian #define IMAGE_FILE_MACHINE_THUMB 0x01c2 #define IMAGE_FILE_MACHINE_IA64 0x0200 // Intel 64 #define IMAGE_FILE_MACHINE_MIPS16 0x0266 // MIPS #define IMAGE_FILE_MACHINE_MIPSFPU 0x0366 // MIPS #define IMAGE_FILE_MACHINE_MIPSFPU16 0x0466 // MIPS #define IMAGE_FILE_MACHINE_ALPHA64 0x0284 // ALPHA64
看下PE中怎么存儲的.
按照小尾方式,則是 0x014C ,那么對應上面的宏則是386的程序(看注釋),而我們的匯編編譯出來的標准PE也正是標准PE,如果學習PE,自己可以去看下PE存儲
2.可選頭大小
這個地方是我計算偏移得出,根據結構體的類型大小,可以自己計算偏移得出.
可以看出,可選頭的大小是0x00E0 大小,換算成10進制就可以知道,E0是224字節大小,所以根據這個,可以計算出可選頭大小
3.文件屬性
文件屬性緊跟在E0 00 后面,它是0F 01
文件屬性是按照位來的.
什么意思?
先看下宏定義:
#define IMAGE_FILE_RELOCS_STRIPPED 0x0001 // Relocation info stripped from file. #define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 // File is executable (i.e. no unresolved externel references). #define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 // Line nunbers stripped from file. #define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 // Local symbols stripped from file. #define IMAGE_FILE_AGGRESIVE_WS_TRIM 0x0010 // Agressively trim working set #define IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020 // App can handle >2gb addresses #define IMAGE_FILE_BYTES_REVERSED_LO 0x0080 // Bytes of machine word are reversed. #define IMAGE_FILE_32BIT_MACHINE 0x0100 // 32 bit word machine. #define IMAGE_FILE_DEBUG_STRIPPED 0x0200 // Debugging info stripped from file in .DBG file #define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400 // If Image is on removable media, copy and run from the swap file. #define IMAGE_FILE_NET_RUN_FROM_SWAP 0x0800 // If Image is on Net, copy and run from the swap file. #define IMAGE_FILE_SYSTEM 0x1000 // System File. #define IMAGE_FILE_DLL 0x2000 // File is a DLL. #define IMAGE_FILE_UP_SYSTEM_ONLY 0x4000 // File should only be run on a UP machine #define IMAGE_FILE_BYTES_REVERSED_HI 0x8000 // Bytes of machine word are reversed.
首先按照小尾方式查看.
0x010f
那么先看第一個1,不看后面的,找百位為1的那么就是 0x100 在上面則可以找到對應的宏,它的注釋是:
32 bit word machine. 代表了他是一個32位程序
那么看個位是F,那么就找F,但是需要注意,他因為是位運算,所以是 | 連接起來了,那么F 代表了
0x0001 | 0x0002 | 0x0004 | 0x0008 那么分別就對應前4個宏
那么最終想要表示的結果是
32 bit word machine.
Relocation info stripped from file.
File is executable (i.e. no unresolved externel references)
Line nunbers stripped from file
Local symbols stripped from file.
翻譯過來就是 這是一個32位程序,是一個可執行程序....
那么訓練一下,我隨便寫一個
0x2102
那么 按照 個 十 百 千 位去尋找
先找千位 0x2000 // File is a DLL. 說明這是一個DLL文件
再找百位 0x100 // 32 bit word machine. 說明是一個32位可執行程序
再找十位 十位為零,則沒有.
再找個位 0x0002 // File is executable (i.e. no unresolved externel references). 說明是一個可執行程序
那么總結一下,說明了這個文件是一個 DLL文件,是一個32位程序,是一個可執行程序
總的來說很簡單,主要是熟練運用,在不使用工具的前提下,明白各個位置代表的作用
作者:IBinary
出處:http://www.cnblogs.com/iBinary/
版權所有,歡迎保留原文鏈接進行轉載:)