依星源码资源网,依星资源网

 找回密码
 立即注册

QQ登录

只需一步,快速开始

限时开通VIP永久会员,可免费下载所有附件
查看: 16|回复: 0

PE文件解析基础

[复制链接] 主动推送

1万

主题

1万

帖子

1万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
11645
发表于 5 天前 | 显示全部楼层 |阅读模式
PE文件解析基础
PE加载过程
硬盘文件->加载到内存(FileBuffer)->E Loader加载并拉伸->ImageBuffer(起始位置ImageBase)
DOS头
从0x00~0x3f共0x40字节固定大小
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;

e_magic: pe指纹 "MZ"
e_lfanew: pe头的偏移
其他成员无关紧要

PE文件解析基础

PE文件解析基础
DOS Stub
从dos头到pe头之间是dos存根

PE文件解析基础

PE文件解析基础
dos存根的数据基本没用,主要是在DOS环境运行时执行
我们可以用DosBox的DOS环境运行exe程序
运行结果

PE文件解析基础

PE文件解析基础
查看DosStub处代码

PE文件解析基础

PE文件解析基础
NT/PE头
PE文件头由PE文件头标识,标准PE头,扩展PE头三部分组成
32位
1
2
3
4
5
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;

64位
1
2
3
4
5
typedef struct _IMAGE_NT_HEADERS64 {
  DWORD                   Signature;   
  IMAGE_FILE_HEADER       FileHeader;
  IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

Signature=50 40 00 00 'PE'
FileHeader是标准PE头
OptionalHeader是可选PE头 但是非常重要
标准PE头/文件头
占20字节 在pe文件头标识后即可找到
1
2
3
4
5
6
7
8
9
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;

重要成员
Machine //cpu型号
NumberOfSections //节区数
SizeOfOptionalHeader //可选PE头大小 有默认值,可修改
WORD Characteristics; //属性
可选PE头/扩展头
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
30
31
32
33
34
35
36
37
38
39
40
41
42
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;

重要成员:
1
2
3
4
5
6
7
8
AddressOfEntryPoint;     //程序入口OEP
ImageBase;              //内存镜像地址
SectionAlignment;       //内存对齐大小
FileAlignment;          //文件对齐大小
SizeOfImage;            //文件在内存中的大小(按照SectiionAlignment对齐后)
SizeOfHeaders;          //DOS头+NT头+标准PE头+可选PE头+节表 按照文件对齐后的总大小
NumberOfRvaAndSizes     //数据目录表个数
DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] //数据目录表 存放导出表导入表等的地址和大小

节表
紧跟在可选头后面的就是节表,PE中的节表以数组形式存在
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;

重要成员
Name[IMAGE_SIZEOF_SHORT_NAME]; 节区名
VirtualSize; 节区大小
VirtualAddress; 节区起始地址
PointerToRawData; 节区文件偏移
Characteristics; 节区属性
打印节表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//打印节表
void showSectionHeaders() {
    printf("----------SectionHeaders----------");
    for (DWORD i = 0; i < numberOfSections; i++) {
        //逐个读取节表并打印
        printf("----------Section%d----------", i);
        printf("Name: %8s", pSectionHeader.Name);
        printf("VirtualSize: %x", pSectionHeader.Misc.VirtualSize);
        printf("VirtualAddress: %x", pSectionHeader.VirtualAddress);
        printf("SizeOfRawData: %x", pSectionHeader.SizeOfRawData);
        printf("ointerToRawData: %x", pSectionHeader.PointerToRawData);
        printf("Characteristics: %x", pSectionHeader.Characteristics);
        printf("----------Section%d----------", i);
    }
    printf("----------SectionHeaders----------");
}

运行结果

PE文件解析基础

PE文件解析基础
代码段空白区添加代码基本原理
在代码区添加硬编码(汇编代码的十六进制形式),修改oep使得程序开始执行时执行注入的代码
最后再跳转回原始的oep
  • 获取函数地址
  • 计算偏移
  • 代码区手动添加代码
  • 修改oep指向shellcode
  • 执行完shellcode后跳回oep
注意: 需要先关闭程序的随机基址,在可选头的DllCharacteristics中,将0x40改为0x00即可

PE文件解析基础

PE文件解析基础

案例分析
示例程序代码
1
2
3
4
5
6
7
8
#include
#include

int main() {
     
    MessageBox(0, L"HelloWorld!", L"Title", 0);
    return 0;
}

运行后会弹出HelloWorld弹窗,这里仅做简单注入,四个参数全部压入0,此时会弹出错误窗口
分析:
  • 首先在.text段中找一段空白代码区用于写入硬编码
    这里选取59A0处开始写入 5A00开始是.rdata段

    PE文件解析基础

    PE文件解析基础
  • 确定硬编码
    首先是四个参数 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)

    PE文件解析基础

    PE文件解析基础
  • 计算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

    PE文件解析基础

    PE文件解析基础
  • 写入硬编码并修改
    写入后的代码

    PE文件解析基础

    PE文件解析基础
    修改oep 这里改的是rva 将原本的入口点11023改为165A0即可

    PE文件解析基础

    PE文件解析基础

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

PE文件解析基础

PE文件解析基础
新增节添加代码
通过.text段空白区注入代码实用性不高,通过新增节可以增大注入代码量,灵活性更高
基本过程:
  • 判断是否有足够空间创建新的节表
    每个节表占40字节 要保证有80字节空白区(多余40字节用于兼容部分系统)
    在节表尾部和段首部之间便是空白区
    如果尾部空白区大小不足,可以将PE头整体向上移动,覆盖掉DOS Stub(这段数据不影响程序运行)

    PE文件解析基础

    PE文件解析基础

  • 创建新的节表
    这里可以通过复制.text段的节表实现,复制之后需要调整部分成员
  • 矫正PE文件信息
    需要修改的成员有
    1
    2
    3
    4
    5
    6
    7
    Name // 节区名称
    VirtualAddress // 节区的 RVA 地址(在内存中的偏移地址)
    SizeOfRawData // 节区文件中对齐后的尺寸
    PointerToRawData // 节区在文件中的偏移量
    Characteristics // 节区的属性如可读,可写,可执行等
    NumberOfSections
    SizeOfImage

  • 申请新的空间用于存储新的PE文件
  • 写入注入代码
  • 保存文件

代码实现
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//创建新的节区 返回新节区指针
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.Name, ".text"))
        {
            memcpy(pNewSectionHeader, (LPVOID)&pSectionHeader, 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->ointerToRawData = 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->ointerToRawData, InjectCode, sizeof(InjectCode));//将代码写入新的内存空间           

    //3. 保存文件
    return FileBufferWriteToFile(L"InjectCodeByCreateNewSection1.exe");
}

执行结果
inject节表

PE文件解析基础

PE文件解析基础
inject节区

PE文件解析基础

PE文件解析基础
程序运行情况

PE文件解析基础

PE文件解析基础
扩大节
当节表后的空白区大小不够时,可以选择扩大节
注意只能扩大最后一个节区,因为这样才不会影响到后续偏移
基本过程:
  • 申请空间存储新的FileBuffer
  • 修正Pe信息
    需要修正的成员有
    1
    2
    3
    SizeOfRawData // 节区文件中对齐后的尺寸
    VirtualSize   //内存对齐后的尺寸
    SizeOfImage   //映像大小

  • 保存修改后的PE文件

代码
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
//扩大节区
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");
}

执行结果
可以看到原始节区VirtualSize=57E RawSize=600
扩大0x1400后
新VirtualSize=57E+1400=197E
新RawSize=600+1400=1A00

PE文件解析基础

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

PE文件解析基础

PE文件解析基础

