PE文件结构及其加载机制

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

PE⽂件结构及其加载机制
⼀、PE⽂件结构
PE即Portable Executable,是win32环境⾃⾝所带的执⾏体⽂件格式,其部分特性继承⾃Unix的COFF(Common Object File Format)⽂件格式。

PE表⽰该⽂件格式是跨win32平台的,即使Windows运⾏在⾮Intel的CPU上,任何Win32平台的PE装载器也能识别和使⽤该⽂件格式的⽂件。

所有Win32执⾏体(除了VxD和16位的DLL)都使⽤PE⽂件格式,如EXE⽂件、DLL⽂件等,包括NT的内核模式驱动程序(Kernel Mode Driver)。

PE⽂件⾄少包含两个段,即数据段和代码段。

Windows NT 的应⽤程序有9个预定义的段,分别为 .text 、.bss 、.rdata 、.data 、.pdata 和.debug 段,这些段并不是都是必须的,当然,也可以根据需要定义更多的段(⽐如⼀些加壳程序)。

在应⽤程序中最常出现的段有以下6种:
.执⾏代码段,通常 .text (Microsoft)或 CODE(Borland)命名;
.数据段,通常以 .data 、.rdata 或 .bss(Microsoft)、DATA(Borland)命名;
.资源段,通常以 .rsrc命名;
.导出表,通常以 .edata命名;
.导⼊表,通常以 .idata命名;
.调试信息段,通常以 .debug命名;
PE⽂件的结构在磁盘和内存中是基本⼀样的,但在装⼊内存中时⼜不是完全复制。

Windows装载器在装载的时候仅仅建⽴好虚拟地址和PE ⽂件之间的映射关系,只有真正执⾏到某个内存页中的指令或访问某⼀页中的数据时,这个页才会被从磁盘提交到物理内存。

但因为装载可执⾏⽂件时,有些数据在装⼊前会被预先处理(如需要重定位的代码),装⼊以后,数据之间的相对位置也可能发⽣改变。

因此,⼀个节的偏移和⼤⼩在装⼊内存前后可能是完全不同的。

..
PE的基本结构就是这样了。

下⾯开始各个部分学习。

==================================================
(1)IMAGE_DOS_HEADER和Dos Stub
其实IMAGE_DOS_HEADER和Dos Stub没有什么重要的,只是IMAGE_DOS_HEADER中的第⼗九个成员指向IMAGE_NT_HEADERS的位置。

typedef struct IMAGE_DOS_HEADER
{
WORD e_magic;
WORD e_cblp;
WORD e_cp;
WORD e_crlc;
WORD e_cparhdr;
WORD e_minalloc;
WORD e_maxalloc;
WORD e_ss;
WORD e_sp;
WORD e_csum;
WORD e_ip;
WORD e_cs;
WORD e_lfarlc;
WORD e_ovno;
WORD e_res[4];
WORD e_oemid;
WORD e_oeminfo;
WORD e_res2[10];
DWORD e_lfanew; //指向IMAGE_NT_HEADERS的所在
}IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
下⾯我们⽤⼀个具体的PE⽂件来看看。

这是64个字节(64Byte),下⾯是⼀些分析。

⾸先,只要是PE⽂件,那么开始两个字节就⼀定是4D 5A。

然后看最后四个字节,是E8000000,代表了地址是000000E8h,我们往下找找。

通过搜索,可以直接到达这个位置,⼀会我们再来看这个位置。

也就是说,00000040h到000000E8h之间的数据都是Dos Stub(称为dos残余程序)。

-----------------------------------------------------------------------------
(2)PE⽂件头
现在我们来看看IMAGE_NT_HEADERS的情况,看看e_lfanew指向的这⾥是什么含义,看看000000E8h(这个数值是不固定的,不同的PE程序的值可能不同,我们只要找到这个位置,读取它的值即可找到PE⽂件的头所在)指向的这⾥⼜是什么。

typedef struct IMAGE_NT_HEADERS
{
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
}IMAGE_NT_HEADERS,*PIMAGE_NT_HEADERS;
⾸先我们看到的,这是⼀个结构体,⽽且结构体中还有结构体。

