首页
社区
课程
招聘
PE文件解析基础
发表于: 2023-8-12 10:44 13565

PE文件解析基础

2023-8-12 10:44
13565

硬盘文件->加载到内存(FileBuffer)->PE Loader加载并拉伸->ImageBuffer(起始位置ImageBase)

从0x00~0x3f共0x40字节固定大小

e_magic: pe指纹 "MZ"

e_lfanew: pe头的偏移

其他成员无关紧要

从dos头到pe头之间是dos存根

dos存根的数据基本没用,主要是在DOS环境运行时执行

我们可以用DosBox的DOS环境运行exe程序

运行结果

查看DosStub处代码

PE文件头由PE文件头标识,标准PE头,扩展PE头三部分组成

32位

64位

Signature=50 40 00 00 'PE\0\0'

FileHeader是标准PE头

OptionalHeader是可选PE头 但是非常重要

占20字节 在pe文件头标识后即可找到

重要成员

Machine //cpu型号

NumberOfSections //节区数

SizeOfOptionalHeader //可选PE头大小 有默认值,可修改

WORD Characteristics; //属性

重要成员:

紧跟在可选头后面的就是节表,PE中的节表以数组形式存在

重要成员

Name[IMAGE_SIZEOF_SHORT_NAME]; 节区名

VirtualSize; 节区大小

VirtualAddress; 节区起始地址

PointerToRawData; 节区文件偏移

Characteristics; 节区属性

打印节表

运行结果

在代码区添加硬编码(汇编代码的十六进制形式),修改oep使得程序开始执行时执行注入的代码

最后再跳转回原始的oep

注意: 需要先关闭程序的随机基址,在可选头的DllCharacteristics中,将0x40改为0x00即可

示例程序代码

运行后会弹出HelloWorld弹窗,这里仅做简单注入,四个参数全部压入0,此时会弹出错误窗口

分析:

首先在.text段中找一段空白代码区用于写入硬编码

这里选取59A0处开始写入 5A00开始是.rdata段

确定硬编码

首先是四个参数 6A 00 6A 00 6A 00 6A 00 (4个push 0)

然后是call MessageBox和jmp oep

MessageBox地址可以运行od 输入bp MessageBoxA下断点找到

OEP为411023(ImageBase=400000 ep=11023)

计算call和jmp的偏移

call和jmp的硬编码分别为E8 E9 他们后面跟的4字节数据是偏移值

且offset=desAddr-(call/jmp Addr+5)

偏移值等于目的地址减去自身地址的下个指令地址(自身指令长度为5,所以+5是下个指令地址)

由于.text段的rva=11000 所以va=400000+11000=411000

那么59A0处的RVA=59A0-400+411000=4165A0

call offset=763C0E50-4165AD=75FAA8A3

jmp offset=411023-4165B2=FFFFAA71

写入硬编码并修改

写入后的代码

修改oep 这里改的是rva 将原本的入口点11023改为165A0即可

执行结果

可以看到程序入口点已经被修改为4165A0 并且输出错误弹窗,之后会跳转到原始的OEP处输出HelloWorld弹窗

通过.text段空白区注入代码实用性不高,通过新增节可以增大注入代码量,灵活性更高

基本过程:

判断是否有足够空间创建新的节表

每个节表占40字节 要保证有80字节空白区(多余40字节用于兼容部分系统)

在节表尾部和段首部之间便是空白区

如果尾部空白区大小不足,可以将PE头整体向上移动,覆盖掉DOS Stub(这段数据不影响程序运行)

创建新的节表

这里可以通过复制.text段的节表实现,复制之后需要调整部分成员

矫正PE文件信息

需要修改的成员有

申请新的空间用于存储新的PE文件

写入注入代码

保存文件

代码实现

执行结果

inject节表

inject节区

程序运行情况

当节表后的空白区大小不够时,可以选择扩大节

注意只能扩大最后一个节区,因为这样才不会影响到后续偏移

基本过程:

申请空间存储新的FileBuffer

修正Pe信息

需要修正的成员有

保存修改后的PE文件

代码

执行结果

可以看到原始节区VirtualSize=57E RawSize=600

扩大0x1400后

新VirtualSize=57E+1400=197E

新RawSize=600+1400=1A00

Image大小仅增加1000

这是由于400+600=A00没有超过内存对齐大小,原来的内存额外空间可以容纳这400字节,所以映像只需增加一个页

合并节也是针对ImageBuffer而言,可以直接对Imagebuffer操作

代码

执行结果

合并前节表包含9个节区,PE文件很紧凑 原文件大小39KB

合并后仅剩一个节区 并且PE文件很空旷,大部分空间是0 新文件大小128KB

合并后节区开始地址为1000 而headers到200处截止 此时又可以添加新的节区

RVA(Relative Virtual Address)VA(Virtual Address)

​ RVA = VA - ImageBase

VA(Virtual Address) VA是虚拟地址,它是代码或数据在内存中的真实地址,用于指定在内存中的具体位置。在运行时,操作系统会将RVA转换为真实的VA,即根据模块的加载基址(ImageBase)将RVA映射到内存中的实际地址。

​ VA = RVA + ImageBase

FOA(File Offset Address) 是可执行文件(如PE文件)中的文件偏移地址,它指的是代码或数据在文件中的位置偏移量。与RVA和VA不同,FOA是直接表示在文件中的偏移量,不涉及地址重定位或内存映射。

FOA和RAW均是指文件偏移

总结:

RVA=VA-ImageBase

确定RVA所在节区

可通过节区内存起始地址RVA<=RVA<=节区内存起始地址RVA+节区大小

即 VirtualAddress<=RVA<=VirtualAddress+VirtualSize判断RVA应该属于哪个节区

FOA=RVA-节区起始地址RVA+节区文件偏移=RVA - VirtualAddress + PointerToRawData

节区文件起始地址<=FOA<=节区文件起始地址+节区大小

即 PointerToRawData<=FOA<=PointerToRawData+SizeOfRawData

OEP的VA=411023

ImageBase=400000

.text 段

VirtualAddress=11000 (节区内存起始地址RVA) PointerToRawData=400 (节区文件偏移FOA)

VirtualSize(节区在内存中的大小)=5585 SizeOfRawData=5600(节区在文件中的大小)

节区在文件中的大小大于内存中的大小,因为文件对齐所以会有部分填充0的空白区域

注: 这里的VirtualSize并不一定准确,因为加载后内存对齐值为1000 所以实际内存大小应该是6000

所以OEP RVA=411023-400000=11023

显然该RVA属于.text段: 11000<=RVA<=11000+5585

OEP FOA=11023-11000+400=423

通过PETools检查可以发现入口点FOA(即RAW)=0x423 与计算结果一致

在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接。

优点 方便

缺点 二进制代码需要编译到exe中,浪费空间

注: 静态库的二进制代码保存在.lib中

在VS中可以创建静态库项目,建议创建空项目

在静态库项目中创建StaticLib.cpp源文件和StaticLib.h头文件

代码实例

点击生成即可生成静态库得到.lib文件

在项目中找到StaticLib.lib和StaticLib.h文件,复制到需要使用该静态库的工程目录中

在工程目录中导入StaticLib.h文件

程序代码

运行结果

动态库是在程序需要使用时加载,不同程序可以使用同一份dll,大大节省了空间

dll查看工具推荐https://github.com/lucasg/Dependencies

同创建静态库类似,建议使用空项目,创建好后将项目属性中的生成文件修改为动态库Dll即可

然后创建头文件和源文件

生成动态库文件有关键字导出和.def导出两种方式

关键字功能解释

代码实例

点击生成即可得到.dll和.lib文件

首先在源文件目录创建.def文件

代码示例

设置一下链接器,找到输入,修改模块定义文件如下

点击生成即可得到.dll和.lib文件

注意NONAME导出的函数只有序号没有函数名,原来的sub函数在这里是Oridinal_3

基本步骤

将.dll .lib放到工程目录中

使用#pragma comment(lib,"dllname.lib")导入lib文件

静态库的.lib文件保存二进制代码,而dll中的.lib文件仅仅是指示函数位置

加入函数声明

extern "C" __declspec(dllexport) void func();

具体示例

首先将.dll和.lib放到工程目录中

程序代码

运行结果

基本使用方法

示例程序代码

运行结果

定义

表项定义

可选头的最后两个成员分别定义了数据目录表的个数和数据目录表数组,指向了一些关键表格

比较重要的有导出表,导入表,重定位表,IAT表

打印数据目录表

导出表记录了pe文件导出的函数,所以.exe和.dll程序都可以导出函数

数据目录表中记录了导出表的地址和偏移 这个地址是RVA,需要转换为FOA

通过NONAME关键字可以使部分导出函数没有名称仅有地址

关键成员

具体示例

假设:

导出函数名称表 FuncNameTable

导出函数序号表 FuncOridinalTable

导出函数地址表 FuncAddressTable

函数地址RVA FuncAddress

使用上一节的Dll函数

DLL的导出表信息

假设已知导出函数序号OridinalNum

那么FuncAddress=FuncAddressTable[OridinalNum-Base]

即导出函数序号-Base值可以直接作为下标查找导出函数地址表得到导出函数地址

已知函数sub的导出序号为3 所以3-1=2直接查找得到其地址

所以无名函数的序号可以通过遍历导出函数地址表来得到

通过函数名称查找函数地址的过程

首先查找导出函数名称表,判断数组中哪个字符串和目的函数名称相同

将该元素的下标作为索引,查找导出函数序号表

将导出函数序号表中该下标元素的内容作为下标查找导出函数地址表,该值即为函数地址