合并节
合并节也是针对ImageBuffer而言,可以直接对Imagebuffer操作
  • 将所有节区属性合并到节区1
  • 调整节表的文件偏移和内存大小
  • 删除其他节表
  • 保存文件
代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//合并所有节区为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.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");
}

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

PE文件解析基础

PE文件解析基础
合并后仅剩一个节区 并且PE文件很空旷,大部分空间是0 新文件大小128KB
合并后节区开始地址为1000 而headers到200处截止 此时又可以添加新的节区

PE文件解析基础

PE文件解析基础
RVA&VA&FOA&RAW
RVA(Relative Virtual Address)VA(Virtual Address)
  • RVA(Relative Virtual Address) RVA是相对虚拟地址,它是相对于模块的加载基址(ImageBase)的地址偏移量。在可执行文件中,RVA通常用于指定代码或数据在内存中的位置。RVA是相对于模块内部的地址,不受具体加载地址的影响
​ 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是相对于模块加载基址的地址偏移量,RVA是在可执行文件中使用的,用于在文件中表示位置
  • VA是代码或数据在内存中的真实地址,VA是在运行时使用的,表示内存中的实际位置。
  • FOA是代码或数据在文件中距离文件起始位置(0x00)的偏移值
换算已知RVA求FOA
  • RVA=VA-ImageBase
  • 确定RVA所在节区
    可通过节区内存起始地址RVA<=RVA<=节区内存起始地址RVA+节区大小
    即 VirtualAddress<=RVA<=VirtualAddress+VirtualSize判断RVA应该属于哪个节区
  • FOA=RVA-节区起始地址RVA+节区文件偏移=RVA - VirtualAddress + PointerToRawData

已知FOA求RVA
  • 确定FOA所在节区
节区文件起始地址<=FOA<=节区文件起始地址+节区大小
即 PointerToRawData<=FOA<=PointerToRawData+SizeOfRawData
  • RVA=FOA-节区文件起始地址+节区内存起始地址RVA=FOA-PointerToRawData+VirtualAddress
实例
OEP的VA=411023
ImageBase=400000
.text 段
VirtualAddress=11000 (节区内存起始地址RVA) PointerToRawData=400 (节区文件偏移FOA)
VirtualSize(节区在内存中的大小)=5585 SizeOfRawData=5600(节区在文件中的大小)
节区在文件中的大小大于内存中的大小,因为文件对齐所以会有部分填充0的空白区域
注: 这里的VirtualSize并不一定准确,因为加载后内存对齐值为1000 所以实际内存大小应该是6000

PE文件解析基础

PE文件解析基础
所以OEP RVA=411023-400000=11023
显然该RVA属于.text段: 11000<=RVA<=11000+5585
OEP FOA=11023-11000+400=423
通过PETools检查可以发现入口点FOA(即RAW)=0x423 与计算结果一致

PE文件解析基础

PE文件解析基础
换算代码
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
30
31
32
33
34
35
//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.VirtualAddress && RVA < pSectionHeader.VirtualAddress + AlignSize(pSectionHeader.Misc.VirtualSize, 0x1000))//成功找到所属节区
        {
            FOA = RVA - pSectionHeader.VirtualAddress + pSectionHeader.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.PointerToRawData && FOA < pSectionHeader.PointerToRawData + pSectionHeader.SizeOfRawData) {
            RVA = FOA - pSectionHeader.PointerToRawData + pSectionHeader.VirtualAddress;
            break;
        }
    }
    return RVA;

}

//输入原始大小和对齐值返回对齐后的大小
DWORD AlignSize(DWORD OrigSize, DWORD AlignVal) {
    //通过对对齐值取模判断是否对齐,如果对齐则返回原值,否则返回对齐后的值
    return OrigSize % AlignVal ? (OrigSize / AlignVal + 1) * AlignVal : OrigSize;
}

静态/动态链接库静态库
在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接。
优点 方便
缺点 二进制代码需要编译到exe中,浪费空间
注: 静态库的二进制代码保存在.lib中
生成静态库
在VS中可以创建静态库项目,建议创建空项目

PE文件解析基础

PE文件解析基础
在静态库项目中创建StaticLib.cpp源文件和StaticLib.h头文件
代码实例
1
2
3
4
5
6
7
8
//StaticLib.cpp 源文件保存函数代码
#include
void func() {
    printf("HelloStaticLib!");
}

//StaticLib.h 头文件保存函数声明
void func();

点击生成即可生成静态库得到.lib文件
使用静态库函数
在项目中找到StaticLib.lib和StaticLib.h文件,复制到需要使用该静态库的工程目录中

PE文件解析基础

PE文件解析基础
在工程目录中导入StaticLib.h文件

PE文件解析基础

PE文件解析基础

PE文件解析基础

PE文件解析基础
程序代码
1
2
3
4
5
6
7
#include  
#include "StaticLib.h"  //导入头文件,头文件有函数声明
#pragma comment(lib,"StaticLib.lib")//加载静态库,这里保存函数二进制代码
int main() {
    func();     //直接使用静态库中定义的函数即可
    return 0;
}

运行结果

PE文件解析基础

PE文件解析基础
动态库
动态库是在程序需要使用时加载,不同程序可以使用同一份dll,大大节省了空间
同创建静态库类似,建议使用空项目,创建好后将项目属性中的生成文件修改为动态库Dll即可
然后创建头文件和源文件
导出函数
生成动态库文件有关键字导出和.def导出两种方式
__declspec关键字导出函数
关键字功能解释
1
2
3
extern  //表示是全局函数 可供其他函数调用
"C"     //按照C语言的方式编译链接,此时函数名不变 C++由于有重载机制,编译出的函数符号名会比较复杂
__declspec(dllexport)//告诉编译器该函数为导出函数

代码实例
1
2
3
4
5
6
7
8
9
10
//DllTest.h
extern "C" __declspec(dllexport) void func();

//DllTest.cpp
//注意这里.cpp和.h的函数前都需要有__declspec(dllexport) 否则只会生成.dll而没有.lib
//注意都要有extern "C" 即保证函数声明和函数定义要一致
#include
extern "C" __declspec(dllexport) void func() {
    printf("HelloDynamicLib!");
}

点击生成即可得到.dll和.lib文件
.def文件导出函数
首先在源文件目录创建.def文件

PE文件解析基础

PE文件解析基础
代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//DllTest.cpp
#include
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导出的函数只有序号没有函数名

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

PE文件解析基础

PE文件解析基础
点击生成即可得到.dll和.lib文件
注意NONAME导出的函数只有序号没有函数名,原来的sub函数在这里是Oridinal_3

PE文件解析基础

PE文件解析基础
使用dll隐式链接
基本步骤
  • 将.dll .lib放到工程目录中
  • 使用#pragma comment(lib,"dllname.lib")导入lib文件
    静态库的.lib文件保存二进制代码,而dll中的.lib文件仅仅是指示函数位置
  • 加入函数声明
    extern "C" __declspec(dllexport) void func();

具体示例
首先将.dll和.lib放到工程目录中

PE文件解析基础

PE文件解析基础
程序代码
1
2
3
4
5
6
#pragma comment(lib,"DllTest.lib")          //导入.lib
extern "C" __declspec(dllimport) void func();//导入函数声明
int main() {
    func();//直接调用
    return 0;
}

运行结果

PE文件解析基础

PE文件解析基础
显式链接
基本使用方法
1
2
3
4
5
6
7
8
9
10
11
//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
2
3
4
5
6
7
8
9
10
#include//包含了win32的函数和数据结构
#include
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;
}

运行结果

PE文件解析基础