我们先看看它的总⼤⼩和在c32中的情况
【1】其中红⾊的部分就是DWORD Signature,表⽰字符“PE\0\0”,⼗六进制为00004550h,这个值也是不会变化的。

【2】绿⾊线部分表⽰IMAGE_FILE_HEADER FileHeader,我们具体来看看这个结构体代表了什么含义。

typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //运⾏平台
WORD NumberOfSections; //块(section)数⽬
DWORD TimeDateStamp; //时间⽇期标记
DWORD PointerToSymbolTable; //COFF符号指针,这是程序调试信息
DWORD NumberOfSymbols; //符号数
WORD SizeOfOptionalHeader; //可选部⾸长度,是IMAGE_OPTIONAL_HEADER的长度
WORD Characteristics; //⽂件属性
}
第⼀个表⽰这个程序运⾏需要的平台,来看看我刚才这个程序的值
这⾥表⽰运⾏的平台是Intel的CPU,下⾯是⼀个列表代表各种对应的平台的值:
第⼆个表⽰块的数⽬
0004当然就是4个节了,⼀会我们可以看看是哪四个节
第三个表⽰时间,我们先看看
也就是4F91318Fh,⽤计算器算⼀下就是1334915471,这个值应该是秒,我们换算⼀下,结果⼤约是42年
这个是程序的创建⽇期,减去42年,⼤约就是1970年,这个⽇期就是从1970到⽂件最后修改时间之间的秒数?这个我可不知道,有待研究。

第六个SizeOfOptionalHeader表⽰之后OptionalHeader的⼤⼩,我们先来看看
00E0就是⼗进制的224,也就是说OptionalHeader的⼤⼩是224字节。

第七个值Characteristics表⽰⽂件属性,它的每⼀个bit都代表了某种含义。

Bit 0 :置1表⽰⽂件中没有重定向信息。

每个段都有它们⾃⼰的重定向信息。

这个标志在可执⾏⽂件中没有使⽤,在可执⾏⽂件中是⽤⼀个叫做基址重定向⽬录表来表⽰重定向信息的,这将在下⾯介绍。

Bit 1 :置1表⽰该⽂件是可执⾏⽂件(也就是说不是⼀个⽬标⽂件或库⽂件)。

Bit 2 :置1表⽰没有⾏数信息;在可执⾏⽂件中没有使⽤。

Bit 3 :置1表⽰没有局部符号信息;在可执⾏⽂件中没有使⽤。

Bit 4 :
Bit 7
Bit 8 :表⽰希望机器为32位机。

这个值永远为1。

Bit 9 :表⽰没有调试信息,在可执⾏⽂件中没有使⽤。

Bit 10:置1表⽰该程序不能运⾏于可移动介质中(如软驱或CD-ROM)。

在这种情况下,OS必须把⽂件拷贝到交换⽂件中执⾏。

Bit 11:置1表⽰程序不能在⽹上运⾏。

在这种情况下,OS必须把⽂件拷贝到交换⽂件中执⾏。

Bit 12:置1表⽰⽂件是⼀个系统⽂件例如驱动程序。

在可执⾏⽂件中没有使⽤。

Bit 13:置1表⽰⽂件是⼀个动态链接库(DLL)。

Bit 14:表⽰⽂件被设计成不能运⾏于多处理器系统中。

Bit 15:表⽰⽂件的字节顺序如果不是机器所期望的,那么在读出之前要进⾏
交换。

在可执⾏⽂件中它们是不可信的(操作系统期望按正确的字节顺序执⾏程序)。

010Fh就是0000000100001111
具体的我们来看⼀张图:
【3】下⾯是OptionalHeader,占224个字节
我们看看它的结构是怎样的
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;
同样的,我们先在msdn中查看下这个结构体
第⼀个值,表⽰⽂件的格式,它可能的值有
IMAGE_NT_OPTIONAL_HDR_MAGIC,这个值包括两个值,分别为IMAGE_NT_OPTIONAL_HDR32_MAGIC、
IMAGE_NT_OPTIONAL_HDR64_MAGIC,分别表⽰是32位的应⽤程序和64位的应⽤程序,具体的⼗六进制值为0x10b和0x20b。

还有⼀个值是IMAGE_ROM_OPTIONAL_HDR_MAGIC,表⽰这是⼀个ROM⽂件,其值为0x107。

