lyl610abc 发表于 2021-4-6 14:17

PE文件笔记十二 修正内存对齐

继续PE系列笔记的更新

PE其它笔记索引可前往:
(https://www.52pojie.cn/thread-1391994-1-1.html)

------

在前面(https://www.52pojie.cn/thread-1409844-1-1.html)中,需要修正节表成员;修正节表成员时要把Misc(实际大小)和 SizeOfRawData(文件对齐后的大小)修正为内存对齐后的大小

但在扩大节中,只针对最后一个节进行修正,没有影响到其它的成员,接下来学习将所有节表都修正为内存对齐

此篇笔记为学习节操作之修正内存对齐

# 修正内存对齐

## 为什么要修正内存对齐

修正内存对齐 使得 节文件对齐后的大小和内存对齐后的大小一致,方便后续合并节

------

## 修正内存对齐涉及的结构体成员

| 涉及的节表成员   | 含义                   |
| :--------------- | :--------------------- |
| Misc             | 节的实际大小         |
| SizeOfRawData    | 节在文件中对齐后的尺寸 |
| PointerToRawData | 节区在文件中的偏移   |

------

## 修正内存对齐的流程

1. 计算节内存对齐后的大小
2. 计算差值 = 节内存对齐后的大小 - 节文件对齐后的大小
3. 计算节在文件中的末尾位置 = 节在文件中的偏移 + 节文件对齐后的大小
4. 在节的文件中的末尾位置后填充新空间,新空间的大小为 前面计算的差值
5. 修正Misc和SizeOfRawData为节内存对齐后的大小
6. 在该节后面的节在文件中的偏移增加差值

------

## 按流程修正内存对齐

此次依旧以先前的EverEdit.exe为例进行修正内存对齐的演示,这次选择修正倒数第二个节

------

### 节信息

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210406130733632.png)

------

这里只关注最后两个节:

| 节名称 | Misc    | SizeOfRawData | PointerToRawData |
| ------ | ------- | ------------- | ---------------- |
| .rsrc| 0x62008 | 0x62200       | 0x1dfa00         |
| .reloc | 0x1643a | 0x16600       | 0x241c00         |

------

### 计算节内存对齐后的大小

计算节内存对齐后的大小在先前的笔记(https://www.52pojie.cn/thread-1409844-1-1.html#37855398_%E4%BF%AE%E6%AD%A3%E8%8A%82%E8%A1%A8%E6%88%90%E5%91%98)中已经说明过,这里不再赘述,直接计算

节内存对齐后的大小 = ( max{Misc,SizeOfRawData} ÷ SectionAlignment)向上取整 × SectionAlignment

即节内存对齐后的大小 = ( max{0x62008,0x62200}÷0x1000)向上取整 × 0x1000

即节内存对齐后的大小 = (0x62200 ÷ 0x1000)向上取整 × 0x1000 = 0x63000

------

### 计算差值

差值 = 节内存对齐后的大小 - 节文件对齐后的大小= 节内存对齐后的大小 - SizeOfRawData

即 差值 = 0x63000 - 0x62200 = 0xE00

------

### 计算节在文件中的末尾位置

节在文件中的末尾位置 = 节在文件中的偏移 + 节文件对齐后的大小

即节在文件中的末尾位置 = PointerToRawData + SizeOfRawData

即节在文件中的末尾位置 = 0x1dfa00 + 0x62200 = 0x241C00

------

### 填充新空间

用WinHex打开EverEdit.exe,找到前面计算出来的节在文件中的末尾位置

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210406133220273.png)

------

选中末尾后 编辑→粘贴0字节

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210406133338744.png)

------

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210406133302893.png)

选择插入的大小为:0xE00(对应十进制为3584),即插入前面计算出来的差值

------

插入后,保存

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210406133416207.png)

------

### 修正节成员

修正Misc和SizeOfRawData为节内存对齐后的大小:0x63000

这里为了省事,直接使用PE工具:DIE进行修改

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210406133728686.png)

------

修正后:

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210406133806124.png)

------

### 修正后面的节

在该节后面的节在文件中的偏移增加差值

该节后面只有一个

| 节名称 | Misc    | SizeOfRawData | PointerToRawData |
| ------ | ------- | ------------- | ---------------- |
| .reloc | 0x1643a | 0x16600       | 0x241c00         |

修改其PointerToRawData = PointerToRawData + 差值

即 PointerToRawData = 0x241c00 + 0xe00 = 242A00

