PE文件格式:
PE文件格式:
PE文件是指32位可执行文件,也叫PE32,64位的是PE+或者PE32+
obj文件无法执行,dll和sys不能单独执行,可用其他方式(调试器、服务等·)
如何加载内存、从何处开始运行、运行时需要哪些dll文件、需要多大的堆、栈内存等,大部分都包含在PE头的结构体中
PE文件的基本结构:
PE头(DOS头到节区头)、PE体(其余部分)。
文件中使用偏移,内存中使用VA(虚拟地址)表示位置。文件的内容一般分为代码、数据、资源
各节区头定义了各节区在文件和内存的大小和、位置、属性等PE头与各节区尾部存在一个NULL区,因为文件和内存中节区的起始位置应该在各文件、内存最小单元的倍数位置上,所以剩余区域用NULL填充。
VA&RVA:
VA是指进程虚拟内存的绝对地址,RVA(虚拟地址)指以某一个基准位置(ImageBase)开始的相对地址。 RVA+ImageBase = VA
PE头:
DOS头:
最前面添加的是IMAGE_DOS_HEADER结构体,用来扩展DOS EXE头
这个结构体大小为40h个字节。我们知道其中两个就好。e_magic(DOS签名,4D5A对应ASCII是“MZ”)和e_lfanew(指示NT头的偏移量)
e_lfanew值指向NT头所在位置(NT头的名称为IMAGE_NT_HEADERS)
DOS存根:
即使没有,也能正常运行
32位的windows OS不会识别它(被识别为PE文件,所以忽略),在DOS环境识别为DOS EXE文件
NT头:
该结构体大小为F8
第一个成员是签名(其值为50450000h(‘PE’00))。宁外两个是文件头和可选头
文件头:
是表示文件大致属性的IMAGE_FILE_HEADER结构体
主要说四个:
Machine:
每个CPU都拥有唯一的Machine码,兼容32位intel x86芯片的Machine码为14C(在winnt.h文件中有宏定义)
NumberOfSections:
指出文件中存在的节区数,当定义节区与实际节区不同时,会发生运行错误
SizeOfOptionalHeader:
指出IMAGE_OPTIONAL_HEADER32结构体大小,因为PE32+与PE这个结构体大小不同,所以要指出
Characteristics:
该字段用于标识文件属性,文件是否是可运行形态、是否为dll文件等。在winnt.h中记住定义的0002h(文件可执行)和2000h这两个数(File is a dll)
TimeDateStamp:
记录编译器创建此文件的时间
可选头:
IMAGE_OPTIONAL_HEADER32是PE文件中最大的
Magic:
IMAGE_OPTIONAL_HEADER32的Magic为10B,IMAGE_OPTIONAL_HEADER64的Magic码为20B
AddressOfEntryPoint:
持有EP的RVA,指出文件最先执行的起始位置
ImageBase:
在进程虚拟内存中,指出文件优先装入的地址。EXE、DLL文件被装入用户内存,SYS被装入内核内存。一般EXE的ImageBase为00400000,DLL的ImageBase为10000000,文件载入内存后,EIP设为ImageBase+AddressOfEntryPoint
SectionAlignment、FileAlignment:
SectionAlignment指出了节区在内存中的最小单元。磁盘和内存中的节区大小必须为这两个值的整数倍
SizeOfImage:
指定了PE Image在虚拟内存中所占空间内存大小。文件大小一般与加载进入内存的大小是不相同的
SizeOfHeader:
指出整个PE头的大小,第一节区所在位置与SizeOfHeader距文件开始偏移的量相同
Subsystem:
用来区分系统驱动文件与普通可执行文件
1(Driver:系统驱动) 2 (GUI:窗口应用程序) 3(CUI:控制台应用程序)
NumberOfRvaAndSizes:
指定DataDirectory(结构体最后一个成员)数组的个数
DataDirectory:
数组的每一项都被定义了值
节区头:
定义了各节区的属性(文件/内存起始位置、大小、访问权限),PE文件中的code(代码)(执行,读取权限)、data(数据)(非执行,读写权限)、resource(资源)(非执行,读取权限)等按照属性分类存储在不同节区。
节区头是由IMAGE_SECTION_HEADER结构体组成的数组,每个结构体对应一个节区
VirtualSize:内存中节区所占大小
VirtualAddress:内存中节区起始位置(RVA)
SizeOfRawData:磁盘文件中节区所占大小
PointerToRawData:磁盘中文件起始位置
Characteristics:节区属性
VirtualSize和SizeOfRawData不带任何值,分别于SectionAlignment和FileAlignment确定
PE规范未明确规定节区的Name,所以可以向里面放任何值
RVA to RAW:
PE文件加载入内存,每个节区都要准确完成内存地址到文件偏移间的映射,这种映射称为RVA to RAW
RAW-PointerToRawData = RVA-VirtualAddress
IAT(导入地址表):
IAT是一种表格,记录程序正在使用哪些库·中的哪些函数
DLL:
加载dll方法有两种,一种是‘显示链接’(程序使用dll时加载,使用完毕后释放内存),一种是“隐式链接”(程序开始时即加载dll,终止时释放)。IAT提供的机制即与隐式链接有关。
IMAGE_IMPORT_DESCRIPTOR结构体记录着PE文件要导入哪些库文件
OriginalFirstThunk:INT的地址(RVA)
Name:库名称字符串地址(RVA)
FirstThunk:IAT的地址(RVA)
INT(Import Name Table(数组))与IAT是长整型,以 NULL结尾,两者大小应相同
INT各元素的值;为IMAGE_IMPORT_DESCRIPTOR结构体指针
DataDirectory[1].VirtualAddress的值即是IMAGE_IMPORT_DESCRIPTOR结构体数组的起始地址
库名称(Name):
Name是一个字符串指针,它指向导入函数所属的库文件名称
OriginalFirstThunk - INT:
INT是一个包含导入函数信息的结构体指针数组。只有获得这些信息,才能在加载到进程内存的库中准确求得相应函数的起始地址
IMAGE_IMPORT_BYNAME:
INT是IMAGE_IMPORT_BYNAME结构体指针数组
FirstThunk - IAT
普通的dll实际地址不会被硬编码到IAT,通常与INT有相同值
EAT(导出地址表):
EAT是一种核心机制,使不同应用程序可以调用库文件使用的函数。通过它可以准确求出相应库导出函数的起始地址
IMAGE_EXPORT_DIRECTORY结构体保存着导出信息
DataDirectory[0].VirtualAddress值即是这个结构体的起始位置
NumberOfFunctions:实际Export函数的个数
NumberOfNames:Export函数中具名的函数个数
AddressOfFunctions:Export函数地址数组
AddressOfNames:函数名称地址数组
AddressOfNameOrdinals:Ordinals地址数组
GetProcAddress引用EAT来获取指定API地址
对于没有函数名称的导出函数,可以通过Ordinal查找到他们的地址