总结为⼀张图
对于这个⽂件,它的值是
表⽰32位的应⽤程序。

第⼆个值是MajorLinkerVersion,表⽰主版本号的链接器,这个值⼀般来说是不重要的。

对于这个⽂件,它的值为
第三个值是MinorLinkerVersion,表⽰次版本号的连接器,这个值⼀般来说是不重要的。

它的值
第四个值是SizeOfCode,表⽰The size of the code section, in bytes, or the sum of all such sections if there are multiple code sections.
代码段的总⼤⼩,单位为字节,如果是多个部分,则表⽰它们的总和。

94208字节,即92kb。

第五个值是SizeOfInitializedData,表⽰所有含已初始化数据的节的⼤⼩。

The size of the initialized data section, in bytes, or the sum of all such sections if there are multiple initialized data sections.
77824字节,76kb。

第六个值是SizeOfUninitializedData,表⽰未初始化数据的节的⼤⼩。

The size of the uninitialized data section, in bytes, or the sum of all such sections if there are multiple uninitialized data sections.
第七个值是AddressOfEntryPoint,表⽰程序的⼊⼝点。

A pointer to the entry point function, relative to the image base address. For executable files, this is the starting address. For device drivers, this is the address of the initialization function. The entry point function is optional for DLLs. When no entry point is present, this member is zero.
我们⽤PEiD来看看这个值
如果看过《C++反汇编与逆向分析技术揭秘》的童鞋就会知道,PEiD是如何⼯作的。

第⼋个值是BaseOfCode,表⽰代码段的起始RVA。

即RVA为00001000,如果加上ImageBase的话,就可以知道这个位置在内存中的位置为00401000,我们⽤OD打开这个程序看看
第九个值是BaseOfData,表⽰数据段的起始RVA。

同样,我们⽤OD来查看下,00018000+00400000=00418000
第⼗个值是ImageBase,表⽰程序的建议装载地址。

⽤PEiD查看
第⼗⼀个值SectionAlignment,表⽰节对齐粒度。

这个值⼀定要⼤于或等于⽂件对齐粒度。

The alignment of sections loaded in memory, in bytes. This value must be greater than or equal to the FileAlignment member.
The default value is the page size for the system.
原来是4096B,也就是4kb了。

第⼗⼆个值是FileAlignment,表⽰⽂件对齐粒度。

The alignment of the raw data of sections in the image file, in bytes. The value should be a power of 2 between 512 and 64K (inclusive). The default is 512. If the SectionAlignment member is less than the system page size, this member must be the same as SectionAlignment.
这个值应该是512B的倍数。

第⼗三个值是MajorOperatingSystemVersion,所需操作系统的主版本号。

The major version number of the required operating system.
第⼗四个值是MinorOperatingSystemVersion,所需操作系统的副版本号。

The minor version number of the required operating system.
第⼗五个值是MajorImageVersion
The major version number of the image.
第⼗六个值是MinorImageVersion
The minor version number of the image.
第⼗七个值是MajorSubsystemVersion
The major version number of the subsystem.
第⼗⼋个值为MinorSubsystemVersion
The minor version number of the subsystem.
第⼗九个值为Win32VersionValue,保留值,且必须为零。

This member is reserved and must be 0.
第⼆⼗个值为SizeOfImage,4个字节,表⽰程序调⼊后占⽤内存⼤⼩(字节),等于所有段的长度之和。

The size of the image, in bytes, including all headers. Must be a multiple of SectionAlignment.
0x2B000=?
好吧,两三天了,终于弄明⽩这个值了,由于在实验过程中,为了防⽌意外,所以复制了⼀个副本在当前⽂件夹下,通过⼆进制的对⽐,发现这两个⽂件的SizeOfImage值是不⼀样的,所以⾛了弯路。

既然错了⽂件,那么我还是以这个⽂件为例吧,因为其他的部分都⼀样,所以就不修改其他的部分了。

0x25000+0x5188=0x2A188,再考虑内存对齐,我们试着⽤这个值除以对齐粒度0x1000,看是否能除尽。

结果是不能除尽,所以要求⼤⼀点,结果这个SizeOfImage就变成了0x2B000。