同样使用PE工具:DIE进行修正

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210406134420140.png)

------

修改后

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210406134456131.png)

------

### 修正前后对比

执行完上一步,倒数第二个节就已经修正完了

对比一下修改前后节的信息

#### 修改前

| 节名称 | Misc    | SizeOfRawData | PointerToRawData |
| ------ | ------- | ------------- | ---------------- |
| .rsrc| 0x62008 | 0x62200       | 0x1dfa00         |
| .reloc | 0x1643a | 0x16600       | 0x241c00         |

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210406134727702.png)

#### 修改后

| 节名称 | Misc    | SizeOfRawData | PointerToRawData |
| ------ | ------- | ------------- | ---------------- |
| .rsrc| 0x63000 | 0x63000       | 0x1dfa00         |
| .reloc | 0x1643a | 0x16600       | 0x242a00         |

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210406134712043.png)

------

### 测试运行

发现仍然能够正常运行

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210404165912116.png)

------

# 代码实现修正内存对齐

```c
#include <stdio.h>
#include <malloc.h>
#include <windows.h>
#include <winnt.h>
#include <math.h>
//在VC6这个比较旧的环境里,没有定义64位的这个宏,需要自己定义,在VS2019中无需自己定义
#define IMAGE_FILE_MACHINE_AMD640x8664


//向文件中指定位置追加数据
//第一个参数为文件路径
//第二个参数为要追加的数据指针
//第三个参数为要追加的数据大小
//第四个参数为位置偏移
//第五个参数为hMap的指针
//第六个参数为pFile的指针
BOOL appendFile(LPCSTR filePath, PVOID writeData, DWORD sizeOfWriteData, DWORD offset, HANDLE* phMap, PVOID* ppFile) {
        HANDLE hFile = CreateFileA(filePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);
        char newPath;
        strcpy(newPath, filePath);

        strcat(newPath, ".exe");
        HANDLE hFile2 = CreateFileA(newPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, 0);

        //WriteFile用于接收实际写入的大小的参数
        DWORD dwWritenSize = 0;

        //根据文件句柄创建映射
        HANDLE hMap = CreateFileMappingA(hFile, NULL, PAGE_READWRITE, 0, 0, 0);
        //映射内容
        LPVOID pFile = MapViewOfFile(hMap, FILE_SHARE_WRITE, 0, 0, 0);

        BYTE* content = (BYTE*)pFile;
        content += offset;

        //写入要插入数据前的数据
        DWORD size = SetFilePointer(hFile, NULL, NULL, FILE_END);
        BOOL bRet;
        bRet = WriteFile(hFile2, pFile, offset, &dwWritenSize, NULL);
        if (!bRet)return false;
        //写入要插入的数据
        SetFilePointer(hFile, NULL, NULL, FILE_END);
        bRet = WriteFile(hFile2, writeData, sizeOfWriteData, &dwWritenSize, NULL);
        if (!bRet)return false;
        //写入要插入数据后的数据
        SetFilePointer(hFile, NULL, NULL, FILE_END);
        bRet = WriteFile(hFile2, content, size - offset, &dwWritenSize, NULL);
        if (!bRet)return false;
    //在删除文件前要先关闭句柄和映射
        CloseHandle(hFile);
        CloseHandle(hMap);
        CloseHandle(*phMap);
        UnmapViewOfFile(pFile);
        UnmapViewOfFile(*ppFile);
        bRet = DeleteFileA(filePath);
        if (!bRet)return false;

        hFile = CreateFileA(filePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, 0);
        //根据文件句柄创建映射
        hMap = CreateFileMappingA(hFile2, NULL, PAGE_READWRITE, 0, 0, 0);
        //映射内容
        pFile = MapViewOfFile(hMap, FILE_SHARE_WRITE, 0, 0, 0);
        SetFilePointer(hFile, NULL, NULL, FILE_BEGIN);
        bRet = WriteFile(hFile, pFile, sizeOfWriteData + size, &dwWritenSize, NULL);
        if (!bRet)return false;
    //在删除文件前要先关闭句柄和映射
        CloseHandle(hFile);
        CloseHandle(hFile2);
        CloseHandle(hMap);
        UnmapViewOfFile(pFile);
        bRet = DeleteFileA(newPath);
        if (!bRet)return false;
        hFile = CreateFileA(filePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, 0);
        //根据文件句柄创建映射
        hMap = CreateFileMappingA(hFile, NULL, PAGE_READWRITE, 0, 0, 0);
        //映射内容
        *ppFile = MapViewOfFile(hMap, FILE_SHARE_WRITE, 0, 0, 0);
        *phMap = hMap;
        CloseHandle(hFile);
        return true;
}
//根据pFile获取PE文件结构
void GetPeStruct32(LPVOID pFile, _IMAGE_DOS_HEADER* dos, _IMAGE_NT_HEADERS* nt, _IMAGE_SECTION_HEADER** sectionArr) {
        dos = (_IMAGE_DOS_HEADER*)pFile;

        //创建指向PE文件头标志的指针
        DWORD* peId;
        //让PE文件头标志指针指向其对应的地址=DOS首地址+偏移
        peId = (DWORD*)((UINT)dos + dos->e_lfanew);

        //创建指向可选PE头的第一个成员magic的指针
        WORD* magic;
        //让magic指针指向其对应的地址=PE文件头标志地址+PE文件头标志大小+标准PE头大小
        magic = (WORD*)((UINT)peId + sizeof(DWORD) + sizeof(_IMAGE_FILE_HEADER));

        //根据magic判断为32位程序还是64位程序

        //让PE文件头指针指向其对应的地址
        nt = (_IMAGE_NT_HEADERS*)peId;

        //创建指向块表的指针
        _IMAGE_SECTION_HEADER* sectionHeader;
        //让块表的指针指向其对应的地址
        sectionHeader = (_IMAGE_SECTION_HEADER*)((UINT)nt + sizeof(_IMAGE_NT_HEADERS));
        //计数,用来计算块表地址
        int cnt = 0;
        //比较 计数 和 块表的个数,即遍历所有块表
        while (cnt < nt->FileHeader.NumberOfSections) {
                //创建指向块表的指针
                _IMAGE_SECTION_HEADER* section;
                //让块表的指针指向其对应的地址=第一个块表地址+计数*块表的大小
                section = (_IMAGE_SECTION_HEADER*)((UINT)sectionHeader + sizeof(_IMAGE_SECTION_HEADER) * cnt);
                //将得到的块表指针存入数组
                sectionArr = section;

        }
}

//修正节表的Misc和SizeOfRawData
//第一个参数为指向dos头的指针
//第二个参数为指向nt头的指针
//第三个参数为存储指向节指针的数组
//第四个参数为文件路径
//第五个参数为文件映射
//第六个参数为文件映射内容指针
//第七个参数为要修正的节表在数组中的下标
void sectionAlignment(_IMAGE_DOS_HEADER* dos, _IMAGE_NT_HEADERS* nt, _IMAGE_SECTION_HEADER** sectionArr, LPCSTR filePath, HANDLE* phMap,LPVOID* ppFile, int n) {
       
        //获得最后一个节的实际大小
        DWORD VirtualSize = sectionArr->Misc.VirtualSize;
        //获得最后一个节的文件对齐后的大小
        DWORD SizeOfRawData = sectionArr->SizeOfRawData;
        //计算上一个节内存对齐后的大小
        UINT SizeInMemory = (UINT)ceil((double)max(VirtualSize, SizeOfRawData) / (double)nt->OptionalHeader.SectionAlignment) * nt->OptionalHeader.SectionAlignment;
        printf("%X\n", SizeInMemory);
        //计算差值= 内存对齐后大小 - 文件对齐后大小
        UINT offset = SizeInMemory - sectionArr->SizeOfRawData;
        printf("%X\n", offset);
        //根据节在文件中的偏移 + 文件对齐后的大小 得到节的末尾
        UINT end = sectionArr->PointerToRawData + sectionArr->SizeOfRawData;
        printf("end:%X\n", end);
       
        //申请要填充的空间
        INT* content = (INT*)malloc(offset);
        //初始化为0
        ZeroMemory(content, offset);
        //WriteFile用于接收实际写入的大小的参数
        DWORD dwWritenSize = 0;

        BOOL bRet=appendFile(filePath, (PVOID)content, offset, end,phMap,ppFile);
        GetPeStruct32(*ppFile, dos, nt, sectionArr);
        if (bRet) {
                //开始修正Misc和SizeOfRawData
                sectionArr->Misc.VirtualSize = SizeInMemory;
                sectionArr->SizeOfRawData = SizeInMemory;
                //修正后面受到影响的节的PointerOfRawData和VirtualAddress
                int i;
                while (n + 1 <= nt->FileHeader.NumberOfSections - 1) {
                        n++;
                        sectionArr->PointerToRawData += offset;
                }
        }


}

int main(int argc, char* argv[])
{
        //创建DOS对应的结构体指针
        _IMAGE_DOS_HEADER* dos;
        //读取文件,返回文件句柄
        HANDLE hFile = CreateFileA("C:\\Users\\lyl610abc\\Desktop\\EverEdit\\EverEdit.exe", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);
        //根据文件句柄创建映射
        HANDLE hMap = CreateFileMappingA(hFile, NULL, PAGE_READWRITE, 0, 0, 0);
        //映射内容
        LPVOID pFile = MapViewOfFile(hMap, FILE_SHARE_WRITE, 0, 0, 0);
        //类型转换,用结构体的方式来读取
        dos = (_IMAGE_DOS_HEADER*)pFile;
        //输出dos->e_magic,以十六进制输出
        printf("dos->e_magic:%X\n", dos->e_magic);


        //创建指向PE文件头标志的指针
        DWORD* peId;
        //让PE文件头标志指针指向其对应的地址=DOS首地址+偏移
        peId = (DWORD*)((UINT)dos + dos->e_lfanew);
        //输出PE文件头标志,其值应为4550,否则不是PE文件
        printf("peId:%X\n", *peId);


        //创建指向可选PE头的第一个成员magic的指针
        WORD* magic;
        //让magic指针指向其对应的地址=PE文件头标志地址+PE文件头标志大小+标准PE头大小
        magic = (WORD*)((UINT)peId + sizeof(DWORD) + sizeof(_IMAGE_FILE_HEADER));
        //输出magic,其值为0x10b代表32位程序,其值为0x20b代表64位程序
        printf("magic:%X\n", *magic);
        //根据magic判断为32位程序还是64位程序
        switch (*magic) {
        case IMAGE_NT_OPTIONAL_HDR32_MAGIC:
        {
                printf("32位程序\n");
                //确定为32位程序后,就可以使用_IMAGE_NT_HEADERS来接收数据了
                //创建指向PE文件头的指针
                _IMAGE_NT_HEADERS* nt;
                //让PE文件头指针指向其对应的地址
                nt = (_IMAGE_NT_HEADERS*)peId;
                printf("Machine:%X\n", nt->FileHeader.Machine);
                printf("Magic:%X\n", nt->OptionalHeader.Magic);
                //创建一个指针数组,该指针数组用来存储所有的节表指针
                //这里相当于_IMAGE_SECTION_HEADER* sectionArr,声明了一个动态数组
                _IMAGE_SECTION_HEADER** sectionArr = (_IMAGE_SECTION_HEADER**)malloc(sizeof(_IMAGE_SECTION_HEADER*) * nt->FileHeader.NumberOfSections);

                //创建指向块表的指针
                _IMAGE_SECTION_HEADER* sectionHeader;
                //让块表的指针指向其对应的地址
                sectionHeader = (_IMAGE_SECTION_HEADER*)((UINT)nt + sizeof(_IMAGE_NT_HEADERS));
                //计数,用来计算块表地址
                int cnt = 0;
                //比较 计数 和 块表的个数,即遍历所有块表
                while (cnt < nt->FileHeader.NumberOfSections) {
                        //创建指向块表的指针
                        _IMAGE_SECTION_HEADER* section;
                        //让块表的指针指向其对应的地址=第一个块表地址+计数*块表的大小
                        section = (_IMAGE_SECTION_HEADER*)((UINT)sectionHeader + sizeof(_IMAGE_SECTION_HEADER) * cnt);
                        //将得到的块表指针存入数组
                        sectionArr = section;
                        //输出块表名称
                        printf("%s\n", section->Name);
                }
                CloseHandle(hFile);
               
       
                int i;
                //sectionAlignment(dos, nt, sectionArr, "C:\\Users\\lyl610abc\\Desktop\\EverEdit\\EverEdit.exe",hMap, pFile,2);
               
                for (i = 0; i < nt->FileHeader.NumberOfSections; i++) {
                        sectionAlignment(dos, nt, sectionArr, "C:\\Users\\lyl610abc\\Desktop\\EverEdit\\EverEdit.exe", &hMap, &pFile,i);
                }
               

                break;
        }

        case IMAGE_NT_OPTIONAL_HDR64_MAGIC:
        {
                printf("64位程序\n");
                //确定为64位程序后,就可以使用_IMAGE_NT_HEADERS64来接收数据了
                //创建指向PE文件头的指针
                _IMAGE_NT_HEADERS64* nt;
                nt = (_IMAGE_NT_HEADERS64*)peId;
                printf("Machine:%X\n", nt->FileHeader.Machine);
                printf("Magic:%X\n", nt->OptionalHeader.Magic);

                //创建一个指针数组,该指针数组用来存储所有的节表指针
                //这里相当于_IMAGE_SECTION_HEADER* sectionArr,声明了一个动态数组
                _IMAGE_SECTION_HEADER** sectionArr = (_IMAGE_SECTION_HEADER**)malloc(sizeof(_IMAGE_SECTION_HEADER*) * nt->FileHeader.NumberOfSections);

                //创建指向块表的指针
                _IMAGE_SECTION_HEADER* sectionHeader;
                //让块表的指针指向其对应的地址,区别在于这里加上的偏移为_IMAGE_NT_HEADERS64
                sectionHeader = (_IMAGE_SECTION_HEADER*)((UINT)nt + sizeof(_IMAGE_NT_HEADERS64));
                //计数,用来计算块表地址
                int cnt = 0;
                //比较 计数 和 块表的个数,即遍历所有块表
                while (cnt < nt->FileHeader.NumberOfSections) {
                        //创建指向块表的指针
                        _IMAGE_SECTION_HEADER* section;
                        //让块表的指针指向其对应的地址=第一个块表地址+计数*块表的大小
                        section = (_IMAGE_SECTION_HEADER*)((UINT)sectionHeader + sizeof(_IMAGE_SECTION_HEADER) * cnt);
                        //将得到的块表指针存入数组
                        sectionArr = section;
                        //输出块表名称
                        printf("%s\n", section->Name);
                }

                break;
        }

        default:
        {
                printf("error!\n");
                break;
        }

        }
        return 0;
}
```