假设要查找plus函数

plus这个函数名在函数名称表中的下标为1

而FuncOridinalTable[1]=0

所以plus Address=FuncAddressTable[0]=1125d

由于导出函数地址表的大小=NumberOfFunctions=导出函数最大序号-最小序号

当序号不是连续时,就会用0地址填充多余表项

导出函数序号表存储的序号是真实序号-Base

所以序号最小的导出函数对应的存储序号是0

导出函数序号表中不保存无名称函数的序号

通过序号查找函数时,将序号值-Base直接作为下标查找导出函数地址表

代码

运行结果和PE工具显示的一致

运行结果

查看二进制文件

导出函数地址表 从7CC8开始共0xE个表项

导出函数序号表 从7D0C开始共2个表项 每个表项2字节

存储序号分别是0xe(f func1) 0x0(1 plus)

导出函数名称表 从7D04开始共2个表项 每个表项四字节 指向字符串指针

重定位的概念: 进程拥有独立的4GB虚拟空间,.exe最先被加载,其次加载.dll 显然exe可以占用默认基址400000起始的空间,但是dll默认基址10000000会有冲突

如果能按照预定ImageBase加载则不需要重定位表,所以很多exe程序没有重定位表但是dll有

部分编译生成的地址=ImageBase+RVA (VA绝对地址)

假设全局变量x的RVA=62153 基址400000

那么mov eax,[x] =A1 53 21 46 00

即一些指令中涉及到地址的硬编码是固定写好的(绝对地址)

如果dll模块没有加载到默认的基址处,那么这些使用绝对地址的指令就需要修正

重定位表则记录了需要修正的指令地址

重定位表定义

数据目录表中第6个表项指向了重定位表

重定位表是一块一块存储的,每块的大小不一定相等,通过重定位表起始地址+SizeOfBlock可以查找下一块数据

重定位表的每个块会存储每一页(1000h)需要修改的表项 VirtualAddress即是页面起始地址

所以真正需要修复的地址=VirtualAddress+表项地址

假设VirtualAddress=8000 表项存储 12 34 56 78

那么需要修改的地址为8012 8034 8056 8078 (不考虑下面的高四位标识)

每个重定位项占2字节 其中高四位用于表示这个地址是否需要修改,低12位用于存储偏移值

如果高四位=0011那么需要修改

注意: 由于内存对齐的需要,假设表项有5个共10字节,那么实际表项会多一个空项用于内存对齐

结束时重定位表结构全为0

查看重定位表

运行结果

数据目录表第2个表项是导入表,紧跟在导出表后

PE文件可能会有多个导入表以结构体数组形式存在,结束标识为全0

重要成员

OriginalFirstThunk 指向INT表

FirstThunk 指向IAT表

INT: 导入名称表 存储导入函数名称

IAT: 导入地址表 存储导入函数地址

这两张表的定义如下

IAT表中,该结构存储的是导入函数地址RVA

INT表中,该结构可能存储导入函数序号或者是导入函数名称结构地址RVA

IAT表有两种情况

在PE文件加载到内存前,两张表存储的内容一致,加载后修复IAT

PE加载前 INT和IAT都指向IMAGE_IMPORT_BY_NAME 即导入函数名称结构

加载到内存后 IAT表被修复 存储导入函数地址

PE文件加载前IAT表已经修复过

此时IAT已经保存了导入函数地址 地址=RVA+ImageBase,这就是绑定导入

导入表的TimeDateStamp为-1时表示已经进行绑定导入,如果为0表示没有绑定导入

优点: 启动程序快

缺点: 如果没有加载到正确基址仍然需要修复IAT表

以DllTest.dll为例,用PEStudy打开

上方区域有三张导入表,分别列出了dllname以及它们INT和IAT的位置

下方区域第一列是IAT表项(即导入函数地址) 第二列是INT表项的FOA(ThunkData) 第三列是INT表项指向的值(*ThunkData)

打印导入表的代码

运行结果