第⼆⼗⼀个值为SizeOfHeaders,占⽤4个字节,表⽰所有头加节表的⼤⼩。

The combined size of the following items, rounded to a multiple of the value specified in the FileAlignment member.
4 byte signature
size of
size of optional header
size of all section headers
也就是0x1000了。

第⼆⼗⼆个值为CheckSum,占⽤四个字节。

The image file checksum. The following files are validated(验证) at load time: all drivers, any DLL loaded at boot time, and any DLL loaded into a critical (关键)system process.
第⼆⼗三个值为Subsystem,占⽤两个字节。

表⽰⽂件运⾏所需的⼦系统。

The subsystem required to run this image. The following values are defined.
Value Meaning
IMAGE_SUBSYSTEM_UNKNOWN
0Unknown subsystem.
IMAGE_SUBSYSTEM_NATIVE
1No subsystem required (device drivers and native system processes). IMAGE_SUBSYSTEM_WINDOWS_GUI
2Windows graphical user interface (GUI) subsystem.
IMAGE_SUBSYSTEM_WINDOWS_CUI
3Windows character-mode user interface (CUI) subsystem.
IMAGE_SUBSYSTEM_OS2_CUI
5OS/2 CUI subsystem.
IMAGE_SUBSYSTEM_POSIX_CUI
7POSIX CUI subsystem.
IMAGE_SUBSYSTEM_WINDOWS_CE_GUI
9Windows CE system.
IMAGE_SUBSYSTEM_EFI_APPLICATION
10Extensible Firmware Interface (EFI) application.
IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER
11EFI driver with boot services.
IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER
EFI driver with run-time services.
12
EFI driver with run-time services.
IMAGE_SUBSYSTEM_EFI_ROM
13EFI ROM image.
IMAGE_SUBSYSTEM_XBOX
14Xbox system.
IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION
16Boot application.
第⼆⼗四个值为DllCharacteristics,占⽤两个字节。

表⽰dll⽂件的属性值。

The DLL characteristics of the image. The following values are defined.
Value Meaning 0x0001Reserved.(保留)
0x0002Reserved.
0x0004Reserved.
0x0008Reserved.
IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 0x0040The DLL can be relocated at load time.(允许在载⼊的时候进⾏重定位)
IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY 0x0080Code integrity checks are forced. If you set this flag and a section contains only uninitialized data, set the PointerToRawData member of for that section to zero; otherwise, the image will fail to load because the digital signature cannot be verified.
IMAGE_DLLCHARACTERISTICS_NX_COMPAT 0x0100The image is compatible(兼容) with data execution prevention (DEP).
IMAGE_DLLCHARACTERISTICS_NO_ISOLATION
0x0200The image is isolation(隔离) aware, but should not be isolated.
IMAGE_DLLCHARACTERISTICS_NO_SEH 0x0400The image does not use structured exception handling (SEH). No handlers can be called in this image.
IMAGE_DLLCHARACTERISTICS_NO_BIND
0x0800Do not bind the image.
0x1000Reserved.
IMAGE_DLLCHARACTERISTICS_WDM_DRIVER
0x2000 A WDM driver.
0x4000Reserved.
IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE
0x8000The image is terminal server aware.
第⼆⼗五个值为SizeOfStackReserve,占⽤4个字节。

表⽰初始化是的堆栈⼤⼩。

The number of bytes to reserve for the stack. Only the memory specified by the SizeOfStackCommit member is committed at load time; the rest is made available one page at a time until this reserve size is reached.
0x00100000=1MB
第⼆⼗六个值为SizeOfStackCommit,占⽤四个字节。

表⽰初始化时实际提交的堆栈⼤⼩。

The number of bytes to commit for the stack.
0x1000字节=4kb
第⼆⼗七个值为SizeOfHeapReserve,占⽤四个字节。

初始化时保留堆的⼤⼩。

The number of bytes to reserve for the local heap. Only the memory specified by the SizeOfHeapCommit member is committed at load time; the rest is made available one page at a time until this reserve size is reached.
第⼆⼗⼋个值为SizeOfHeapCommit,占⽤四个字节。

初始化时实际提交的堆得⼤⼩。

The number of bytes to commit for the local heap.
第⼆⼗九个值为LoaderFlags,占⽤4个字节。