------

## 运行结果

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210406135744307.png)

------

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210406135858359.png)

------

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210406135927238.png)

------

## 代码说明

这次的代码比较复杂,因为C语言没有提供**对文件指定位置插入数据**的函数;只能自己手动实现了一个appendFile

其实就是:

1. 把要插入数据之前的数据写出到新文件
2. 将要插入数据写入到前面写出到文件末尾
3. 把要插入数据之后的数据写出到文件末尾
4. 删除原本的文件
5. 将新文件复制为原本的文件
6. 删除旧的文件

------

其中要注意,删除文件的时候要将先前获得的句柄和映射,即hFile,hMap,pFile关闭

关闭hFile,hMap,pFile后,需要重新获得新的PE结构,即更新dos、nt、sectionArr

------

# 说明

- 修正内存对齐会影响后面的节,要修正后面的节在文件中的偏移
- 对文件指定位置插入数据需要自己实现
- 删除文件前要关闭相关的句柄和映射
- 这次代码实现关于PE部分其实并没有什么难点,主要麻烦在要对文件指定位置插入数据

------

# 附件

附上本笔记中分析的EverEdit文件:[点我下载](https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/EverEdit修正内存对齐.zip?versionId=CAEQHhiBgIDUiqSXxRciIDljZDVkYTViNzQwMjRhNWE5YzhjMTVkZWI5ZDVhMWUx)

此次附件中添加了 修正完内存对齐后的exe

!(https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210406141233875.png)

lyl610abc 发表于 2021-4-6 15:25

sam喵喵 发表于 2021-4-6 15:21
C语言有个fseek和ftell,不知道能不能用

fseek和ftell也是可以用的
我这里是直接用WIN32的API 来操作的

sam喵喵 发表于 2021-4-6 15:21

C语言有个fseek和ftell,不知道能不能用

janhenk 发表于 2021-4-6 15:22

不错,可以有学习学习

own 发表于 2021-4-6 15:36

膜拜大佬

wendellzwy 发表于 2021-4-6 15:55

厉害了,完全看不懂。

傅粉何郎 发表于 2021-4-6 16:00

不错,学习一下

cob 发表于 2021-4-6 16:02

谢谢大佬的教导

蓝色的抉择 发表于 2021-4-6 16:03

师傅太强了,最近在研究内存dump文件的原理,师傅的笔记很有用

nmy124 发表于 2021-4-6 20:22

谢谢老师讲解,真的不错,可以有学习学习
页: [1] 2
查看完整版本: PE文件笔记十二 修正内存对齐