0%

逆向工程核心原理(二)

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查找到他们的地址