未使⽤。

This member is obsolete.
第三⼗个值为NumberOfRvaAndSizes,占⽤四个字节。

表⽰下⾯个成员数据⽬录结构的数量。

这个值⼀般就直接是16.
下⾯是最后⼀个成员DataDirectory,占⽤128个字节,为⼀个IMAGE_DATA_DIRECTORY structure结构体数组(16个)。

A pointer to the first structure in the data directory.
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
这个结构体有两个成员,⼀个成员占⽤4个字节,也就是8个字节。

这个数组有16个数据,也就是16*8=128字节。

我们来看第⼀个。

IMAGE_DIRECTORY_ENTRY_EXPORT 导出表
这个程序没有导出函数,所以没有导出表。

第⼆个IMAGE_DIRECTORY_ENTRY_IMPORT 导⼊表
这个程序需要⽤到dll中的函数
我们⽤PEiD来查看下
结果是⼀样的。

这个是RVA,表⽰偏移地址哦。

第三个IMAGE_DIRECTORY_ENTRY_RESOURCE 资源⽬录
从上⾯这张图也可以看出。

RVA为00025000,⼤⼩为5188byte
第四个IMAGE_DIRECTORY_ENTRY_EXCEPTION 异常⽬录
未使⽤。

第五个 IMAGE_DIRECTORY_ENTRY_SECURITY 安全⽬录
第六个 IMAGE_DIRECTORY_ENTRY_BASERELOC 重定位表
第七个 IMAGE_DIRECTORY_ENTRY_DEBUG 调试信息
第⼋个 IMAGE_DIRECTORY_ENTRY_COPYRIGHT 版权信息
第九个 IMAGE_DIRECTORY_ENTRY_GLOBALPTR
第⼗个 IMAGE_DIRECTORY_ENTRY_TLS 线程的本地存储器
第⼗⼀个 IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 载⼊配置⽬录Load configuration table address and size
第⼗⼆个 IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 绑定导⼊表地址和⼤⼩Bound import table address and size
第⼗三个 IMAGE_DIRECTORY_ENTRY_IAT 导⼊函数地址表Import Address Table Import address table address and size
⽤Exeinfo PE 查看
第⼗四个 IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT
Delay import descriptor address and size
第⼗五个 IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR
The CLR header address and size
第⼗六个 IMAGE_NUMBEROF_DIRECTORY_ENTRIES 保留值
到此,整个PE⽂件头结束了。

下⾯我们开始学习节表。

不知道还记不记得在前⾯哪个结构体中出现过节的数量?
嘿嘿,忘记了吧,我们翻开以前的记录,看看。

原来是
typedef struct IMAGE_NT_HEADERS
{
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
}IMAGE_NT_HEADERS,*PIMAGE_NT_HEADERS;
中的
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //运⾏平台
WORD NumberOfSections; //块(section)数⽬
DWORD TimeDateStamp; //时间⽇期标记
DWORD PointerToSymbolTable; //COFF符号指针,这是程序调试信息
DWORD NumberOfSymbols; //符号数
WORD SizeOfOptionalHeader; //可选部⾸长度,是IMAGE_OPTIONAL_HEADER的长度 WORD Characteristics; //⽂件属性
}
第⼆个成员就是了。

我们回去找找这个程序的这个值是多少。

原来是四个节啊,当然了,也可以说四个段。

果然是四个段。

好了,复习完需要的知识,我们就继续学习。

