-
DOS头
- e_magic:最开头的两个字节4D5A是DOS签名,不可改
- e_lfanew:最后四个字节,小端序,所以80指向PE头的偏移,也就是开始位置

-
NT头
1 2 3 4 5typedef struct _IMAGE_NT_HEADERS { DWORD Signature; // 50 45 PE标识 IMAGE_FILE_HEADER FileHeader; // 标准PE头 IMAGE_OPTIONAL_HEADER32 OptionalHeader; ``// 扩展PE头 } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;-
Signature(4字节)
PE标识不可改 50 45
-
标准PE头 IMAGE_FILE_HEADER(20字节)
1 2 3 4 5 6 7 8 9typedef struct _IMAGE_FILE_HEADER { WORD Machine; // 可以运行在什么样的CPU上 WORD NumberOfSections; // 表示节的数量 DWORD TimeDateStamp; // 编译器填写的时间戳 DWORD PointerToSymbolTable; // 调试相关 DWORD NumberOfSymbols; // 调试相关 WORD SizeOfOptionalHeader; // 扩展PE头的大小,32位E0,64位F0 WORD Characteristics; // 文件属性,每个二进制位代表不同信息 } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;文件属性

-
扩展PE头 IMAGE_OPTIONAL_HEADER
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29//32位为例 typedef struct _IMAGE_OPTIONAL_HEADER32 { WORD Magic; // 标志:PE32 是 0x10B BYTE MajorLinkerVersion; // 链接器的主版本号 BYTE MinorLinkerVersion; // 链接器的次版本号 DWORD SizeOfCode; // 代码段的大小(没用) DWORD SizeOfInitializedData; // 初始化数据段的大小(没用) DWORD SizeOfUninitializedData;// 未初始化数据段的大小(没用) DWORD AddressOfEntryPoint; // 程序入口点的 RVA DWORD BaseOfCode; // 代码段的起始 RVA(没用) DWORD BaseOfData; // 数据段的起始 RVA(没用) DWORD ImageBase; // 内存镜像基址 DWORD SectionAlignment; // 内存对齐 DWORD FileAlignment; // 文件对齐 WORD MajorOperatingSystemVersion; // OS 主版本号 WORD MinorOperatingSystemVersion; // OS 次版本号 DWORD SizeOfImage; // 镜像的总大小 DWORD SizeOfHeaders; // 头+节表按照文件对齐后的总大小 DWORD CheckSum; // 校验和 WORD Subsystem; // 子系统(如 GUI、CUI) WORD DllCharacteristics; // DLL 属性 DWORD SizeOfStackReserve; // 堆栈保留大小 DWORD SizeOfStackCommit; // 堆栈提交大小 DWORD SizeOfHeapReserve; // 堆保留大小 DWORD SizeOfHeapCommit; // 堆提交大小 DWORD LoaderFlags; // 装载器标志(通常为 0) DWORD NumberOfRvaAndSizes; // 数据目录项数 IMAGE_DATA_DIRECTORY DataDirectory[16]; // 表,结构体数组 } IMAGE_OPTIONAL_HEADER32;-
重要字段
WORD Magic; 32位程序:10B;64位程序:20B
DWORD AddressOfEntryPoint; 程序入口 相对于加载到内存后起始地址的偏移,需要与ImageBase配合使用
**DWORD ImageBase; ** 内存镜像基址 **WORD DllCharacteristics;** 里可以选择是否为动态基址
-
-
**DWORD SectionAlignment; ** 内存对齐
DWORD FileAlignment; 文件对齐
DWORD SizeOfImage; 文件在内存中的大小,按SectionAlignment对齐后的大小
DWORD SizeOfHeaders; DOS头+NT头+标准PE头+可选PE头+区段头,按FileAlignment对齐后的大小
**DWORD CheckSum;**表示校验和,是用来判断文件是否被修改的,它的计算方法就是文件的两个字节与两个字节相加,最终的值(不考虑溢出情况)就是校验和。
DWORD NumberOfRvaAndSizes; 数据目录项数
IMAGE_DATA_DIRECTORY DataDirectory (数据⽬录) 指向数据⽬录中第⼀个 IMAGE_DATA_DIRECTORY 结构的指针。每个条⽬包含地址(RVA)和⼤⼩
|
|

-
RVA和FOA转换 RVA-对应区段RVA=FOA-对应区段的FOA
-
节表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15typedef struct _IMAGE_SECTION_HEADER { BYTE Name[8]; // 节名称(最多 8 字节,可能没有结束符) union { DWORD PhysicalAddress; // 实际已弃用,常为 0 DWORD VirtualSize; // 节的实际大小(内存中的大小) } Misc; DWORD VirtualAddress; // 节在内存中的 RVA(相对虚拟地址) DWORD SizeOfRawData; // 节在文件中的大小(以 FileAlignment 对齐) DWORD PointerToRawData; // 节在文件中的偏移(以 FileAlignment 对齐) DWORD PointerToRelocations; // 重定位表的文件偏移(通常为 0,已弃用) DWORD PointerToLinenumbers; // 调试信息的文件偏移(通常为 0) WORD NumberOfRelocations; // 重定位条目数量(通常为 0) WORD NumberOfLinenumbers; // 调试行号条目数量(通常为 0) DWORD Characteristics; // 节的属性标志(权限、类型等) } IMAGE_SECTION_HEADER;DWORD VirtualAddress; 节在内存中的偏移位置
DWORD PointerToRawData; 节在文件中的偏移
-
空白添加代码
-
扩大节
-
分配一块新的空间,大小为S
-
将最后一个节的SizeOfRawData和VirtualSize改为N
N=max(SizeOfRawData,VirtualSize)+S
-
修改SizeOfImage
-
修改节的属性使它可执行
-
-
新增节的步骤
- 判断是否有足够的空间,可以添加一个节表
- 在节表中新增一个成员
- 修改PE头中节的数量
- 修改SizeOfimage的大小
- 在原有数据的最后,新增一个节的数据(内存对齐的整数倍)
- 修正新增节表的属性
-
导出表,先拿到可选PE头,看最后一个字段数据目录表的第0项,存放导出表的偏移
1 2 3 4typedef struct _IMAGE_DATA_DIRECTORY{ DWORD VirtualAddresss; //内存中的偏移 DWORD Size; }IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;-
导出表结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; // 保留,恒为0x00000000(没有) DWORD TimeDateStamp; // 文件的产生时间戳(没有) WORD MajorVersion; // 主版本号(没有) WORD MinorVersion; // 次版本号(没有) DWORD Name; // 指向导出表文件名的RVA DWORD Base; // 导出函数的起始序号 DWORD NumberOfFunctions; // 导出函数总数(最大序号-最小序号+1) DWORD NumberOfNames; // 以名称导 出函数的总数 DWORD AddressOfFunctions; // 导出函数地址表的RVA DWORD AddressOfNames; // 函数名称地址表的RVA DWORD AddressOfNameOrdinals; // 函数名序号表的RVA } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;AddressOfFunctions,AddressOfNames,AddressOfNameOrdinals

已知名称,可从AddressOfNames获得相应索引,再通过该索引在AddressOfNameOrdinals找到函数偏移地址在AddressOfFunctions表中的索引。
-
-
导入表
-
前置知识
-
调用dll文件的原理
程序在调用dll文件函数时,并不是把dll文件函数的代码编译到当前文件中,而是把dll文件对应的函数地址保存到了当前文件中
在文件中,对应的函数地址部分存储的时函数名称
-
一个进程空间中exe dll文件如何被加载到内存
-
exe文件调用的动调链接库在内存中和硬盘中有什么不同
-
-
扩展PE头中数据目录存储的是第一个导入表的偏移,导入表有很多张,20个字节是一个,以20个0为结束
-
导入表结构体
1 2 3 4 5 6 7 8 9 10typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; // INT地址 }; DWORD TimeDateStamp; // 时间戳 DWORD ForwarderChain; // 转发链 DWORD Name; // DLL名称RVA DWORD FirstThunk; // IAT地址 } IMAGE_IMPORT_DESCRIPTOR;DWORD OriginalFirstThunk INT导入名称表地址
表中成员
1 2 3 4 5 6 7 8 9typedef struct _IMAGE_THUNK_DATA32 { union { DWORD ForwarderString; // PBYTE DWORD Function; // PDWORD DWORD Ordinal; DWORD AddressOfData; //RVA 指向_IMAGE_IMPORT_BY_NAME } u1; } IMAGE_THUNK_DATA32; typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
1 2 3 4typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; //可能为0,编译器决定,如果不为0,是函数在导出表中的索引 BYTE Name[1]; //函数名称,以0结尾,由于不知道到底多长,所以干脆只给出第一个字符,找到0结束 } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;DWORD FirstThunk IAT导入地址表地址
加载前是和INT一样,加载后存的是函数地址
-
-
重定位表 修正因为动调加载导致的地址错误
1 2 3 4 5typedef struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress; // 需重定位数据的起始RVA DWORD SizeOfBlock; // 本结构与TypeOffset总大小 } IMAGE_BASE_RELOCATION; typedef IMAGE_BASE_RELOCATION UNALIGNED IMAGE_BASE_RELOCATION;

高4位判断是否需要修改