PE文件格式详解,第二讲,NT头文件格式,以及文件头格式
PS:本篇博客默认你已经有了汇编基础,所以会使用32位汇编编写最小PE进行讲解
今天详解NT 头格式,以及文件头格式,以及作用, 关于DOS头文件格式,以及DOSStub昨天的博客已经写过了.主要是分散讲解.便于理解.
一丶最小PE的生成,以及标准PE的生成
ps: (如果直接学习NT头,文件头,请不用看这个生成PE,直接看下面讲解即可)
1.标准PE的生成
为了便于学习PE文件格式,所以这里写出一个最小PE,还有一个最小的标准PE,让大家理解.
32位汇编编写.(汇编是能编写最小PE的)
首先我们先写一段基本的汇编代码,然后一层一层的优化
32位汇编代码:
代码语言:javascript复制.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主要是分区太多.所以去掉.
怎么去掉? 只需要把上面的汇编代码修改一下即可.
修改为:
代码语言:c复制.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头:
代码语言:javascript复制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各个位置内容.
下面模版自动点击则可以解析
三丶文件头
文件头结构体:
代码语言:javascript复制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中已经给出了.
代码:
代码语言:javascript复制#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
文件属性是按照位来的.
什么意思?
先看下宏定义:
代码语言:javascript复制#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位程序,是一个可执行程序
总的来说很简单,主要是熟练运用,在不使用工具的前提下,明白各个位置代表的作用