附: 示例程序和修改后的程序

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
typedef struct _IMAGE_NT_HEADERS {
  DWORD                   Signature;
  IMAGE_FILE_HEADER       FileHeader;//20字节
  IMAGE_OPTIONAL_HEADER32 OptionalHeader;//32位0xE0字节 64位0xF0字节
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
typedef struct _IMAGE_NT_HEADERS {
  DWORD                   Signature;
  IMAGE_FILE_HEADER       FileHeader;//20字节
  IMAGE_OPTIONAL_HEADER32 OptionalHeader;//32位0xE0字节 64位0xF0字节
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
typedef struct _IMAGE_NT_HEADERS64 {
  DWORD                   Signature;   
  IMAGE_FILE_HEADER       FileHeader;
  IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
typedef struct _IMAGE_NT_HEADERS64 {
  DWORD                   Signature;   
  IMAGE_FILE_HEADER       FileHeader;
  IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
typedef struct _IMAGE_FILE_HEADER {
  WORD  Machine; //程序允许的cpu型号,为0代表所有   
  WORD  NumberOfSections; //区段数量
  DWORD TimeDateStamp; //时间戳
  DWORD PointerToSymbolTable;
  DWORD NumberOfSymbols;
  WORD  SizeOfOptionalHeader; //可选pe头大小 32位默认E0 64位默认F0
  WORD  Characteristics; //文件属性,每个位有不同含义
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
typedef struct _IMAGE_FILE_HEADER {
  WORD  Machine; //程序允许的cpu型号,为0代表所有   
  WORD  NumberOfSections; //区段数量
  DWORD TimeDateStamp; //时间戳
  DWORD PointerToSymbolTable;
  DWORD NumberOfSymbols;
  WORD  SizeOfOptionalHeader; //可选pe头大小 32位默认E0 64位默认F0
  WORD  Characteristics; //文件属性,每个位有不同含义
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
typedef struct _IMAGE_OPTIONAL_HEADER {
    //
    // Standard fields.
    //
 
    WORD    Magic;                      //用于标识32/64位文件 PE32: 10B PE64: 20B
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;                 //所有代码段的总大小 按照FileAlignment对齐后的大小 编译器填入 无用
    DWORD   SizeOfInitializedData;      //所有已初始化数据区块的大小 按文件对齐 编译器填入 没用(可改)
    DWORD   SizeOfUninitializedData;    //所有未初始化数据区块的大小 按文件对齐 编译器填入 没用(可改)
    DWORD   AddressOfEntryPoint;        //程序入口OEP RVA
    DWORD   BaseOfCode;                //代码区起始地址
    DWORD   BaseOfData;                //数据区起始地址
 
    //
    // NT additional fields.
    //
 
    DWORD   ImageBase;                      //内存镜像基址(程序默认载入基地址)
    DWORD   SectionAlignment;               //内存中对齐大小
    DWORD   FileAlignment;                  //文件的对齐大小(存储在硬盘中的对齐值)
    WORD    MajorOperatingSystemVersion;
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;
    DWORD   SizeOfImage;                    //内存中整个PE文件的映射的尺寸,可比实际值大,必须是SectionAlignment的整数倍
    DWORD   SizeOfHeaders;                  //所有的头加上节表文件对齐之后的值
    DWORD   CheckSum;                       //映像校验和,一些系统.dll文件有要求,判断是否被修改
    WORD    Subsystem;                     
    WORD    DllCharacteristics;             //文件特性,不是针对DLL文件的,16进制转换2进制可以根据属性对应的表格得到相应的属性
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags;
    DWORD   NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //数据目录表,结构体数组
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
typedef struct _IMAGE_OPTIONAL_HEADER {
    //
    // Standard fields.
    //
 
    WORD    Magic;                      //用于标识32/64位文件 PE32: 10B PE64: 20B
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;                 //所有代码段的总大小 按照FileAlignment对齐后的大小 编译器填入 无用
    DWORD   SizeOfInitializedData;      //所有已初始化数据区块的大小 按文件对齐 编译器填入 没用(可改)
    DWORD   SizeOfUninitializedData;    //所有未初始化数据区块的大小 按文件对齐 编译器填入 没用(可改)
    DWORD   AddressOfEntryPoint;        //程序入口OEP RVA
    DWORD   BaseOfCode;                //代码区起始地址
    DWORD   BaseOfData;                //数据区起始地址
 
    //
    // NT additional fields.
    //
 
    DWORD   ImageBase;                      //内存镜像基址(程序默认载入基地址)
    DWORD   SectionAlignment;               //内存中对齐大小
    DWORD   FileAlignment;                  //文件的对齐大小(存储在硬盘中的对齐值)
    WORD    MajorOperatingSystemVersion;
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;
    DWORD   SizeOfImage;                    //内存中整个PE文件的映射的尺寸,可比实际值大,必须是SectionAlignment的整数倍
    DWORD   SizeOfHeaders;                  //所有的头加上节表文件对齐之后的值
    DWORD   CheckSum;                       //映像校验和,一些系统.dll文件有要求,判断是否被修改
    WORD    Subsystem;                     
    WORD    DllCharacteristics;             //文件特性,不是针对DLL文件的,16进制转换2进制可以根据属性对应的表格得到相应的属性
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags;
    DWORD   NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //数据目录表,结构体数组
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
AddressOfEntryPoint;     //程序入口OEP
ImageBase;              //内存镜像地址
SectionAlignment;       //内存对齐大小
FileAlignment;          //文件对齐大小
SizeOfImage;            //文件在内存中的大小(按照SectiionAlignment对齐后)
SizeOfHeaders;          //DOS头+NT头+标准PE头+可选PE头+节表 按照文件对齐后的总大小
NumberOfRvaAndSizes     //数据目录表个数
DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] //数据目录表 存放导出表导入表等的地址和大小
AddressOfEntryPoint;     //程序入口OEP
ImageBase;              //内存镜像地址
SectionAlignment;       //内存对齐大小
FileAlignment;          //文件对齐大小
SizeOfImage;            //文件在内存中的大小(按照SectiionAlignment对齐后)
SizeOfHeaders;          //DOS头+NT头+标准PE头+可选PE头+节表 按照文件对齐后的总大小
NumberOfRvaAndSizes     //数据目录表个数
DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] //数据目录表 存放导出表导入表等的地址和大小
typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];    //8字节节区名
    unio{   //内存中的大小,该节在没有对齐之前的真实尺寸,该值可以不准确
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc;
    DWORD   VirtualAddress;                //内存中的偏移地址 加上ImageBase才是真实地址
    DWORD   SizeOfRawData;                 //节在文件中对齐后的大小
    DWORD   PointerToRawData;              //节区在文件中的偏移
    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;               //节的属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];    //8字节节区名
    unio{   //内存中的大小,该节在没有对齐之前的真实尺寸,该值可以不准确
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc;
    DWORD   VirtualAddress;                //内存中的偏移地址 加上ImageBase才是真实地址
    DWORD   SizeOfRawData;                 //节在文件中对齐后的大小
    DWORD   PointerToRawData;              //节区在文件中的偏移
    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;               //节的属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
//打印节表
void showSectionHeaders() {
    printf("\n----------SectionHeaders----------\n");
    for (DWORD i = 0; i < numberOfSections; i++) {
        //逐个读取节表并打印
        printf("\n----------Section%d----------\n", i);
        printf("Name: %8s\n", pSectionHeader[i].Name);
        printf("VirtualSize: %x\n", pSectionHeader[i].Misc.VirtualSize);
        printf("VirtualAddress: %x\n", pSectionHeader[i].VirtualAddress);
        printf("SizeOfRawData: %x\n", pSectionHeader[i].SizeOfRawData);
        printf("PointerToRawData: %x\n", pSectionHeader[i].PointerToRawData);
        printf("Characteristics: %x\n", pSectionHeader[i].Characteristics);
        printf("\n----------Section%d----------\n", i);
    }
    printf("\n----------SectionHeaders----------\n");
}
//打印节表
void showSectionHeaders() {
    printf("\n----------SectionHeaders----------\n");
    for (DWORD i = 0; i < numberOfSections; i++) {
        //逐个读取节表并打印
        printf("\n----------Section%d----------\n", i);
        printf("Name: %8s\n", pSectionHeader[i].Name);
        printf("VirtualSize: %x\n", pSectionHeader[i].Misc.VirtualSize);
        printf("VirtualAddress: %x\n", pSectionHeader[i].VirtualAddress);
        printf("SizeOfRawData: %x\n", pSectionHeader[i].SizeOfRawData);
        printf("PointerToRawData: %x\n", pSectionHeader[i].PointerToRawData);
        printf("Characteristics: %x\n", pSectionHeader[i].Characteristics);
        printf("\n----------Section%d----------\n", i);
    }
    printf("\n----------SectionHeaders----------\n");
}
#include <Windows.h>
#include <stdio.h>
 
int main() {
     
    MessageBox(0, L"HelloWorld!", L"Title", 0);
    return 0;
}
#include <Windows.h>
#include <stdio.h>
 
int main() {
     
    MessageBox(0, L"HelloWorld!", L"Title", 0);
    return 0;
}
Name // 节区名称
VirtualAddress // 节区的 RVA 地址(在内存中的偏移地址)
SizeOfRawData // 节区文件中对齐后的尺寸
PointerToRawData // 节区在文件中的偏移量
Characteristics // 节区的属性如可读,可写,可执行等
NumberOfSections
SizeOfImage
Name // 节区名称
VirtualAddress // 节区的 RVA 地址(在内存中的偏移地址)
SizeOfRawData // 节区文件中对齐后的尺寸
PointerToRawData // 节区在文件中的偏移量
Characteristics // 节区的属性如可读,可写,可执行等
NumberOfSections
SizeOfImage
//创建新的节区 返回新节区指针
PIMAGE_SECTION_HEADER CreateNewSection(const char* NewSectionName,DWORD NewSectionSize) {
    //1. 检查节表空闲区是否足够保存新的节表 80字节
    //空白空间起始地址=NT头偏移+NT头大小+所有节表大小
    DWORD BlankMemAddr = (NToffset + sizeof(IMAGE_NT_HEADERS)) + numberOfSections * sizeof(IMAGE_SECTION_HEADER);
    DWORD BlankMemSize = sizeOfHeaders - BlankMemAddr;//空白空间大小=SizeOfHeaders-各个表头大小-所有节表大小
    if (BlankMemSize < sizeof(IMAGE_SECTION_HEADER) * 2)
        return NULL;
 
    //2. 申请新的空间
    ExpandFileBuffer(NewSectionSize);
    PIMAGE_SECTION_HEADER pNewSectionHeader = (PIMAGE_SECTION_HEADER)(FileBuffer + BlankMemAddr);//指向新增的节表
 
    //3. 复制.text段的节表信息
    for (DWORD i = 0; i < numberOfSections; i++) {
        if (!strcmp((char*)pSectionHeader[i].Name, ".text"))
        {
            memcpy(pNewSectionHeader, (LPVOID)&pSectionHeader[i], sizeof(IMAGE_SECTION_HEADER));
            break;
        }
    }
 
    //4. 修正PE文件信息
    //标准PE头
    pFileHeader->NumberOfSections = ++numberOfSections;         //NumberOfSections +1
 
    //节区头
    memcpy(pNewSectionHeader->Name, NewSectionName,strlen(NewSectionName));//name
    pNewSectionHeader->Misc.VirtualSize = NewSectionSize;               //virtualsize
 
    //注意这里必须先修改VirtualAddress
    //virtualaddress 各段间是紧邻着的 所以可以根据上个段的末尾来确定新段的起始地址 上个段的起始地址+上个段的大小对于0x1000向上取整即可
    pNewSectionHeader->VirtualAddress = AlignSize(pSectionHeader[numberOfSections - 2].VirtualAddress + pSectionHeader[numberOfSections - 2].SizeOfRawData, 0x1000);
    pNewSectionHeader->SizeOfRawData = NewSectionSize;//SizeOfRawData
    //PointerToRawData 文件偏移=上个段的文件起始地址+段在文件中的大小
    pNewSectionHeader->PointerToRawData = pSectionHeader[numberOfSections - 2].PointerToRawData + pSectionHeader[numberOfSections - 2].SizeOfRawData;
    pNewSectionHeader->Characteristics |= 0x20000000;           //Characteristics 可执行权限
 
    //可选头
    pOptionalHeader->SizeOfImage = sizeOfImage = sizeOfImage + AlignSize(NewSectionSize,0x1000);//可选PE头 SizeOfImage 必须是内存对齐的整数倍 直接添加一页大小
 
    return pNewSectionHeader;
}
 
//通过创建新节区的方式注入代码
BOOL InjectCodeByCreateNewSection() {
    //1. 创建新的节区
    PIMAGE_SECTION_HEADER pNewSectionHeader = CreateNewSection(".inject", 0x1000);
 
    //修正可选头
    DWORD OEP = addressOfEntryPoint; //保存OEP
    pOptionalHeader->DllCharacteristics &= 0xFFFFFFBF;//取消ASLR随机基址 随机基址的值是0x40 所以和(0xFFFFFFFF-0x40)进行与运算即可
    pOptionalHeader->AddressOfEntryPoint = addressOfEntryPoint= pNewSectionHeader->VirtualAddress;//修改EP 注意ep=rva 不用加基址
 
    //2. 将代码写入新的节区
    BYTE InjectCode[18] = {         //偏移  指令
        0x6a,0x00,                  //0     push 0
        0x6a,0x00,                  //0     push 0
        0x6a,0x00,                  //0     push 0
        0x6a,0x00,                  //0     push 0
        0xe8,0x00,0x00,0x00,0x00,   //8     call MessageBox MessageBox=0x763C0E50 这个地址会随着系统启动而变化
        0xe9,0x00,0x00,0x00,0x00    //13    jmp oep
 
    };
    DWORD MessageBoxAddr = 0x76260E50;
    //矫正call和jmp地址
    *(DWORD*)&InjectCode[9] =OffsetOfCallAndJmp(MessageBoxAddr, imageBase + pNewSectionHeader->VirtualAddress+8) ;
    *(DWORD*)&InjectCode[14] = OffsetOfCallAndJmp(OEP, pNewSectionHeader->VirtualAddress + 13);//跳转回oep正常执行程序    
    memcpy(FileBuffer + pNewSectionHeader->PointerToRawData, InjectCode, sizeof(InjectCode));//将代码写入新的内存空间           
 
    //3. 保存文件
    return FileBufferWriteToFile(L"InjectCodeByCreateNewSection1.exe");
}
//创建新的节区 返回新节区指针
PIMAGE_SECTION_HEADER CreateNewSection(const char* NewSectionName,DWORD NewSectionSize) {
    //1. 检查节表空闲区是否足够保存新的节表 80字节
    //空白空间起始地址=NT头偏移+NT头大小+所有节表大小
    DWORD BlankMemAddr = (NToffset + sizeof(IMAGE_NT_HEADERS)) + numberOfSections * sizeof(IMAGE_SECTION_HEADER);
    DWORD BlankMemSize = sizeOfHeaders - BlankMemAddr;//空白空间大小=SizeOfHeaders-各个表头大小-所有节表大小
    if (BlankMemSize < sizeof(IMAGE_SECTION_HEADER) * 2)
        return NULL;
 
    //2. 申请新的空间
    ExpandFileBuffer(NewSectionSize);
    PIMAGE_SECTION_HEADER pNewSectionHeader = (PIMAGE_SECTION_HEADER)(FileBuffer + BlankMemAddr);//指向新增的节表
 
    //3. 复制.text段的节表信息
    for (DWORD i = 0; i < numberOfSections; i++) {
        if (!strcmp((char*)pSectionHeader[i].Name, ".text"))
        {
            memcpy(pNewSectionHeader, (LPVOID)&pSectionHeader[i], sizeof(IMAGE_SECTION_HEADER));
            break;
        }
    }
 
    //4. 修正PE文件信息
    //标准PE头
    pFileHeader->NumberOfSections = ++numberOfSections;         //NumberOfSections +1
 
    //节区头
    memcpy(pNewSectionHeader->Name, NewSectionName,strlen(NewSectionName));//name
    pNewSectionHeader->Misc.VirtualSize = NewSectionSize;               //virtualsize
 
    //注意这里必须先修改VirtualAddress
    //virtualaddress 各段间是紧邻着的 所以可以根据上个段的末尾来确定新段的起始地址 上个段的起始地址+上个段的大小对于0x1000向上取整即可
    pNewSectionHeader->VirtualAddress = AlignSize(pSectionHeader[numberOfSections - 2].VirtualAddress + pSectionHeader[numberOfSections - 2].SizeOfRawData, 0x1000);
    pNewSectionHeader->SizeOfRawData = NewSectionSize;//SizeOfRawData
    //PointerToRawData 文件偏移=上个段的文件起始地址+段在文件中的大小
    pNewSectionHeader->PointerToRawData = pSectionHeader[numberOfSections - 2].PointerToRawData + pSectionHeader[numberOfSections - 2].SizeOfRawData;
    pNewSectionHeader->Characteristics |= 0x20000000;           //Characteristics 可执行权限
 
    //可选头
    pOptionalHeader->SizeOfImage = sizeOfImage = sizeOfImage + AlignSize(NewSectionSize,0x1000);//可选PE头 SizeOfImage 必须是内存对齐的整数倍 直接添加一页大小
 
    return pNewSectionHeader;
}
 
//通过创建新节区的方式注入代码
BOOL InjectCodeByCreateNewSection() {
    //1. 创建新的节区
    PIMAGE_SECTION_HEADER pNewSectionHeader = CreateNewSection(".inject", 0x1000);
 
    //修正可选头
    DWORD OEP = addressOfEntryPoint; //保存OEP
    pOptionalHeader->DllCharacteristics &= 0xFFFFFFBF;//取消ASLR随机基址 随机基址的值是0x40 所以和(0xFFFFFFFF-0x40)进行与运算即可
    pOptionalHeader->AddressOfEntryPoint = addressOfEntryPoint= pNewSectionHeader->VirtualAddress;//修改EP 注意ep=rva 不用加基址
 
    //2. 将代码写入新的节区
    BYTE InjectCode[18] = {         //偏移  指令
        0x6a,0x00,                  //0     push 0
        0x6a,0x00,                  //0     push 0
        0x6a,0x00,                  //0     push 0
        0x6a,0x00,                  //0     push 0
        0xe8,0x00,0x00,0x00,0x00,   //8     call MessageBox MessageBox=0x763C0E50 这个地址会随着系统启动而变化
        0xe9,0x00,0x00,0x00,0x00    //13    jmp oep
 
    };
    DWORD MessageBoxAddr = 0x76260E50;
    //矫正call和jmp地址
    *(DWORD*)&InjectCode[9] =OffsetOfCallAndJmp(MessageBoxAddr, imageBase + pNewSectionHeader->VirtualAddress+8) ;
    *(DWORD*)&InjectCode[14] = OffsetOfCallAndJmp(OEP, pNewSectionHeader->VirtualAddress + 13);//跳转回oep正常执行程序    
    memcpy(FileBuffer + pNewSectionHeader->PointerToRawData, InjectCode, sizeof(InjectCode));//将代码写入新的内存空间           
 
    //3. 保存文件
    return FileBufferWriteToFile(L"InjectCodeByCreateNewSection1.exe");
}
SizeOfRawData // 节区文件中对齐后的尺寸
VirtualSize   //内存对齐后的尺寸
SizeOfImage   //映像大小
SizeOfRawData // 节区文件中对齐后的尺寸
VirtualSize   //内存对齐后的尺寸
SizeOfImage   //映像大小
//扩大节区
BOOL ExpandSection(DWORD ExSize) {
    //扩大节区大小是针对ImageBuffer而言的,所以我们添加的大小要进行内存对齐
    //1. 申请一块新空间
    ExpandFileBuffer(ExSize);       //注意这个节表指针要在申请新空间之后
    PIMAGE_SECTION_HEADER pLastSectionHeader = &pSectionHeader[numberOfSections - 1];//只能扩大最后一个节区
 
    //2. 调整SizeOfImage
    //如果VirtualSize+ExSize超过了AlignSize(VirtualSize,0x1000) 那么需要调整,否则不需要改变
    //例如vs=0x500 ex=0x400 显然,原始vs内存对齐也会占0x1000 扩展后没有超过0x1000
    //取文件大小和内存大小的最大值
     
    //先计算扩展后的内存对齐值和扩展前的内存对齐值之间的差值
    DWORD AlignExImage = AlignSize(pLastSectionHeader->Misc.VirtualSize + ExSize, 0x1000) -
        AlignSize(max(pLastSectionHeader->Misc.VirtualSize, pLastSectionHeader->SizeOfRawData), 0x1000);//内存对齐后的值
    if(AlignExImage >0)//如果差值>0说明需要扩展映像 否则内存对齐的空白区足够存储扩展区
        pOptionalHeader->SizeOfImage = sizeOfImage = sizeOfImage + AlignExImage;
 
    //3. 修改文件大小和内存大小 注意要在修改sizeofimage后再更新这两个值
    pLastSectionHeader->SizeOfRawData += AlignSize(ExSize, 0x200);//文件大小必须是文件对齐整数倍
    pLastSectionHeader->Misc.VirtualSize += ExSize;//由于是内存对齐前的大小,所以直接加上文件对齐后的大小即可
 
    //4. 保存文件
    return FileBufferWriteToFile(L"ExpandSectionFile.exe");
}
//扩大节区
BOOL ExpandSection(DWORD ExSize) {
    //扩大节区大小是针对ImageBuffer而言的,所以我们添加的大小要进行内存对齐
    //1. 申请一块新空间
    ExpandFileBuffer(ExSize);       //注意这个节表指针要在申请新空间之后
    PIMAGE_SECTION_HEADER pLastSectionHeader = &pSectionHeader[numberOfSections - 1];//只能扩大最后一个节区
 
    //2. 调整SizeOfImage
    //如果VirtualSize+ExSize超过了AlignSize(VirtualSize,0x1000) 那么需要调整,否则不需要改变
    //例如vs=0x500 ex=0x400 显然,原始vs内存对齐也会占0x1000 扩展后没有超过0x1000
    //取文件大小和内存大小的最大值
     
    //先计算扩展后的内存对齐值和扩展前的内存对齐值之间的差值
    DWORD AlignExImage = AlignSize(pLastSectionHeader->Misc.VirtualSize + ExSize, 0x1000) -
        AlignSize(max(pLastSectionHeader->Misc.VirtualSize, pLastSectionHeader->SizeOfRawData), 0x1000);//内存对齐后的值
    if(AlignExImage >0)//如果差值>0说明需要扩展映像 否则内存对齐的空白区足够存储扩展区
        pOptionalHeader->SizeOfImage = sizeOfImage = sizeOfImage + AlignExImage;
 
    //3. 修改文件大小和内存大小 注意要在修改sizeofimage后再更新这两个值
    pLastSectionHeader->SizeOfRawData += AlignSize(ExSize, 0x200);//文件大小必须是文件对齐整数倍
    pLastSectionHeader->Misc.VirtualSize += ExSize;//由于是内存对齐前的大小,所以直接加上文件对齐后的大小即可
 
    //4. 保存文件
    return FileBufferWriteToFile(L"ExpandSectionFile.exe");
}
//合并所有节区为1个
BOOL CombineSection() {
    //1. 直接修改ImageBuffer
    PIMAGE_DOS_HEADER pDosHeaderOfImage = (PIMAGE_DOS_HEADER)imageBuffer;
    PIMAGE_NT_HEADERS pNtHeadersOfImage = (PIMAGE_NT_HEADERS)(imageBuffer + pDosHeader->e_lfanew);
    PIMAGE_FILE_HEADER pFileHeaderOfImage = (PIMAGE_FILE_HEADER)(&pNtHeadersOfImage->FileHeader);
    PIMAGE_OPTIONAL_HEADER pOptionalHeaderOfImage = (PIMAGE_OPTIONAL_HEADER)(&pNtHeadersOfImage->OptionalHeader);
    PIMAGE_SECTION_HEADER pSectionHeaderOfImage = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeaderOfImage + pFileHeaderOfImage->SizeOfOptionalHeader);
 
    //复制节区属性
    for (DWORD i = 1; i < numberOfSections; i++) {
        pSectionHeaderOfImage[0].Characteristics |= pSectionHeaderOfImage[i].Characteristics;
    }
    //调整节表
    pSectionHeaderOfImage[0].PointerToRawData = pSectionHeaderOfImage[0].VirtualAddress;//文件偏移改为内存偏移
    pSectionHeaderOfImage[0].Misc.VirtualSize = pSectionHeaderOfImage[0].SizeOfRawData = sizeOfImage - pSectionHeaderOfImage[0].VirtualAddress;//新的节区大小为所有节区内存大小之和
    pOptionalHeaderOfImage->SizeOfHeaders = AlignSize(sizeOfHeaders - (numberOfSections - 1) * sizeof(IMAGE_SECTION_HEADER), 0x200);//调整头大小
    //删除其他节表
    memset(&pSectionHeaderOfImage[1], 0, sizeof(IMAGE_SECTION_HEADER) * (numberOfSections - 1));
    pFileHeaderOfImage->NumberOfSections = 1;
    return ImageBufferWriteToFile(L"CombineSectionFromDailyExercise.exe");
}
//合并所有节区为1个
BOOL CombineSection() {
    //1. 直接修改ImageBuffer
    PIMAGE_DOS_HEADER pDosHeaderOfImage = (PIMAGE_DOS_HEADER)imageBuffer;
    PIMAGE_NT_HEADERS pNtHeadersOfImage = (PIMAGE_NT_HEADERS)(imageBuffer + pDosHeader->e_lfanew);
    PIMAGE_FILE_HEADER pFileHeaderOfImage = (PIMAGE_FILE_HEADER)(&pNtHeadersOfImage->FileHeader);
    PIMAGE_OPTIONAL_HEADER pOptionalHeaderOfImage = (PIMAGE_OPTIONAL_HEADER)(&pNtHeadersOfImage->OptionalHeader);
    PIMAGE_SECTION_HEADER pSectionHeaderOfImage = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeaderOfImage + pFileHeaderOfImage->SizeOfOptionalHeader);
 
    //复制节区属性
    for (DWORD i = 1; i < numberOfSections; i++) {
        pSectionHeaderOfImage[0].Characteristics |= pSectionHeaderOfImage[i].Characteristics;
    }
    //调整节表
    pSectionHeaderOfImage[0].PointerToRawData = pSectionHeaderOfImage[0].VirtualAddress;//文件偏移改为内存偏移
    pSectionHeaderOfImage[0].Misc.VirtualSize = pSectionHeaderOfImage[0].SizeOfRawData = sizeOfImage - pSectionHeaderOfImage[0].VirtualAddress;//新的节区大小为所有节区内存大小之和
    pOptionalHeaderOfImage->SizeOfHeaders = AlignSize(sizeOfHeaders - (numberOfSections - 1) * sizeof(IMAGE_SECTION_HEADER), 0x200);//调整头大小
    //删除其他节表
    memset(&pSectionHeaderOfImage[1], 0, sizeof(IMAGE_SECTION_HEADER) * (numberOfSections - 1));
    pFileHeaderOfImage->NumberOfSections = 1;
    return ImageBufferWriteToFile(L"CombineSectionFromDailyExercise.exe");
}
//RVA转FOA
DWORD RVA2FOA(DWORD RVA) {
    DWORD FOA = 0;
    //1. 判断RVA属于哪个节区 节区内存起始地址<=RVA<=节区内存起始地址+节区大小 内存大小需要对齐 注意右边界应该是开区间
    //2. FOA=RVA-VirtualAddress+PointerToRawData
    for (DWORD i = 0; i < numberOfSections; i++) {
        if (RVA >= pSectionHeader[i].VirtualAddress && RVA < pSectionHeader[i].VirtualAddress + AlignSize(pSectionHeader[i].Misc.VirtualSize, 0x1000))//成功找到所属节区
        {
            FOA = RVA - pSectionHeader[i].VirtualAddress + pSectionHeader[i].PointerToRawData;
            break;
        }
    }
    return FOA;
}
 
//FOA转RVA
DWORD FOA2RVA(DWORD FOA) {
    DWORD RVA = 0;
    //1. 判断FOA属于哪个节区 节区文件起始地址<=FOA<=节区文件起始地址+节区大小 文件大小默认是对齐值
    //2. RVA=FOA-PointerToRawData+VirtualAddress
    for (DWORD i = 0; i < numberOfSections; i++) {
        if (FOA >= pSectionHeader[i].PointerToRawData && FOA < pSectionHeader[i].PointerToRawData + pSectionHeader[i].SizeOfRawData) {
            RVA = FOA - pSectionHeader[i].PointerToRawData + pSectionHeader[i].VirtualAddress;
            break;
        }
    }
    return RVA;
 
}
 
//输入原始大小和对齐值返回对齐后的大小
DWORD AlignSize(DWORD OrigSize, DWORD AlignVal) {
    //通过对对齐值取模判断是否对齐,如果对齐则返回原值,否则返回对齐后的值
    return OrigSize % AlignVal ? (OrigSize / AlignVal + 1) * AlignVal : OrigSize;
}
//RVA转FOA
DWORD RVA2FOA(DWORD RVA) {
    DWORD FOA = 0;
    //1. 判断RVA属于哪个节区 节区内存起始地址<=RVA<=节区内存起始地址+节区大小 内存大小需要对齐 注意右边界应该是开区间
    //2. FOA=RVA-VirtualAddress+PointerToRawData
    for (DWORD i = 0; i < numberOfSections; i++) {
        if (RVA >= pSectionHeader[i].VirtualAddress && RVA < pSectionHeader[i].VirtualAddress + AlignSize(pSectionHeader[i].Misc.VirtualSize, 0x1000))//成功找到所属节区
        {
            FOA = RVA - pSectionHeader[i].VirtualAddress + pSectionHeader[i].PointerToRawData;
            break;
        }
    }
    return FOA;
}
 
//FOA转RVA
DWORD FOA2RVA(DWORD FOA) {
    DWORD RVA = 0;
    //1. 判断FOA属于哪个节区 节区文件起始地址<=FOA<=节区文件起始地址+节区大小 文件大小默认是对齐值
    //2. RVA=FOA-PointerToRawData+VirtualAddress
    for (DWORD i = 0; i < numberOfSections; i++) {
        if (FOA >= pSectionHeader[i].PointerToRawData && FOA < pSectionHeader[i].PointerToRawData + pSectionHeader[i].SizeOfRawData) {
            RVA = FOA - pSectionHeader[i].PointerToRawData + pSectionHeader[i].VirtualAddress;
            break;
        }
    }
    return RVA;
 
}
 
//输入原始大小和对齐值返回对齐后的大小
DWORD AlignSize(DWORD OrigSize, DWORD AlignVal) {
    //通过对对齐值取模判断是否对齐,如果对齐则返回原值,否则返回对齐后的值
    return OrigSize % AlignVal ? (OrigSize / AlignVal + 1) * AlignVal : OrigSize;
}
//StaticLib.cpp 源文件保存函数代码
#include <stdio.h>
void func() {
    printf("HelloStaticLib!");
}
 
//StaticLib.h 头文件保存函数声明
void func();
//StaticLib.cpp 源文件保存函数代码
#include <stdio.h>
void func() {
    printf("HelloStaticLib!");
}
 
//StaticLib.h 头文件保存函数声明
void func();
#include  <stdio.h>
#include "StaticLib.h"  //导入头文件,头文件有函数声明
#pragma comment(lib,"StaticLib.lib")//加载静态库,这里保存函数二进制代码
int main() {
    func();     //直接使用静态库中定义的函数即可
    return 0;
}
#include  <stdio.h>
#include "StaticLib.h"  //导入头文件,头文件有函数声明
#pragma comment(lib,"StaticLib.lib")//加载静态库,这里保存函数二进制代码
int main() {
    func();     //直接使用静态库中定义的函数即可
    return 0;
}
extern  //表示是全局函数 可供其他函数调用
"C"     //按照C语言的方式编译链接,此时函数名不变 C++由于有重载机制,编译出的函数符号名会比较复杂
__declspec(dllexport)//告诉编译器该函数为导出函数
extern  //表示是全局函数 可供其他函数调用
"C"     //按照C语言的方式编译链接,此时函数名不变 C++由于有重载机制,编译出的函数符号名会比较复杂
__declspec(dllexport)//告诉编译器该函数为导出函数
//DllTest.h
extern "C" __declspec(dllexport) void func();
 
//DllTest.cpp
//注意这里.cpp和.h的函数前都需要有__declspec(dllexport) 否则只会生成.dll而没有.lib
//注意都要有extern "C" 即保证函数声明和函数定义要一致
#include<stdio.h>
extern "C" __declspec(dllexport) void func() {
    printf("HelloDynamicLib!");
}
//DllTest.h
extern "C" __declspec(dllexport) void func();
 
//DllTest.cpp
//注意这里.cpp和.h的函数前都需要有__declspec(dllexport) 否则只会生成.dll而没有.lib
//注意都要有extern "C" 即保证函数声明和函数定义要一致
#include<stdio.h>
extern "C" __declspec(dllexport) void func() {
    printf("HelloDynamicLib!");
}
//DllTest.cpp
#include<stdio.h>
void func1() {
    printf("HelloDynamicLib!");
}
int plus(int x, int y) {
    return x + y;
}
 
int sub(int x, int y) {
    return x - y;
}
 
//DllTest.def
LIBRARY "DllTest"   //标识工程目录
EXPORTS            //导出标识
 
func1 @15           //函数名@序号
plus @1
sub  @3 NONAME      //NONAME导出的函数只有序号没有函数名
//DllTest.cpp
#include<stdio.h>
void func1() {
    printf("HelloDynamicLib!");
}
int plus(int x, int y) {
    return x + y;
}
 
int sub(int x, int y) {
    return x - y;
}
 
//DllTest.def
LIBRARY "DllTest"   //标识工程目录
EXPORTS            //导出标识
 
func1 @15           //函数名@序号
plus @1
sub  @3 NONAME      //NONAME导出的函数只有序号没有函数名
#pragma comment(lib,"DllTest.lib")          //导入.lib
extern "C" __declspec(dllimport) void func();//导入函数声明
int main() {
    func();//直接调用
    return 0;
}
#pragma comment(lib,"DllTest.lib")          //导入.lib
extern "C" __declspec(dllimport) void func();//导入函数声明
int main() {
    func();//直接调用
    return 0;
}
//1. 定义函数指针
typedef int (__stdcall *lpPlus)(int,int);
//2. 声明函数指针
lpPlus plus;
//3. 动态加载dll
HINSTANCE hModule=LoadLibrary("Dllname.dll");
//4. 获取函数地址
plus=(lpPlus)GetProcAddress(hModule,"_Plus@8");
//默认的cdecl可以直接用函数名 如果是__stdcall会导致函数名改变
//5. 调用函数
int a=plus(1,2);
//1. 定义函数指针
typedef int (__stdcall *lpPlus)(int,int);
//2. 声明函数指针
lpPlus plus;
//3. 动态加载dll
HINSTANCE hModule=LoadLibrary("Dllname.dll");
//4. 获取函数地址
plus=(lpPlus)GetProcAddress(hModule,"_Plus@8");
//默认的cdecl可以直接用函数名 如果是__stdcall会导致函数名改变
//5. 调用函数
int a=plus(1,2);
#include<Windows.h>//包含了win32的函数和数据结构
#include<stdio.h>
int main() {
    typedef int(*lpPlus)(int, int);
    lpPlus plus;
    HINSTANCE hModule = LoadLibrary(L"DllTest.dll");//L是代表宽字符
    plus = (lpPlus)GetProcAddress(hModule, "plus");
    printf("%d", plus(1, 2));
    return 0;
}
#include<Windows.h>//包含了win32的函数和数据结构
#include<stdio.h>
int main() {
    typedef int(*lpPlus)(int, int);
    lpPlus plus;
    HINSTANCE hModule = LoadLibrary(L"DllTest.dll");//L是代表宽字符
    plus = (lpPlus)GetProcAddress(hModule, "plus");
    printf("%d", plus(1, 2));
    return 0;
}
typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;//虚拟地址RVA,数据目录表的起始位置
    DWORD   Size;//大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;//虚拟地址RVA,数据目录表的起始位置
    DWORD   Size;//大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
#define IMAGE_DIRECTORY_ENTRY_EXPORT        //0 导出表
#define IMAGE_DIRECTORY_ENTRY_IMPORT        //1 导入表
#define IMAGE_DIRECTORY_ENTRY_RESOURCE      //2 资源目录
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION     //3 异常目录
#define IMAGE_DIRECTORY_ENTRY_SECURITY      //4 安全目录
#define IMAGE_DIRECTORY_ENTRY_BASERELOC     //5 重定位基本表
#define IMAGE_DIRECTORY_ENTRY_DEBUG         //6 调试目录
#define IMAGE_DIRECTORY_ENTRY_COPYRIGHT     //7 描述字串 64位为ARCHITECTURE
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR     //8 机器值
#define IMAGE_DIRECTORY_ENTRY_TLS           //9 TLS目录
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG   //10 载入配值目录
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT  //11 绑定输入表
#define IMAGE_DIRECTORY_ENTRY_IAT           //12 导入地址表
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT  //13 延迟载入描述
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR//14 COM信息
//第16个保留
#define IMAGE_DIRECTORY_ENTRY_EXPORT        //0 导出表
#define IMAGE_DIRECTORY_ENTRY_IMPORT        //1 导入表
#define IMAGE_DIRECTORY_ENTRY_RESOURCE      //2 资源目录
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION     //3 异常目录
#define IMAGE_DIRECTORY_ENTRY_SECURITY      //4 安全目录
#define IMAGE_DIRECTORY_ENTRY_BASERELOC     //5 重定位基本表
#define IMAGE_DIRECTORY_ENTRY_DEBUG         //6 调试目录
#define IMAGE_DIRECTORY_ENTRY_COPYRIGHT     //7 描述字串 64位为ARCHITECTURE
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR     //8 机器值
#define IMAGE_DIRECTORY_ENTRY_TLS           //9 TLS目录
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG   //10 载入配值目录
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT  //11 绑定输入表
#define IMAGE_DIRECTORY_ENTRY_IAT           //12 导入地址表
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT  //13 延迟载入描述
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR//14 COM信息
//第16个保留
DWORD   NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //数据目录表,结构体数组
DWORD   NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //数据目录表,结构体数组
//打印数据目录表
    void PrintDirectory() {
        PIMAGE_DATA_DIRECTORY pDirectory = pOptionalHeader->DataDirectory;
        printf("\n**********数据目录表**********\n");
        for (DWORD i = 0; i < pOptionalHeader->NumberOfRvaAndSizes; i++) {
            switch (i) {
            case IMAGE_DIRECTORY_ENTRY_EXPORT:
                printf("\n==========导出表==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_IMPORT:
                printf("\n==========导入表==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_RESOURCE:
                printf("\n==========资源目录==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_EXCEPTION:
                printf("\n==========异常目录==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_SECURITY:
                printf("\n==========安全目录=========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_BASERELOC:
                printf("\n==========重定位基本表==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_DEBUG:
                printf("\n==========调试目录==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_ARCHITECTURE:
                printf("\n==========描述字串==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_GLOBALPTR:
                printf("\n==========机器值==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_TLS:
                printf("\n==========TLS目录==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG:
                printf("\n==========载入配置目录==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT:
                printf("\n==========绑定输入表==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_IAT:
                printf("\n==========导入地址表==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT:
                printf("\n==========延迟导入表==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR:
                printf("\n==========COM信息==========\n");
                break;
            case 15:
                printf("\n==========保留表==========\n");
                break;
            }
            printf("VirtualAddress=%x\nSize=%x\nFOA=%x\n", pDirectory[i].VirtualAddress, pDirectory[i].Size,RVA2FOA(pDirectory[i].VirtualAddress));
             
        }
        printf("\n**********数据目录表打印完毕**********\n\n");
    }
//打印数据目录表
    void PrintDirectory() {
        PIMAGE_DATA_DIRECTORY pDirectory = pOptionalHeader->DataDirectory;
        printf("\n**********数据目录表**********\n");
        for (DWORD i = 0; i < pOptionalHeader->NumberOfRvaAndSizes; i++) {
            switch (i) {
            case IMAGE_DIRECTORY_ENTRY_EXPORT:
                printf("\n==========导出表==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_IMPORT:
                printf("\n==========导入表==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_RESOURCE:
                printf("\n==========资源目录==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_EXCEPTION:
                printf("\n==========异常目录==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_SECURITY:
                printf("\n==========安全目录=========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_BASERELOC:
                printf("\n==========重定位基本表==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_DEBUG:
                printf("\n==========调试目录==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_ARCHITECTURE:
                printf("\n==========描述字串==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_GLOBALPTR:
                printf("\n==========机器值==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_TLS:
                printf("\n==========TLS目录==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG:
                printf("\n==========载入配置目录==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT:
                printf("\n==========绑定输入表==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_IAT:
                printf("\n==========导入地址表==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT:
                printf("\n==========延迟导入表==========\n");
                break;
            case IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR:
                printf("\n==========COM信息==========\n");
                break;
            case 15:
                printf("\n==========保留表==========\n");
                break;
            }
            printf("VirtualAddress=%x\nSize=%x\nFOA=%x\n", pDirectory[i].VirtualAddress, pDirectory[i].Size,RVA2FOA(pDirectory[i].VirtualAddress));
             
        }
        printf("\n**********数据目录表打印完毕**********\n\n");
    }
typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;
    WORD    MajorVersion;
    WORD    MinorVersion;
    DWORD   Name;                  // 指向导出表文件名 RVA-> FOA + FileBuffer=char *name
    DWORD   Base;                  // 导出函数起始序号
    DWORD   NumberOfFunctions;      // 所有导出函数的个数
    DWORD   NumberOfNames;          // 以函数名称导出的函数个数
    DWORD   AddressOfFunctions;     // 导出函数地址表首地址RVA 记录了所有导出函数的地址 每个表项大小4字节
    DWORD   AddressOfNames;         // 导出函数名称表首地址RVA 每个表项都是函数名的字符串指针RVA 每个表项大小4字节
    DWORD   AddressOfNameOrdinals;  // 导出函数序号表首地址RVA 其中存储的序号为-Base后的值  每个表项大小2字节
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;
    WORD    MajorVersion;
    WORD    MinorVersion;
    DWORD   Name;                  // 指向导出表文件名 RVA-> FOA + FileBuffer=char *name
    DWORD   Base;                  // 导出函数起始序号
    DWORD   NumberOfFunctions;      // 所有导出函数的个数
    DWORD   NumberOfNames;          // 以函数名称导出的函数个数
    DWORD   AddressOfFunctions;     // 导出函数地址表首地址RVA 记录了所有导出函数的地址 每个表项大小4字节
    DWORD   AddressOfNames;         // 导出函数名称表首地址RVA 每个表项都是函数名的字符串指针RVA 每个表项大小4字节
    DWORD   AddressOfNameOrdinals;  // 导出函数序号表首地址RVA 其中存储的序号为-Base后的值  每个表项大小2字节
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
Name               
Base               
NumberOfFunctions      
NumberOfNames          
AddressOfFunctions   
AddressOfNames       
AddressOfNameOrdinals 
Name               
Base               
NumberOfFunctions      
NumberOfNames          
AddressOfFunctions   
AddressOfNames       
AddressOfNameOrdinals 
//DllTest.def
LIBRARY "DllTest"
EXPORTS
 
func1 @15
plus @1
sub  @3 NONAME  //sub序号为3 无导出函数名
     
//DllTest.cpp
#include<stdio.h>
#include<windows.h>
void func1() {
    printf("HelloDynamicLib!");
}
int plus(int x, int y) {
    return x + y;
}
 
int sub(int x, int y) {
    return x - y;
}
//DllTest.def
LIBRARY "DllTest"
EXPORTS
 
func1 @15
plus @1
sub  @3 NONAME  //sub序号为3 无导出函数名
     
//DllTest.cpp
#include<stdio.h>
#include<windows.h>
void func1() {
    printf("HelloDynamicLib!");
}
int plus(int x, int y) {
    return x + y;
}
 
int sub(int x, int y) {
    return x - y;
}
if(strcmp(name,FuncNameTable[i])==0)
    FuncAddress=FuncAddressTable[FuncOridinalTable[i]];
if(strcmp(name,FuncNameTable[i])==0)
    FuncAddress=FuncAddressTable[FuncOridinalTable[i]];
//通过函数序号获取函数地址
DWORD GetFuncAddrByOridinals(WORD OridinalNum) {
    DWORD* pExportFuncAddressTable = (DWORD*)(RVA2FOA(pExportDirectory->AddressOfFunctions) + FileBuffer);//导出函数地址表
    return pExportFuncAddressTable[OridinalNum - pExportDirectory->Base];//减去Base值作为索引直接查找函数地址
}
//通过函数序号获取函数地址
DWORD GetFuncAddrByOridinals(WORD OridinalNum) {
    DWORD* pExportFuncAddressTable = (DWORD*)(RVA2FOA(pExportDirectory->AddressOfFunctions) + FileBuffer);//导出函数地址表
    return pExportFuncAddressTable[OridinalNum - pExportDirectory->Base];//减去Base值作为索引直接查找函数地址
}
//通过函数名获取函数地址
    DWORD GetFuncAddrByName(const char* FuncName) {
        WORD* pExportFuncOridinalsTable = (WORD*)(RVA2FOA(pExportDirectory->AddressOfNameOrdinals) + FileBuffer);//导出函数序号表
        DWORD* pExportFuncAddressTable = (DWORD*)(RVA2FOA(pExportDirectory->AddressOfFunctions) + FileBuffer);//导出函数地址表
        DWORD* pExportFuncNamesTable = (DWORD*)(RVA2FOA(pExportDirectory->AddressOfNames) + FileBuffer);//导出函数名称表
 
        DWORD pos = -1,OridinalNum=0;
        //1. 通过导出函数名称表得到序号表下标
        for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++) {
            //注意导出函数名称表表项是字符串指针 该指针值为RVA
            if (strcmp(FuncName, (char*)(RVA2FOA(pExportFuncNamesTable[i])+FileBuffer)) == 0)
            {
                pos = i;
                break;
            }
        }
        if (pos == -1)//查找失败
            return 0;
 
        //2. 通过序号表得到序号
        OridinalNum = pExportFuncOridinalsTable[pos];
 
        //3. 得到函数地址
        return pExportFuncAddressTable[OridinalNum];
    }
//通过函数名获取函数地址
    DWORD GetFuncAddrByName(const char* FuncName) {
        WORD* pExportFuncOridinalsTable = (WORD*)(RVA2FOA(pExportDirectory->AddressOfNameOrdinals) + FileBuffer);//导出函数序号表
        DWORD* pExportFuncAddressTable = (DWORD*)(RVA2FOA(pExportDirectory->AddressOfFunctions) + FileBuffer);//导出函数地址表
        DWORD* pExportFuncNamesTable = (DWORD*)(RVA2FOA(pExportDirectory->AddressOfNames) + FileBuffer);//导出函数名称表
 
        DWORD pos = -1,OridinalNum=0;
        //1. 通过导出函数名称表得到序号表下标
        for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++) {
            //注意导出函数名称表表项是字符串指针 该指针值为RVA
            if (strcmp(FuncName, (char*)(RVA2FOA(pExportFuncNamesTable[i])+FileBuffer)) == 0)
            {
                pos = i;
                break;
            }
        }
        if (pos == -1)//查找失败
            return 0;
 
        //2. 通过序号表得到序号
        OridinalNum = pExportFuncOridinalsTable[pos];
 
        //3. 得到函数地址
        return pExportFuncAddressTable[OridinalNum];
    }
//根据函数序号返回函数名地址RVA
    BYTE* GetFuncNameByOridinals(WORD OridinalNum) {
        WORD* pExportFuncOridinalsTable = (WORD*)(RVA2FOA(pExportDirectory->AddressOfNameOrdinals) + FileBuffer);//导出函数序号表
        DWORD* pExportFuncNamesTable = (DWORD*)(RVA2FOA(pExportDirectory->AddressOfNames) + FileBuffer);//导出函数名称表
        for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++)
        {
            if (pExportFuncOridinalsTable[i] == OridinalNum)//实际存储的序号=函数序号-base
                return RVA2FOA(pExportFuncNamesTable[i])+FileBuffer;
        }
        return NULL;//没有找到说明是无名函数
    }
 //打印导出表详细信息
    void PrintExportDirectory() {
        printf("\n==========导出表==========\n");
        printf("Name: %x (%s)\n",pExportDirectory->Name,(char*)(FileBuffer+RVA2FOA(pExportDirectory->Name)));
        printf("Base: %x\n", pExportDirectory->Base);
        printf("NumberOfFunctions: \t%x\n", pExportDirectory->NumberOfFunctions);
        printf("NumberOfNames: \t\t%x\n", pExportDirectory->NumberOfNames);
        printf("AddressOfFunctions: \tRVA=%x\tFOA=%x\n", pExportDirectory->AddressOfFunctions,RVA2FOA(pExportDirectory->AddressOfFunctions));
        printf("AddressOfNames: \tRVA=%x\tFOA=%x\n", pExportDirectory->AddressOfNames, RVA2FOA(pExportDirectory->AddressOfNames));
        printf("AddressOfNameOrdinals: \tRVA=%x\tFOA=%x\n", pExportDirectory->AddressOfNameOrdinals , RVA2FOA(pExportDirectory->AddressOfNameOrdinals));
         
        WORD* pExportFuncOridinalsTable =(WORD*)(RVA2FOA(pExportDirectory->AddressOfNameOrdinals) + FileBuffer);//导出函数序号表
        DWORD* pExportFuncAddressTable = (DWORD*)(RVA2FOA(pExportDirectory->AddressOfFunctions)+ FileBuffer);//导出函数地址表
        DWORD* pExportFuncNamesTable = (DWORD*)(RVA2FOA(pExportDirectory->AddressOfNames )+ FileBuffer);//导出函数名称表
 
        printf("\nOridinal\t     RVA\t     FOA\tFunctionName\n");
        
        for (DWORD i = 0; i < pExportDirectory->NumberOfFunctions; i++) {
            if (pExportFuncAddressTable[i] == 0)//地址为零则跳过
                continue;
            BYTE* FuncName = NULL;
            //由于导出函数序号表仅保存有名函数序号,所以有序号必定有名称,否则无名称
            //函数序号=函数地址表下标+Base
            printf("%08x\t%08x\t%08x\t",i+pExportDirectory->Base, pExportFuncAddressTable[i],RVA2FOA(pExportFuncAddressTable[i]));
            //是否存在函数名要单独判断 存储序号=函数序号-Base,故传递i即可
            if (FuncName = GetFuncNameByOridinals(i))
                printf("%s\n", FuncName);
            else
                printf("NONAME\n");
        }
        printf("\n==========导出表结束==========\n");
    }
//根据函数序号返回函数名地址RVA
    BYTE* GetFuncNameByOridinals(WORD OridinalNum) {
        WORD* pExportFuncOridinalsTable = (WORD*)(RVA2FOA(pExportDirectory->AddressOfNameOrdinals) + FileBuffer);//导出函数序号表
        DWORD* pExportFuncNamesTable = (DWORD*)(RVA2FOA(pExportDirectory->AddressOfNames) + FileBuffer);//导出函数名称表
        for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++)
        {
            if (pExportFuncOridinalsTable[i] == OridinalNum)//实际存储的序号=函数序号-base
                return RVA2FOA(pExportFuncNamesTable[i])+FileBuffer;
        }
        return NULL;//没有找到说明是无名函数
    }
 //打印导出表详细信息
    void PrintExportDirectory() {
        printf("\n==========导出表==========\n");
        printf("Name: %x (%s)\n",pExportDirectory->Name,(char*)(FileBuffer+RVA2FOA(pExportDirectory->Name)));
        printf("Base: %x\n", pExportDirectory->Base);
        printf("NumberOfFunctions: \t%x\n", pExportDirectory->NumberOfFunctions);
        printf("NumberOfNames: \t\t%x\n", pExportDirectory->NumberOfNames);
        printf("AddressOfFunctions: \tRVA=%x\tFOA=%x\n", pExportDirectory->AddressOfFunctions,RVA2FOA(pExportDirectory->AddressOfFunctions));
        printf("AddressOfNames: \tRVA=%x\tFOA=%x\n", pExportDirectory->AddressOfNames, RVA2FOA(pExportDirectory->AddressOfNames));
        printf("AddressOfNameOrdinals: \tRVA=%x\tFOA=%x\n", pExportDirectory->AddressOfNameOrdinals , RVA2FOA(pExportDirectory->AddressOfNameOrdinals));
         
        WORD* pExportFuncOridinalsTable =(WORD*)(RVA2FOA(pExportDirectory->AddressOfNameOrdinals) + FileBuffer);//导出函数序号表
        DWORD* pExportFuncAddressTable = (DWORD*)(RVA2FOA(pExportDirectory->AddressOfFunctions)+ FileBuffer);//导出函数地址表
        DWORD* pExportFuncNamesTable = (DWORD*)(RVA2FOA(pExportDirectory->AddressOfNames )+ FileBuffer);//导出函数名称表
 
        printf("\nOridinal\t     RVA\t     FOA\tFunctionName\n");
        
        for (DWORD i = 0; i < pExportDirectory->NumberOfFunctions; i++) {
            if (pExportFuncAddressTable[i] == 0)//地址为零则跳过
                continue;
            BYTE* FuncName = NULL;
            //由于导出函数序号表仅保存有名函数序号,所以有序号必定有名称,否则无名称
            //函数序号=函数地址表下标+Base
            printf("%08x\t%08x\t%08x\t",i+pExportDirectory->Base, pExportFuncAddressTable[i],RVA2FOA(pExportFuncAddressTable[i]));
            //是否存在函数名要单独判断 存储序号=函数序号-Base,故传递i即可
            if (FuncName = GetFuncNameByOridinals(i))
                printf("%s\n", FuncName);
            else
                printf("NONAME\n");
        }
        printf("\n==========导出表结束==========\n");
    }
typedef struct _IMAGE_BASE_RELOCATION {
    DWORD   VirtualAddress; // 重定位数据页面起始地址
    DWORD   SizeOfBlock;    // 重定位块的长度
//WORD    TypeOffset[1];    // 重定位项数组
    //该数组每个元素占2字节,加上VirtualAddress后才是真实地址
} IMAGE_BASE_RELOCATION;
//最后一个块的值全为0
typedef IMAGE_BASE_RELOCATION*,PIMAGE_BASE_RELOCATION;
typedef struct _IMAGE_BASE_RELOCATION {
    DWORD   VirtualAddress; // 重定位数据页面起始地址
    DWORD   SizeOfBlock;    // 重定位块的长度
//WORD    TypeOffset[1];    // 重定位项数组
    //该数组每个元素占2字节,加上VirtualAddress后才是真实地址
} IMAGE_BASE_RELOCATION;
//最后一个块的值全为0
typedef IMAGE_BASE_RELOCATION*,PIMAGE_BASE_RELOCATION;
//通过RVA判断所属区段名
PCHAR GetSectionNameByRva(DWORD RVA) {
    for (DWORD i = 0; i < numberOfSections; i++) {
        if (RVA >= pSectionHeader[i].VirtualAddress && RVA < pSectionHeader[i].VirtualAddress + AlignSize(pSectionHeader[i].Misc.VirtualSize, 0x1000))//成功找到所属节区
            return (PCHAR)pSectionHeader[i].Name;
    }
}
//打印重定位表的某个块
void PrintRelocationBlock(PIMAGE_BASE_RELOCATION pRelocationBlock) {
    PWORD pBlock = (PWORD)((DWORD)pRelocationBlock + 8);//注意每个表项占2字节 但是高4位用来判断是否需要修改
    DWORD PageOffset = pRelocationBlock->VirtualAddress;//每个块的虚拟地址即为页面起始地址
 
    printf("序号\t属性\t     RVA\t     FOA\t指向RVA\n");
    for (DWORD i = 0; i < (pRelocationBlock->SizeOfBlock - 8) / 2; i++) {
        //每块高四位用作属性判断,低12位才是页内偏移值 还要注意与运算优先级低于+ 不用括号会导致出错
        //指向的RVA即需要矫正的地址
        printf("%04x\t%4x\t%08x\t%08x\t%08x\n", i, pBlock[i] >> 12, (pBlock[i] & 0x0fff) + PageOffset, RVA2FOA((pBlock[i] & 0x0fff) + PageOffset), *(DWORD*)(FileBuffer + RVA2FOA((pBlock[i] & 0x0fff) + PageOffset)) & 0x00ffffff);
    }
}
 
//打印重定位表
void PrintRelocationTable() {
    PIMAGE_BASE_RELOCATION pRelocationTable = pBaseRelocation;
    printf("\n==========重定位表==========\n");
    printf("序号\t    区段\t     RVA\t     FOA\t项目数\n");
 
    //表块全为0时结束
    DWORD count = 0;
    while (pRelocationTable->VirtualAddress || pRelocationTable->SizeOfBlock) {
        //项目数=(sizeofBlock-8)/2
        printf("%4d\t%8s\t%08x\t%08x\t%08x\n", count++, GetSectionNameByRva(pRelocationTable->VirtualAddress), pRelocationTable->VirtualAddress, RVA2FOA(pRelocationTable->VirtualAddress), (pRelocationTable->SizeOfBlock - 8) / 2);
        pRelocationTable = (PIMAGE_BASE_RELOCATION)((DWORD)pRelocationTable + pRelocationTable->SizeOfBlock);//注意这里应该将指针值强转后+块大小指向下一个块
    }
 
    pRelocationTable = pBaseRelocation;
    count = 0;
    while (pRelocationTable->VirtualAddress || pRelocationTable->SizeOfBlock) {
        printf("\n==========Block%d==========\n", count++);
        PrintRelocationBlock(pRelocationTable);//打印第i个块
        pRelocationTable = (PIMAGE_BASE_RELOCATION)((DWORD)pRelocationTable + pRelocationTable->SizeOfBlock);
    }
 
    printf("\n==========重定位表结束==========\n");
}
//通过RVA判断所属区段名
PCHAR GetSectionNameByRva(DWORD RVA) {
    for (DWORD i = 0; i < numberOfSections; i++) {
        if (RVA >= pSectionHeader[i].VirtualAddress && RVA < pSectionHeader[i].VirtualAddress + AlignSize(pSectionHeader[i].Misc.VirtualSize, 0x1000))//成功找到所属节区
            return (PCHAR)pSectionHeader[i].Name;
    }
}
//打印重定位表的某个块
void PrintRelocationBlock(PIMAGE_BASE_RELOCATION pRelocationBlock) {
    PWORD pBlock = (PWORD)((DWORD)pRelocationBlock + 8);//注意每个表项占2字节 但是高4位用来判断是否需要修改
    DWORD PageOffset = pRelocationBlock->VirtualAddress;//每个块的虚拟地址即为页面起始地址
 
    printf("序号\t属性\t     RVA\t     FOA\t指向RVA\n");
    for (DWORD i = 0; i < (pRelocationBlock->SizeOfBlock - 8) / 2; i++) {
        //每块高四位用作属性判断,低12位才是页内偏移值 还要注意与运算优先级低于+ 不用括号会导致出错
        //指向的RVA即需要矫正的地址
        printf("%04x\t%4x\t%08x\t%08x\t%08x\n", i, pBlock[i] >> 12, (pBlock[i] & 0x0fff) + PageOffset, RVA2FOA((pBlock[i] & 0x0fff) + PageOffset), *(DWORD*)(FileBuffer + RVA2FOA((pBlock[i] & 0x0fff) + PageOffset)) & 0x00ffffff);
    }
}
 
//打印重定位表
void PrintRelocationTable() {
    PIMAGE_BASE_RELOCATION pRelocationTable = pBaseRelocation;
    printf("\n==========重定位表==========\n");
    printf("序号\t    区段\t     RVA\t     FOA\t项目数\n");
 
    //表块全为0时结束
    DWORD count = 0;
    while (pRelocationTable->VirtualAddress || pRelocationTable->SizeOfBlock) {
        //项目数=(sizeofBlock-8)/2
        printf("%4d\t%8s\t%08x\t%08x\t%08x\n", count++, GetSectionNameByRva(pRelocationTable->VirtualAddress), pRelocationTable->VirtualAddress, RVA2FOA(pRelocationTable->VirtualAddress), (pRelocationTable->SizeOfBlock - 8) / 2);
        pRelocationTable = (PIMAGE_BASE_RELOCATION)((DWORD)pRelocationTable + pRelocationTable->SizeOfBlock);//注意这里应该将指针值强转后+块大小指向下一个块
    }
 
    pRelocationTable = pBaseRelocation;
    count = 0;
    while (pRelocationTable->VirtualAddress || pRelocationTable->SizeOfBlock) {
        printf("\n==========Block%d==========\n", count++);
        PrintRelocationBlock(pRelocationTable);//打印第i个块
        pRelocationTable = (PIMAGE_BASE_RELOCATION)((DWORD)pRelocationTable + pRelocationTable->SizeOfBlock);
    }
 

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2023-8-12 11:05 被东方玻璃编辑 ,原因: 排版图片
上传的附件:
收藏
免费 13
支持
分享
打赏 + 1.00雪花
打赏次数 1 雪花 + 1.00
 
赞赏  R0g   +1.00 2023/09/24
最新回复 (4)
雪    币: 1855
活跃值: (4141)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
666
2023-8-13 01:22
0
雪    币: 2481
活跃值: (2393)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3

楼主这篇文章让我重温了下罗云彬Win32汇编语言的PE这章节

最后于 2023-8-17 20:22 被neulin编辑 ,原因:
2023-8-17 20:21
0
雪    币: 2277
活跃值: (5005)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
学习了
2023-8-22 10:32
0
雪    币: 42
活跃值: (569)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
已收藏
2023-11-27 15:31
0
游客
登录 | 注册 方可回帖
返回
//