PE文件解析基础
数据目录表
定义
1
2
3
4
typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;//虚拟地址RVA,数据目录表的起始位置
    DWORD   Size;//大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

表项定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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个保留

可选头的最后两个成员分别定义了数据目录表的个数和数据目录表数组,指向了一些关键表格
1
2
DWORD   NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //数据目录表,结构体数组

比较重要的有导出表,导入表,重定位表,IAT表
打印数据目录表
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//打印数据目录表
    void PrintDirectory() {
        PIMAGE_DATA_DIRECTORY pDirectory = pOptionalHeader->DataDirectory;
        printf("**********数据目录表**********");
        for (DWORD i = 0; i < pOptionalHeader->NumberOfRvaAndSizes; i++) {
            switch (i) {
            case IMAGE_DIRECTORY_ENTRY_EXPORT:
                printf("==========导出表==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_IMPORT:
                printf("==========导入表==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_RESOURCE:
                printf("==========资源目录==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_EXCEPTION:
                printf("==========异常目录==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_SECURITY:
                printf("==========安全目录=========");
                break;
            case IMAGE_DIRECTORY_ENTRY_BASERELOC:
                printf("==========重定位基本表==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_DEBUG:
                printf("==========调试目录==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_ARCHITECTURE:
                printf("==========描述字串==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_GLOBALPTR:
                printf("==========机器值==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_TLS:
                printf("==========TLS目录==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG:
                printf("==========载入配置目录==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT:
                printf("==========绑定输入表==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_IAT:
                printf("==========导入地址表==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT:
                printf("==========延迟导入表==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR:
                printf("==========COM信息==========");
                break;
            case 15:
                printf("==========保留表==========");
                break;
            }
            printf("VirtualAddress=%xSize=%xFOA=%x", pDirectory.VirtualAddress, pDirectory.Size,RVA2FOA(pDirectory.VirtualAddress));
            
        }
        printf("**********数据目录表打印完毕**********");
    }

导出表
导出表记录了pe文件导出的函数,所以.exe和.dll程序都可以导出函数
数据目录表中记录了导出表的地址和偏移 这个地址是RVA,需要转换为FOA
1
2
3
4
5
6
7
8
9
10
11
12
13
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;

通过NONAME关键字可以使部分导出函数没有名称仅有地址
关键成员
1
2
3
4
5
6
7
Name               
Base               
NumberOfFunctions      
NumberOfNames         
AddressOfFunctions   
AddressOfNames      
AddressOfNameOrdinals

具体示例
假设:
导出函数名称表 FuncNameTable
导出函数序号表 FuncOridinalTable
导出函数地址表 FuncAddressTable
函数地址RVA FuncAddress
使用上一节的Dll函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//DllTest.def
LIBRARY "DllTest"
EXPORTS

func1 @15
plus @1
sub  @3 NONAME  //sub序号为3 无导出函数名
     
//DllTest.cpp
#include
#include
void func1() {
    printf("HelloDynamicLib!");
}
int plus(int x, int y) {
    return x + y;
}

int sub(int x, int y) {
    return x - y;
}

DLL的导出表信息

PE文件解析基础

PE文件解析基础
序号导出
假设已知导出函数序号OridinalNum
那么FuncAddress=FuncAddressTable[OridinalNum-Base]
即导出函数序号-Base值可以直接作为下标查找导出函数地址表得到导出函数地址
已知函数sub的导出序号为3 所以3-1=2直接查找得到其地址

PE文件解析基础

PE文件解析基础
所以无名函数的序号可以通过遍历导出函数地址表来得到
名称导出
通过函数名称查找函数地址的过程
  • 首先查找导出函数名称表,判断数组中哪个字符串和目的函数名称相同
  • 将该元素的下标作为索引,查找导出函数序号表
  • 将导出函数序号表中该下标元素的内容作为下标查找导出函数地址表,该值即为函数地址

1
2
if(strcmp(name,FuncNameTable)==0)
    FuncAddress=FuncAddressTable[FuncOridinalTable];

假设要查找plus函数
plus这个函数名在函数名称表中的下标为1
而FuncOridinalTable[1]=0
所以plus Address=FuncAddressTable[0]=1125d

PE文件解析基础

PE文件解析基础
注意
  • 导出函数地址表中有很多地址为0的项目
由于导出函数地址表的大小=NumberOfFunctions=导出函数最大序号-最小序号
当序号不是连续时,就会用0地址填充多余表项

PE文件解析基础

PE文件解析基础
  • 导出函数序号表存储的序号是真实序号-Base
    所以序号最小的导出函数对应的存储序号是0
  • 导出函数序号表中不保存无名称函数的序号
    通过序号查找函数时,将序号值-Base直接作为下标查找导出函数地址表

通过序号查找函数地址
1
2
3
4
5
//通过函数序号获取函数地址
DWORD GetFuncAddrByOridinals(WORD OridinalNum) {
    DWORD* pExportFuncAddressTable = (DWORD*)(RVA2FOA(pExportDirectory->AddressOfFunctions) + FileBuffer);//导出函数地址表
    return pExportFuncAddressTable[OridinalNum - pExportDirectory->Base];//减去Base值作为索引直接查找函数地址
}

通过函数名查找函数地址
代码
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
//通过函数名获取函数地址
    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)+FileBuffer)) == 0)
            {
                pos = i;
                break;
            }
        }
        if (pos == -1)//查找失败
            return 0;

        //2. 通过序号表得到序号
        OridinalNum = pExportFuncOridinalsTable[pos];

        //3. 得到函数地址
        return pExportFuncAddressTable[OridinalNum];
    }

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

PE文件解析基础

PE文件解析基础
打印导出表
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//根据函数序号返回函数名地址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 == OridinalNum)//实际存储的序号=函数序号-base
                return RVA2FOA(pExportFuncNamesTable)+FileBuffer;
        }
        return NULL;//没有找到说明是无名函数
    }
//打印导出表详细信息
    void PrintExportDirectory() {
        printf("==========导出表==========");
        printf("Name: %x (%s)",pExportDirectory->Name,(char*)(FileBuffer+RVA2FOA(pExportDirectory->Name)));
        printf("Base: %x", pExportDirectory->Base);
        printf("NumberOfFunctions:         %x", pExportDirectory->NumberOfFunctions);
        printf("NumberOfNames:                 %x", pExportDirectory->NumberOfNames);
        printf("AddressOfFunctions:         RVA=%x        FOA=%x", pExportDirectory->AddressOfFunctions,RVA2FOA(pExportDirectory->AddressOfFunctions));
        printf("AddressOfNames:         RVA=%x        FOA=%x", pExportDirectory->AddressOfNames, RVA2FOA(pExportDirectory->AddressOfNames));
        printf("AddressOfNameOrdinals:         RVA=%x        FOA=%x", 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("Oridinal             RVA             FOA        FunctionName");
        
        for (DWORD i = 0; i < pExportDirectory->NumberOfFunctions; i++) {
            if (pExportFuncAddressTable == 0)//地址为零则跳过
                continue;
            BYTE* FuncName = NULL;
            //由于导出函数序号表仅保存有名函数序号,所以有序号必定有名称,否则无名称
            //函数序号=函数地址表下标+Base
            printf("%08x        %08x        %08x        ",i+pExportDirectory->Base, pExportFuncAddressTable,RVA2FOA(pExportFuncAddressTable));
            //是否存在函数名要单独判断 存储序号=函数序号-Base,故传递i即可
            if (FuncName = GetFuncNameByOridinals(i))
                printf("%s", FuncName);
            else
                printf("NONAME");
        }
        printf("==========导出表结束==========");
    }

运行结果

PE文件解析基础

PE文件解析基础
查看二进制文件
导出函数地址表 从7CC8开始共0xE个表项

PE文件解析基础

PE文件解析基础
导出函数序号表 从7D0C开始共2个表项 每个表项2字节
存储序号分别是0xe(f func1) 0x0(1 plus)

PE文件解析基础

PE文件解析基础
导出函数名称表 从7D04开始共2个表项 每个表项四字节 指向字符串指针

PE文件解析基础

PE文件解析基础
重定位表重定位
重定位的概念: 进程拥有独立的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个表项指向了重定位表
1
2
3
4
5
6
7
8
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;

重定位表是一块一块存储的,每块的大小不一定相等,通过重定位表起始地址+SizeOfBlock可以查找下一块数据
重定位表的每个块会存储每一页(1000h)需要修改的表项 VirtualAddress即是页面起始地址
所以真正需要修复的地址=VirtualAddress+表项地址
假设VirtualAddress=8000 表项存储 12 34 56 78
那么需要修改的地址为8012 8034 8056 8078 (不考虑下面的高四位标识)
每个重定位项占2字节 其中高四位用于表示这个地址是否需要修改,低12位用于存储偏移值
如果高四位=0011那么需要修改
注意: 由于内存对齐的需要,假设表项有5个共10字节,那么实际表项会多一个空项用于内存对齐
结束时重定位表结构全为0

PE文件解析基础

PE文件解析基础
查看重定位表

PE文件解析基础

PE文件解析基础
打印重定位表
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//通过RVA判断所属区段名
PCHAR GetSectionNameByRva(DWORD RVA) {
    for (DWORD i = 0; i < numberOfSections; i++) {
        if (RVA >= pSectionHeader.VirtualAddress && RVA < pSectionHeader.VirtualAddress + AlignSize(pSectionHeader.Misc.VirtualSize, 0x1000))//成功找到所属节区
            return (PCHAR)pSectionHeader.Name;
    }
}
//打印重定位表的某个块
void PrintRelocationBlock(PIMAGE_BASE_RELOCATION pRelocationBlock) {
    PWORD pBlock = (PWORD)((DWORD)pRelocationBlock + 8);//注意每个表项占2字节 但是高4位用来判断是否需要修改
    DWORD PageOffset = pRelocationBlock->VirtualAddress;//每个块的虚拟地址即为页面起始地址

    printf("序号        属性             RVA             FOA        指向RVA");
    for (DWORD i = 0; i < (pRelocationBlock->SizeOfBlock - 8) / 2; i++) {
        //每块高四位用作属性判断,低12位才是页内偏移值 还要注意与运算优先级低于+ 不用括号会导致出错
        //指向的RVA即需要矫正的地址
        printf("%04x        %4x        %08x        %08x        %08x", i, pBlock >> 12, (pBlock & 0x0fff) + PageOffset, RVA2FOA((pBlock & 0x0fff) + PageOffset), *(DWORD*)(FileBuffer + RVA2FOA((pBlock & 0x0fff) + PageOffset)) & 0x00ffffff);
    }
}

//打印重定位表
void PrintRelocationTable() {
    PIMAGE_BASE_RELOCATION pRelocationTable = pBaseRelocation;
    printf("==========重定位表==========");
    printf("序号            区段             RVA             FOA        项目数");

    //表块全为0时结束
    DWORD count = 0;
    while (pRelocationTable->VirtualAddress || pRelocationTable->SizeOfBlock) {
        //项目数=(sizeofBlock-8)/2
        printf("%4d        %8s        %08x        %08x        %08x", 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("==========Block%d==========", count++);
        PrintRelocationBlock(pRelocationTable);//打印第i个块
        pRelocationTable = (PIMAGE_BASE_RELOCATION)((DWORD)pRelocationTable + pRelocationTable->SizeOfBlock);
    }

    printf("==========重定位表结束==========");
}

运行结果

PE文件解析基础

PE文件解析基础
导入表
数据目录表第2个表项是导入表,紧跟在导出表后
PE文件可能会有多个导入表以结构体数组形式存在,结束标识为全0
1
2
3
4
5
6
7
8
9
10
11
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
    DWORD   Characteristics;
    DWORD   OriginalFirstThunk; //RVA 指向INT 导入名称表 存储导入函数名称 IMAGE_THUNK_DATA结构数组
} DUMMYUNIONNAME;
    DWORD   TimeDateStamp;      //时间戳 如果该值为0则说明dll未被绑定 如果为-1则说明该dll被绑定
    DWORD   ForwarderChain;
    DWORD   Name;              //RVA 指向dll名 以0结尾
    DWORD   FirstThunk;         //RVA 指向IAT 导入地址表 存储导入函数地址 IMAGE_THUNK_DATA结构数组
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

重要成员
OriginalFirstThunk 指向INT表
FirstThunk 指向IAT表
INT和IAT
INT: 导入名称表 存储导入函数名称
IAT: 导入地址表 存储导入函数地址
这两张表的定义如下
1
2
3
4
5
6
7
8
9
typedef struct _IMAGE_THUNK_DATA32 {
    union {
        DWORD ForwarderString;      //PBYTE
        DWORD Function;             //PDWORD
        DWORD Ordinal;             //按序号导入的函数序号
        DWORD AddressOfData;        //PIMAGE_IMPORT_BY_NAME 指向导入函数名称结构
    } u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;

IAT表中,该结构存储的是导入函数地址RVA
INT表中,该结构可能存储导入函数序号或者是导入函数名称结构地址RVA
  • 当最高位为1时表示按序号导入,此时低31位作为序号值
  • 当最高位为0时表示按名称导入,此时低31位作为导入函数名称结构地址RVA
绑定导入
IAT表有两种情况
  • 在PE文件加载到内存前,两张表存储的内容一致,加载后修复IAT
    PE加载前 INT和IAT都指向IMAGE_IMPORT_BY_NAME 即导入函数名称结构

    PE文件解析基础

    PE文件解析基础
    加载到内存后 IAT表被修复 存储导入函数地址

    PE文件解析基础

    PE文件解析基础
  • PE文件加载前IAT表已经修复过
    此时IAT已经保存了导入函数地址 地址=RVA+ImageBase,这就是绑定导入
    导入表的TimeDateStamp为-1时表示已经进行绑定导入,如果为0表示没有绑定导入
    优点: 启动程序快
    缺点: 如果没有加载到正确基址仍然需要修复IAT表

打印导入表
以DllTest.dll为例,用PEStudy打开
上方区域有三张导入表,分别列出了dllname以及它们INT和IAT的位置
下方区域第一列是IAT表项(即导入函数地址) 第二列是INT表项的FOA(ThunkData) 第三列是INT表项指向的值(*ThunkData)

PE文件解析基础

PE文件解析基础
打印导入表的代码
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//打印INT表
   void PrintINT(PIMAGE_IMPORT_DESCRIPTOR pImportTable) {
       printf("==========INT==========");
       printf("ThunkRVA        ThunkFOA        ThunkVal        FuncName");
       PIMAGE_THUNK_DATA32 pThunkData = (PIMAGE_THUNK_DATA32)(RVA2FOA(pImportTable->OriginalFirstThunk) + FileBuffer);
       while (pThunkData->u1.Ordinal) {
           //最高位为1时表示按序号导入,低31位作为序号值
           printf("%08x        %08x        %08x        ", FOA2RVA((DWORD)pThunkData - (DWORD)FileBuffer), (DWORD)pThunkData - (DWORD)FileBuffer, pThunkData->u1);
           if (pThunkData->u1.Ordinal & 0x80000000) {
               printf("%08x", pThunkData->u1.Ordinal & 0x7FFFFFFF);
           }
           //最高位为0时表示按函数名称导入,值作为指向IMAGE_IMPORT_BY_NAME结构体地址的RVA
           else
           {
               PIMAGE_IMPORT_BY_NAME pImportName = (PIMAGE_IMPORT_BY_NAME)(RVA2FOA(pThunkData->u1.AddressOfData) + FileBuffer);
               printf("%s", pImportName->Name);
           }
           pThunkData++;
       }
   }
   //打印IAT表
   void PrintIAT(PIMAGE_IMPORT_DESCRIPTOR pImportTable) {
       printf("==========IAT==========");
       PDWORD pThunkData = (PDWORD)(RVA2FOA(pImportTable->FirstThunk) + FileBuffer);
       printf(" FuncRVA         FuncFOA        FuncAddr");
           while (*pThunkData) {
               printf("%08x        %08x        %08x", *pThunkData,RVA2FOA(*pThunkData), *pThunkData+imageBase);
               pThunkData++;
           }
   }
   //打印导入表
   void PrintImportTable() {
       PIMAGE_IMPORT_DESCRIPTOR pImportTable = pImportDescriptor;
       printf("**********导入表**********");
       printf("DllName                         INT RVA        TimeStamp        IAT RVA");
       while (pImportTable->OriginalFirstThunk) {
           printf("%-24s%08x        %08x        %08x", (RVA2FOA(pImportTable->Name) + FileBuffer),pImportTable->OriginalFirstThunk,pImportTable->TimeDateStamp,pImportTable->FirstThunk);
           pImportTable++;
       }

       pImportTable = pImportDescriptor;
       while (pImportTable->OriginalFirstThunk) {

           printf("==========DllName:%s==========", RVA2FOA(pImportTable->Name) + FileBuffer);
           PrintINT(pImportTable);
           PrintIAT(pImportTable);

           pImportTable++;
       }
       printf("**********导入表**********");
   }

运行结果

PE文件解析基础

PE文件解析基础
参考资料完整代码
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
#include
#include
#include
#include
using namespace std;

class PEFile {
private:
    HANDLE hFile;                                   //文件句柄
    HANDLE hProcess;                                //进程句柄
    DWORD ProcessBaseAddr;                          //进程基址
    BYTE* FileBuffer;                               //文件缓冲指针
    BYTE* imageBuffer;                              //映像缓冲指针
    DWORD fileBufferSize;                           //文件缓冲大小
    DWORD imageBufferSize;                          //映像缓冲大小

    //FileBuffer的各个指针
    PIMAGE_DOS_HEADER pDosHeader;                   //Dos头
    PIMAGE_NT_HEADERS pNtHeader;                    //NT头
    PIMAGE_FILE_HEADER pFileHeader;                 //标准PE头
    PIMAGE_OPTIONAL_HEADER pOptionalHeader;         //扩展PE头
    PIMAGE_DATA_DIRECTORY pDataDirectory;           //数据目录表
    PIMAGE_EXPORT_DIRECTORY pExportDirectory;       //导出表
    PIMAGE_BASE_RELOCATION pBaseRelocation;         //重定位表
    PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor;     //导入表
    PIMAGE_SECTION_HEADER pSectionHeader;           //节表


    //dos头关键成员
    WORD dosSignature;          //dos签名
    LONG   NToffset;            //nt头偏移

    //NT头关键成员
    DWORD peSignature;

    //标准PE头关键成员
    WORD Machine;               //cpu型号
    DWORD numberOfSections;     //节区数
    WORD sizeOfOptionalHeader;  //可选pe头大小

    //可选PE头关键成员
    DWORD addressOfEntryPoint;  //程序入口点EP
    DWORD imageBase;            //内存镜像基址
    DWORD sectionAlignment;     //内存对齐大小
    DWORD fileAlignment;        //文件对齐大小
    DWORD sizeOfImage;          //内存映像大小
    DWORD sizeOfHeaders;        //各种头的大小


    //初始化各个表头指针
    void InitHeaders() {
        pDosHeader = (IMAGE_DOS_HEADER*)FileBuffer;//DOS头
        pNtHeader = (IMAGE_NT_HEADERS*)(FileBuffer + pDosHeader->e_lfanew);//NT头
        pFileHeader = (IMAGE_FILE_HEADER*)((DWORD)pNtHeader + sizeof(DWORD));//标准PE头
        pOptionalHeader = (IMAGE_OPTIONAL_HEADER*)((DWORD)pFileHeader + sizeof(IMAGE_FILE_HEADER));//可选PE头
        pSectionHeader = (IMAGE_SECTION_HEADER*)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);//节表
        pDataDirectory = (PIMAGE_DATA_DIRECTORY)(pOptionalHeader->DataDirectory);//数据目录表
        pBaseRelocation = (PIMAGE_BASE_RELOCATION)(FileBuffer + RVA2FOA(pDataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress));//重定位表
        pImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(FileBuffer + RVA2FOA(pDataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress));//导入表
        if (pDataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress != 0)//如果存在导出表则获取导出表地址,否则置空
            pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(FileBuffer + RVA2FOA(pDataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress));//导出表
        else pExportDirectory = NULL;

    }

    //初始化FileBuffer关键成员
    void InitKeyMembers() {

        //dos头
        dosSignature = pDosHeader->e_magic;
        NToffset = pDosHeader->e_lfanew;

        //NT头
        peSignature = pNtHeader->Signature;

        //标准PE头 20字节
        Machine = pFileHeader->Machine;
        numberOfSections = pFileHeader->NumberOfSections;
        sizeOfOptionalHeader = pFileHeader->SizeOfOptionalHeader;

        //可选头,根据32/64位有不同大小
        addressOfEntryPoint = pOptionalHeader->AddressOfEntryPoint;
        imageBase = pOptionalHeader->ImageBase;
        sectionAlignment = pOptionalHeader->SectionAlignment;
        fileAlignment = pOptionalHeader->FileAlignment;
        sizeOfImage = pOptionalHeader->SizeOfImage;
        sizeOfHeaders = pOptionalHeader->SizeOfHeaders;
    }

    //打印DOS头
    void showDosHeader() {
        printf("----------DosHeader----------");
        printf("DosSignature: %x", dosSignature);
        printf("NtHeaderOffset: %x", NToffset);
        printf("----------DosHeader----------");
    }

    //打印标准Pe头
    void showFileHeader() {
        printf("----------FileHeader----------");
        printf("Machine: %x", Machine);
        printf("NumberOfSections: %x", numberOfSections);
        printf("SizeOfOptionalHeader: %x", sizeOfOptionalHeader);
        printf("----------FileHeader----------");
    }

    //打印可选PE头
    void showOptionalHeader() {
        printf("----------OptionalHeader----------");
        printf("EntryPoint: %x", addressOfEntryPoint);
        printf("ImageBase: %x", imageBase);
        printf("SectionAlignment: %x", sectionAlignment);
        printf("FileAlignment: %x", fileAlignment);
        printf("SizeOfImage; %x", sizeOfImage);
        printf("SizeOfHeaders: %x", sizeOfHeaders);
        printf("----------OptionalHeader----------");
    }

    //打印NT头
    void showNtHeader() {
        printf("-----------NtHeader----------");
        printf("eSignature: %x", peSignature);
        showFileHeader();
        showOptionalHeader();
        printf("-----------NtHeader----------");
    }

    //打印节表
    void showSectionHeaders() {
        printf("----------SectionHeaders----------");
        for (DWORD i = 0; i < numberOfSections; i++) {
            //逐个读取节表并打印
            printf("----------Section%d----------", i);
            printf("Name: %8s", pSectionHeader.Name);
            printf("VirtualSize: %x", pSectionHeader.Misc.VirtualSize);
            printf("VirtualAddress: %x", pSectionHeader.VirtualAddress);
            printf("SizeOfRawData: %x", pSectionHeader.SizeOfRawData);
            printf("ointerToRawData: %x", pSectionHeader.PointerToRawData);
            printf("Characteristics: %x", pSectionHeader.Characteristics);
            printf("----------Section%d----------", i);
        }
        printf("----------SectionHeaders----------");
    }

    //设置FileBuffer
    void SetFileBuffer(BYTE* NewFileBuffer) {
        if (FileBuffer)
            delete[] FileBuffer;       //删除原始空间
        FileBuffer = NewFileBuffer;//指向新的空间
        Init();                    //初始化
    }

    //设置ImageBuffer
    void SetImageBuffer(BYTE* NewImageBuffer) {
        if (imageBuffer)
            delete[] imageBuffer;
        imageBuffer = NewImageBuffer;
    }

    //将FileBuffer拉伸成为ImageBuffer
    void FileBufferToImageBuffer() {
        //1. 申请空间用于存储Image
        imageBuffer = new BYTE[sizeOfImage];
        imageBufferSize = sizeOfImage;
        if (!imageBuffer)
        {
            printf("申请空间失败!");
            system("pause");
            return;
        }
        memset(imageBuffer, 0, sizeOfImage);            //初始化内存空间,全部清零
        memcpy(imageBuffer, FileBuffer, sizeOfHeaders); //直接复制各个表头

        //2. 拉伸FileBuffer并写入ImageBuffer
        for (DWORD i = 0; i < numberOfSections; i++) {
            memcpy(imageBuffer + pSectionHeader.VirtualAddress, FileBuffer + pSectionHeader.PointerToRawData, pSectionHeader.SizeOfRawData);
            //起始地址是imageBase+节区起始地址RVA SizeOfData是节区在文件中保存的数据
            //不使用VirtualSize的原因是例如.textbss段 SizeOfData=0 VirtualSize=10000
            //显然在文件中没有数据需要写入内存,只是在内存中占用那么多大小的空间而已
        }
    }

    //将ImageBuffer压缩为FileBuffer
    void ImageBufferToFileBuffer() {
        //1. 申请空间用于存储ImageBuffer压缩后的FileBuffer
        DWORD NewFileBufferSize = pSectionHeader[numberOfSections - 1].PointerToRawData + pSectionHeader[numberOfSections - 1].SizeOfRawData;
        BYTE* NewFileBuffer = new BYTE[NewFileBufferSize];//最后一个节区的文件起始地址+文件大小即为PE文件大小
        memset(NewFileBuffer, 0, NewFileBufferSize);

        //2. 将ImageBuffer的内容压缩并写入FileBuffer
        for (DWORD i = 0; i < numberOfSections; i++) //复制节区
        {
            memcpy(NewFileBuffer + pSectionHeader.PointerToRawData, imageBuffer + pSectionHeader.VirtualAddress, pSectionHeader.SizeOfRawData);
            //节区文件偏移起始地址 节区内存偏移起始地址 节区文件大小
            //注意这里第三个参数不要使用VirtualSize 否则可能会导致缓冲区溢出
            //(例如: .textbss段在文件中占用空间为0 但是内存中的大小为0x10000 所以这段没有必要写入文件中)
        }
        memcpy(NewFileBuffer, imageBuffer, sizeOfHeaders); //复制各个表头
        SetFileBuffer(NewFileBuffer);                      //重新设置FileBuffer
    }

    //获取进程基址
    DWORD GetProcessBaseAddress(HANDLE hProcess) {
        HMODULE hMods[1024];
        DWORD cbNeeded;
        DWORD baseAddress = 0;

        if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
            for (unsigned int i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) {
                TCHAR szModName[MAX_PATH];
                if (GetModuleFileNameEx(hProcess, hMods, szModName,
                    sizeof(szModName) / sizeof(TCHAR))) {
                    MODULEINFO moduleInfo;
                    if (GetModuleInformation(hProcess, hMods, &moduleInfo, sizeof(moduleInfo))) {
                        baseAddress = (uintptr_t)moduleInfo.lpBaseOfDll;
                        break; // We found the first module's base address
                    }
                }
            }
        }
        return baseAddress;
    }

public:
    //创建进程并获取进程基址
    BOOL CreateProcessWrapper(LPCTSTR applicationName, LPTSTR commandLine) {
        STARTUPINFO startupInfo;
        PROCESS_INFORMATION processInfo;
        ZeroMemory(&startupInfo, sizeof(startupInfo));
        startupInfo.cb = sizeof(startupInfo);

        BOOL success = CreateProcess(
            applicationName,
            commandLine,
            NULL, NULL, FALSE, 0, NULL, NULL,
            &startupInfo,
            &processInfo
        );
        if (success) {
            hProcess = processInfo.hProcess;
            ProcessBaseAddr = GetProcessBaseAddress(hProcess);
        }
        return success;
    }

    //将FileBuffer写入文件
    BOOL FileBufferWriteToFile(const WCHAR* FileName) {
        //创建文件 注意这里第三个参数不能用GENERIC_ALL 推测是由于可执行权限导致出错 仅读写没有问题
        //CREATE_ALWAYS 无论文件是否存在都会写入
        HANDLE hFile = CreateFile(FileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
        if (hFile == INVALID_HANDLE_VALUE)
            return FALSE;
        return WriteFile(hFile, FileBuffer, fileBufferSize, NULL, NULL); // 写入文件
    }

    //将ImageBuffer写入文件
    BOOL ImageBufferWriteToFile(const WCHAR* FileName) {
        HANDLE hFile = CreateFile(FileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
        if (hFile == INVALID_HANDLE_VALUE)
            return FALSE;
        return WriteFile(hFile, imageBuffer, sizeOfImage, NULL, NULL);
    }

    //扩大filebuffer大小 ExSize为文件对齐后的额外空间大小
    void ExpandFileBuffer(DWORD ExSize) {
        BYTE* NewBuffer = new BYTE[fileBufferSize + ExSize];
        memset(NewBuffer + fileBufferSize, 0, ExSize);//额外空间清零
        memcpy(NewBuffer, FileBuffer, fileBufferSize);//复制原始数据
        fileBufferSize += ExSize;//调整大小
        SetFileBuffer(NewBuffer);
    }

    //扩大imgaebuffer大小
    void ExpandImageBuffer(DWORD ExSize) {
        BYTE* NewBuffer = new BYTE[imageBufferSize + ExSize];
        memset(NewBuffer + imageBufferSize, 0, ExSize);
        memcpy(NewBuffer, imageBuffer, imageBufferSize);
        imageBufferSize += ExSize;
        SetImageBuffer(NewBuffer);
    }

    PEFile(LPCWCHAR FileName) {
        hFile = CreateFile(FileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);  //打开文件
        if (!hFile) {
            printf("OpenFileFailure!");
            exit(0);
        }

        fileBufferSize = GetFileSize(hFile, NULL);   //获取文件大小
        FileBuffer = new BYTE[fileBufferSize];     //分配内存空间用于存储文件

        if (!FileBuffer) {
            printf("AllocFileBufferMemoryFailure!");
            exit(0);
        }

        if (!ReadFile(hFile, FileBuffer, fileBufferSize, NULL, NULL)) //读取文件并存储到内存中
        {
            delete[] FileBuffer;
            printf("ReadFileFailure!");
            exit(0);
        }

        CloseHandle(hFile);//读取完后关闭文件

        InitHeaders();
        InitKeyMembers();
        FileBufferToImageBuffer();//创建ImageBuffer

        hProcess = NULL;
        ProcessBaseAddr = 0;
    }

    //初始化表头指针和关键变量
    void Init() {
        InitHeaders();
        InitKeyMembers();
        //InitImageHeaders();
    }

    //打印Pe文件信息
    void showPeFile() {
        showDosHeader();
        showNtHeader();
        showSectionHeaders();
        PrintDirectory();
        PrintExportDirectory();
        PrintRelocationTable();
        PrintImportTable();
    }

    //打印数据目录表
    void PrintDirectory() {
        PIMAGE_DATA_DIRECTORY pDirectory = pOptionalHeader->DataDirectory;
        printf("**********数据目录表**********");
        for (DWORD i = 0; i < pOptionalHeader->NumberOfRvaAndSizes; i++) {
            switch (i) {
            case IMAGE_DIRECTORY_ENTRY_EXPORT:
                printf("==========导出表==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_IMPORT:
                printf("==========导入表==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_RESOURCE:
                printf("==========资源目录==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_EXCEPTION:
                printf("==========异常目录==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_SECURITY:
                printf("==========安全目录=========");
                break;
            case IMAGE_DIRECTORY_ENTRY_BASERELOC:
                printf("==========重定位基本表==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_DEBUG:
                printf("==========调试目录==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_ARCHITECTURE:
                printf("==========描述字串==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_GLOBALPTR:
                printf("==========机器值==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_TLS:
                printf("==========TLS目录==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG:
                printf("==========载入配置目录==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT:
                printf("==========绑定输入表==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_IAT:
                printf("==========导入地址表==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT:
                printf("==========延迟导入表==========");
                break;
            case IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR:
                printf("==========COM信息==========");
                break;
            case 15:
                printf("==========保留表==========");
                break;
            }
            printf("VirtualAddress=%xSize=%xFOA=%x", pDirectory.VirtualAddress, pDirectory.Size, RVA2FOA(pDirectory.VirtualAddress));

        }
        printf("**********数据目录表打印完毕**********");


    }

    //通过函数名获取导出函数地址
    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) + FileBuffer)) == 0)
            {
                pos = i;
                break;
            }
        }
        if (pos == -1)//查找失败
            return 0;

        //2. 通过序号表得到序号
        OridinalNum = pExportFuncOridinalsTable[pos];

        //3. 得到函数地址
        return pExportFuncAddressTable[OridinalNum];
    }

    //通过函数序号获取导出函数地址
    DWORD GetFuncAddrByOridinals(WORD OridinalNum) {
        DWORD* pExportFuncAddressTable = (DWORD*)(RVA2FOA(pExportDirectory->AddressOfFunctions) + FileBuffer);//导出函数地址表
        return pExportFuncAddressTable[OridinalNum - pExportDirectory->Base];//减去Base值作为索引直接查找函数地址
    }

    //根据导出函数序号返回导出函数名
    PCHAR 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 == OridinalNum)//实际存储的序号=函数序号-base
                return (PCHAR)(RVA2FOA(pExportFuncNamesTable) + FileBuffer);
        }
        return NULL;//没有找到说明是无名函数
    }

    //打印导出表详细信息
    void PrintExportDirectory() {
        //不存在导出表
        if (!pExportDirectory)
        {
            printf("**********不存在导出表**********");
            return;
        }
        printf("==========导出表==========");
        printf("Name: %x (%s)", pExportDirectory->Name, (char*)(FileBuffer + RVA2FOA(pExportDirectory->Name)));
        printf("Base: %x", pExportDirectory->Base);
        printf("NumberOfFunctions:         %x", pExportDirectory->NumberOfFunctions);
        printf("NumberOfNames:                 %x", pExportDirectory->NumberOfNames);
        printf("AddressOfFunctions:         RVA=%x        FOA=%x", pExportDirectory->AddressOfFunctions, RVA2FOA(pExportDirectory->AddressOfFunctions));
        printf("AddressOfNames:         RVA=%x        FOA=%x", pExportDirectory->AddressOfNames, RVA2FOA(pExportDirectory->AddressOfNames));
        printf("AddressOfNameOrdinals:         RVA=%x        FOA=%x", 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("Oridinal             RVA             FOA        FunctionName");

        for (DWORD i = 0; i < pExportDirectory->NumberOfFunctions; i++) {
            if (pExportFuncAddressTable == 0)//地址为零则跳过
                continue;
            PCHAR FuncName = NULL;
            //由于导出函数序号表仅保存有名函数序号,所以有序号必定有名称,否则无名称
            //函数序号=函数地址表下标+Base
            printf("%08x        %08x        %08x        ", i + pExportDirectory->Base, pExportFuncAddressTable, RVA2FOA(pExportFuncAddressTable));
            //是否存在函数名要单独判断 存储序号=函数序号-Base,故传递i即可
            if (FuncName = GetFuncNameByOridinals(i))
                printf("%s", FuncName);
            else
                printf("NONAME");
        }
        printf("==========导出表结束==========");
    }

    //打印重定位表的某个块
    void PrintRelocationBlock(PIMAGE_BASE_RELOCATION pRelocationBlock) {
        PWORD pBlock = (PWORD)((DWORD)pRelocationBlock + 8);//注意每个表项占2字节 但是高4位用来判断是否需要修改
        DWORD PageOffset = pRelocationBlock->VirtualAddress;//每个块的虚拟地址即为页面起始地址

        printf("序号        属性             RVA             FOA        指向RVA");
        for (DWORD i = 0; i < (pRelocationBlock->SizeOfBlock - 8) / 2; i++) {
            //每块高四位用作属性判断,低12位才是页内偏移值 还要注意与运算优先级低于+ 不用括号会导致出错
            //指向的RVA即需要矫正的地址
            printf("%04x        %4x        %08x        %08x        %08x", i, pBlock >> 12, (pBlock & 0x0fff) + PageOffset, RVA2FOA((pBlock & 0x0fff) + PageOffset), *(DWORD*)(FileBuffer + RVA2FOA((pBlock & 0x0fff) + PageOffset)) & 0x00ffffff);
        }
    }

    //打印重定位表
    void PrintRelocationTable() {
        PIMAGE_BASE_RELOCATION pRelocationTable = pBaseRelocation;
        printf("==========重定位表==========");
        printf("序号            区段             RVA             FOA        项目数");

        //表块全为0时结束
        DWORD count = 0;
        while (pRelocationTable->VirtualAddress || pRelocationTable->SizeOfBlock) {
            //项目数=(sizeofBlock-8)/2
            printf("%4d        %8s        %08x        %08x        %08x", 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("==========Block%d==========", count++);
            PrintRelocationBlock(pRelocationTable);//打印第i个块
            pRelocationTable = (PIMAGE_BASE_RELOCATION)((DWORD)pRelocationTable + pRelocationTable->SizeOfBlock);
        }

        printf("==========重定位表结束==========");
    }

    //打印INT表
    void PrintINT(PIMAGE_IMPORT_DESCRIPTOR pImportTable) {
        printf("==========INT==========");
        printf("ThunkRVA        ThunkFOA        ThunkVal        FuncName");
        PIMAGE_THUNK_DATA32 pThunkData = (PIMAGE_THUNK_DATA32)(RVA2FOA(pImportTable->OriginalFirstThunk) + FileBuffer);
        while (pThunkData->u1.Ordinal) {
            //最高位为1时表示按序号导入,低31位作为序号值
            printf("%08x        %08x        %08x        ", FOA2RVA((DWORD)pThunkData - (DWORD)FileBuffer), (DWORD)pThunkData - (DWORD)FileBuffer, pThunkData->u1.Ordinal);
            if (pThunkData->u1.Ordinal & 0x80000000) {
                printf("%08x", pThunkData->u1.Ordinal & 0x7FFFFFFF);
            }
            //最高位为0时表示按函数名称导入,值作为指向IMAGE_IMPORT_BY_NAME结构体地址的RVA
            else
            {
                PIMAGE_IMPORT_BY_NAME pImportName = (PIMAGE_IMPORT_BY_NAME)(RVA2FOA(pThunkData->u1.AddressOfData) + FileBuffer);
                printf("%s", pImportName->Name);
            }
            pThunkData++;
        }
    }
    //打印IAT表
    void PrintIAT(PIMAGE_IMPORT_DESCRIPTOR pImportTable) {
        printf("==========IAT==========");
        PDWORD pThunkData = (PDWORD)(RVA2FOA(pImportTable->FirstThunk) + FileBuffer);
        printf(" FuncRVA         FuncFOA        FuncAddr");
            while (*pThunkData) {
                printf("%08x        %08x        %08x", *pThunkData,RVA2FOA(*pThunkData), *pThunkData+imageBase);
                pThunkData++;
            }
    }
    //打印导入表
    void PrintImportTable() {
        PIMAGE_IMPORT_DESCRIPTOR pImportTable = pImportDescriptor;
        printf("**********导入表**********");
        printf("DllName                         INT RVA        TimeStamp        IAT RVA");
        while (pImportTable->OriginalFirstThunk) {
            printf("%-24s%08x        %08x        %08x", (RVA2FOA(pImportTable->Name) + FileBuffer),pImportTable->OriginalFirstThunk,pImportTable->TimeDateStamp,pImportTable->FirstThunk);
            pImportTable++;
        }

        pImportTable = pImportDescriptor;
        while (pImportTable->OriginalFirstThunk) {

            printf("==========DllName:%s==========", RVA2FOA(pImportTable->Name) + FileBuffer);
            PrintINT(pImportTable);
            PrintIAT(pImportTable);

            pImportTable++;
        }
        printf("**********导入表**********");
    }

    //通过RVA判断所属区段名
    PCHAR GetSectionNameByRva(DWORD RVA) {
        for (DWORD i = 0; i < numberOfSections; i++) {
            if (RVA >= pSectionHeader.VirtualAddress && RVA < pSectionHeader.VirtualAddress + AlignSize(pSectionHeader.Misc.VirtualSize, 0x1000))//成功找到所属节区
                return (PCHAR)pSectionHeader.Name;
        }
    }

    //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.VirtualAddress && RVA < pSectionHeader.VirtualAddress + AlignSize(pSectionHeader.Misc.VirtualSize, 0x1000))//成功找到所属节区
            {
                FOA = RVA - pSectionHeader.VirtualAddress + pSectionHeader.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.PointerToRawData && FOA < pSectionHeader.PointerToRawData + pSectionHeader.SizeOfRawData) {
                RVA = FOA - pSectionHeader.PointerToRawData + pSectionHeader.VirtualAddress;
                break;
            }
        }
        return RVA;

    }

    //输入原始大小和对齐值返回对齐后的大小
    DWORD AlignSize(DWORD OrigSize, DWORD AlignVal) {
        //通过对对齐值取模判断是否对齐,如果对齐则返回原值,否则返回对齐后的值
        return OrigSize % AlignVal ? (OrigSize / AlignVal + 1) * AlignVal : OrigSize;
    }

    //计算call/jmp指令的偏移值 目的地址-(当前指令地址+5)
    DWORD OffsetOfCallAndJmp(DWORD DesAddr, DWORD SelfAddr) {
        return DesAddr - (SelfAddr + 5);
    }

    //创建新的节区 返回新节区指针
    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.Name, ".text"))
            {
                memcpy(pNewSectionHeader, (LPVOID)&pSectionHeader, 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->ointerToRawData = 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->ointerToRawData, InjectCode, sizeof(InjectCode));//将代码写入新的内存空间           

        //3. 保存文件
        return FileBufferWriteToFile(L"InjectCodeByCreateNewSection1.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.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"CombineSection1.exe");
    }

    ~PEFile() {
        if (FileBuffer)          //释放空间
            delete[] FileBuffer;
        if (imageBuffer)
            delete[] imageBuffer;
        if (hProcess)
            CloseHandle(hProcess);
    }
};
int main() {
    //PEFile peFile = PEFile(L"C:\Users\admin\Desktop\DailyExercise.exe");
    PEFile peFile = PEFile(L"C:\Users\admin\Desktop\DllTest.dll");
    peFile.showPeFile();
    return 0;
}

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


提取码下载:
文件名称:提取码下载.txt 
下载次数:0  文件大小:13 Bytes  售价:5金钱 [记录]
下载权限: 不限 [购买VIP]   [充值]   [在线充值]   【VIP会员5折;永久VIP免费】
安全检测,请放心下载




相关帖子

扫码关注微信公众号,及时获取最新资源信息!下载附件优惠VIP会员5折;永久VIP免费
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

免责声明:
1、本站提供的所有资源仅供参考学习使用,版权归原著所有,禁止下载本站资源参与商业和非法行为,请在24小时之内自行删除!
2、本站所有内容均由互联网收集整理、网友上传,并且以计算机技术研究交流为目的,仅供大家参考、学习,请勿任何商业目的与商业用途。
3、若您需要商业运营或用于其他商业活动,请您购买正版授权并合法使用。
4、论坛的所有内容都不保证其准确性,完整性,有效性,由于源码具有复制性,一经售出,概不退换。阅读本站内容因误导等因素而造成的损失本站不承担连带责任。
5、用户使用本网站必须遵守适用的法律法规,对于用户违法使用本站非法运营而引起的一切责任,由用户自行承担
6、本站所有资源来自互联网转载,版权归原著所有,用户访问和使用本站的条件是必须接受本站“免责声明”,如果不遵守,请勿访问或使用本网站
7、本站使用者因为违反本声明的规定而触犯中华人民共和国法律的,一切后果自己负责,本站不承担任何责任。
8、凡以任何方式登陆本网站或直接、间接使用本网站资料者,视为自愿接受本网站声明的约束。
9、本站以《2013 中华人民共和国计算机软件保护条例》第二章 “软件著作权” 第十七条为原则:为了学习和研究软件内含的设计思想和原理,通过安装、显示、传输或者存储软件等方式使用软件的,可以不经软件著作权人许可,不向其支付报酬。若有学员需要商用本站资源,请务必联系版权方购买正版授权!
10、本网站如无意中侵犯了某个企业或个人的知识产权,请来信【站长信箱312337667@qq.com】告之,本站将立即删除。
郑重声明:
本站所有资源仅供用户本地电脑学习源代码的内含设计思想和原理,禁止任何其他用途!
本站所有资源、教程来自互联网转载,仅供学习交流,不得商业运营资源,不确保资源完整性,图片和资源仅供参考,不提供任何技术服务。
本站资源仅供本地编辑研究学习参考,禁止未经资源商正版授权参与任何商业行为,违法行为!如需商业请购买各资源商正版授权
本站仅收集资源,提供用户自学研究使用,本站不存在私自接受协助用户架设游戏或资源,非法运营资源行为。
 
在线客服
点击这里给我发消息 点击这里给我发消息 点击这里给我发消息
售前咨询热线
312337667

微信扫一扫,私享最新原创实用干货

QQ|免责声明|小黑屋|依星资源网 ( 鲁ICP备2021043233号-3 )|网站地图

GMT+8, 2024-10-28 16:48

Powered by Net188.com X3.4

邮箱:312337667@qq.com 客服QQ:312337667(工作时间:9:00~21:00)

快速回复 返回顶部 返回列表