================================================
typedef struct_IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union{
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress;
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
同样的,这也是⼀个结构体,⽽且有⼏个节,就有⼏个这种类似的结构体。

"#define IMAGE_SIZEOF_SHORT_NAME 8" 原来是个8
分类:
⼆、导⼊表与IAT
(⼀)、导⼊表简介
在编程中常常⽤到“导⼊函数”(Import functions),导⼊函数就是被程序调⽤但其执⾏代码⼜不在程序中的函数,这些函数的代码位于⼀个或者多个DLL中,在调⽤者程序中只保留⼀些函数信息,包括函数名及其驻留的DLL名等。

于磁盘上的PE ⽂件来说,它⽆法得知这些输⼊函数在内存中的地址,只有当PE ⽂件被装⼊内存后,Windows 加载器才将相关DLL 装⼊,并将调⽤输⼊函数的指令和函数实际所处的地址联系起来。

这就是“动态链接”的概念。

动态链接是通过PE ⽂件中定义的“导⼊表”来完成的,导⼊表中保存的正是函数名和其驻留的DLL 名等。

1.调⽤导⼊函数的指令
程序被执⾏的时候是怎样使⽤导⼊函数的呢?我们来对⼀个简单的弹出⼀个MessageBox的程序反汇编⼀把,看看调⽤导⼊函数的指令都是什么样⼦的.
灰常简单的⼀个⼩程序,如图双击程序只显⽰⼀个对话窗⼝,然后就结束~试验⽤⼩程序,我们尽量的将内部的结构删减,调试起来才⽅便些。

我们这次体验的⽬的就是想靠所学的知识,试图来找到MessageBox 在内存中的地址。

注:MessageBox 是来⾃于USER32.DLL 动态链接库⾥的⼀个函数,我们通过对PE ⽂件的静态反编译分析来观察hello.exe 这个试验品是如何定位和调⽤MessageBox 这个在USER32.dll的函数的。

(MessageBox 有两个版本,⼀个是MessageBoxA 还有⼀个是MessageBoxW 分别代表ASCII码形式和UNICODE~)
需要反汇编的两句源码如下:
invoke MessageBox,NULL,offset szText,offset szCaption,MB_OK
invoke ExitProcess,NULL
我们直接对其反汇编:
反汇编后,对MessageBox和ExitProcess函数的调⽤变成了对0040101A和00401020地址的调⽤,但是这两个地址显然是位于程序⾃⾝模块⽽不是DLL模块中,实际上,这是由编译器在程序所有代码的后⾯⾃动加上的Jmp dword ptr[xxxxxxxx]类型的指令,这个指令时⼀个间接寻址的跳转指令,xxxxxxxx地址中存放的才是真正的导⼊函数的地址。

在这个例⼦中,00402000地址处存放的就是ExitProcess函数的地址。

那在没有装载到内存前,PE⽂件中的00402000地址处的内容⼜是什么呢?我们来分析⼀下它的节表。

由于建议装⼊地址是00400000h,所以00402000地址实际上处于RVA为2000h的地⽅,再看看各个节的虚拟地址,可以发现2000h开始的地⽅位于.rdata节内,⽽这个节的Raw_偏移为600h,也就是00402000h的内容实际上对应于PE⽂件中偏移600h处的数据。

我们再来看看⽂件0600h处的内容是什么:
查看的结果是0002076h,这显然不是内存中的ExitProcess函数的地址。

不过,我们将它作为RVA看会怎么样?RVA地址00002076h也处于.rdata节内,减去节的其实地址00002000h后得到这个RVA相对于节⾸的偏移是76h,也就是对应⽂件0676h开始的地⽅,接下来会惊奇地发现,0676h再过去两个字节的内容正是函数名字符串“ExitProcess”!
是不是感觉有点不对?
如果我告诉你,当PE⽂件被装载的时候,Windows装载器会根据xxxxxxxx处的RVA得到函数名,再根据函数名在内存中找到函数地址,并且⽤函数地址将xxxxxxx处的内容替换成真正的函数地址,那么所有的疑惑就迎刃⽽解了。

接下来看看如何获取导⼊表的位置。

2.获取导⼊表的位置
导⼊表的位置和⼤⼩可以从PE⽂件中IMAGE_OPTIONAL_HEADER32结构的数据⽬录字段中获取。

从IMAGE_OPTIONAL_DIRECTORY 结构的VirtualAddress字段得到的是导⼊表的RVA值,如果在内存中查找导⼊表,那么将RVA值加上PE⽂件装⼊的基址就是实际的地址,如果在PE⽂件中查找导⼊表,那么需要使⽤上⼀篇中讲的将RVA转换成⽂件偏移的⽅法进⾏转换。

(⼆)、导⼊表的结构
1.PE⽂件中的导⼊表
导⼊表由⼀系列的IMAGE_IMPORT_DESCRIPTOR结构组成,结构的数量取决于程序要使⽤的DLL⽂件的数量,每⼀个结构对应⼀个DLL ⽂件,在所有这些结构的最后,由⼀个内容全为0的IMAGE_IMPORT_DESCRIPTOR结构作为结束。

IMAGE_IMPORT_DESCRIPTOR结构的定义:
IMAGE_IMPORT_DESCRIPTOR STRUCT
union
Characteristics DWORD ?
OriginalFirstThunk DWORD ?
ends
TimeDateStamp DWORD ?
ForwarderChain DWORD ?
Name1 DWORD ?
FirstThunk DWORD ?
IMAGE_IMPORT_DESCRIPTOR ENDS
①Name1
它表⽰DLL 名称的相对虚地址(译注:相对⼀个⽤null作为结束符的ASCII字符串的⼀个RVA,该字符串是该导⼊DLL⽂件的名称,如:KERNEL32.DLL)。

②OriginalFirstThunk和FirstThunk
现在可以看成是相同的(现在),它们都指向⼀个包含⼀系列IMAGE_THUNK_DATA结构的数组,数组中的每个IMAGE_THUNK_DATA结构定义了⼀个导⼊函数的信息,数组最后以⼀个内容为0的IMAGE_THUNK_DATA结构作为结束。

⼀个IMAGE_THUNK_DATA结构实际上就是⼀个双字,之所以把它定义成结构,是因为它在不同时刻有不同的含义:
IMAGE_THUNK_DATA STRUC
union u1
ForwarderString DWORD ? ; 指向⼀个转向者字符串的RVA
Function DWORD ? ; 被输⼊的函数的内存地址
Ordinal DWORD ? ; 被输⼊的API 的序数值
AddressOfData DWORD ? ; 指向 IMAGE_IMPORT_BY_NAME
ends
IMAGE_THUNK_DATA ENDS
当 IMAGE_THUNK_DATA 值的最⾼位为 1时,表⽰函数以序号⽅式输⼊,这时候低 31位被看作⼀个函数序号。

(读者可以⽤预定义值IMAGE_ORDINAL_FLAG32或80000000h来对最⾼位进⾏测试)
当 IMAGE_THUNK_DATA 值的最⾼位为 0时,表⽰函数以字符串类型的函数名⽅式输⼊,这时双字的值是⼀个 RVA,指向⼀
IMAGE_IMPORT_BY_NAME 结构。

IMAGE_IMPORT_BY_NAME STRUCT
Hint WORD ?
Name1 BYTE ?
IMAGE_IMPORT_BY_NAME ENDS
结构中的 Hint 字段也表⽰函数的序号,不过这个字段是可选的,有些编译器总是将它设置为 0,Name1字段定义了导⼊函数的名称字符串,这是⼀个以 0 为结尾的字符串。

我们来看⼀个例⼦:
2.内存中的导⼊表
为什么需要两个⼀样的IMAGE_THUNK_DATA 数组呢?
当PE⽂件被装⼊内存的时候,其中⼀个数组的值将被改作他⽤,正如上⾯分析的,Windows装载器会将指令Jmp dword ptr[xxxxxxxx]指定的xxxxxxxx处的RVA替换成真正的函数地址,其实xxxxxxx地址正是FirstThunk字段指向的那个数组的⼀员。

实际上,当PE⽂件被装⼊内存后,内存中的映象就被Windows装载器修正成了下图的样⼦,其中由FirstThunk字段指向的那个数组中的每个双字都被替换成了真正的函数⼊⼝地址,之所以在PE⽂件中使⽤两份IMAGE_THUNK_DATA 数组的拷贝并修改其中的⼀份,是为了最后还可以留下⼀份拷贝⽤来反过来查询地址所对应的导⼊函数名。

3.导⼊地址表(IAT)
暂把上⾯FirstThunk指向的真正导⼊函数地址数组称为导⼊地址数组。

在PE⽂件中,所有DLL对应的导⼊地址数组是被排列在⼀起的,全部这些数组的组合也被称为导⼊地址表(Import Address Table),导⼊表中第⼀个IMAGE_IMPORT_DESCRIPTOR结构的FirstThunk字段指向的就是IAT的起始地址。

也可以通过数据⽬录表的第13项找到IAT数据块的位置和⼤⼩。

转载于:。

相关文档
最新文档