lyl610abc 发表于 2021-4-4 18:25

PE文件笔记十 扩大节

本帖最后由 lyl610abc 于 2021-4-5 18:40 编辑

继续PE系列笔记的更新

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

------

经过前面的实战(https://www.52pojie.cn/thread-1408866-1-1.html)和(https://www.52pojie.cn/thread-1409183-1-1.html),对PE文件结构有了进一步的了解之后继续来学习对节的操作

此篇笔记为学习节操作之扩大节

# 扩大节

## 为什么要扩大节

在先前的实战中,通过在节表和节之间的空白区写入代码并执行来达到了弹窗的效果,但是如果当想要执行的代码量较大时,即空白区的空间不够时,就可以通过扩大节来解决空白区不足的问题

------

## 扩大节涉及的结构体成员

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

| 涉及的扩展PE头成员 | 含义                |
| ------------------ | ------------------- |
| SizeOfImage      | Image(PE文件)大小 |

## 扩大哪个节

既然要扩大节,那么选择哪个节进行扩大比较好?

实际上每个节都可以扩大,但是一般来说是**选取最后一个节**进行扩大,为什么?

因为节是顺序存储的,如果扩大了前面的节就意味着后面的节全部都要相应地修改偏移,比较麻烦

选取最后一个节进行扩大就不会影响到后面的节了

------

## 扩大节流程

1.将最后一个节的SizeOfRawData和VirtualSize改成N,N = 节内存对齐后的大小 + 要扩大的大小
2.修改 SizeOfImage大小=SizeOfImage大小+要扩大的大小
3.分配一块新的空间,大小为:节内存对齐增加的大小+要扩大的大小

------

## 按流程扩大节

此次依旧以先前的EverEdit.exe为例进行扩大节的演示

------

### 修正节表成员

由于选择的是最后一个节进行扩大,于是不涉及VirtualAddress和PointerToRawData的修改,只需修改Misc和SizeOfRawData即可

先用PE工具DIE查看一下最后一个节的成员

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

------

得到了

| 涉及的节表成员 | 值      | 含义                   |
| -------------- | ------- | ---------------------- |
| Misc         | 0x1643a | 节的实际大小         |
| SizeOfRawData| 0x16600 | 节在文件中对齐后的尺寸 |

------

于是可以计算出在节内存对齐后的大小:

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

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

计算 节内存对齐后大小的原理在(https://www.52pojie.cn/thread-1407996-1-1.html#37810231_misc%E5%92%8Csizeofrawdata)中已经说明,这里不再赘述

得到的节内存对齐后的大小后再加上**要扩大的大小**:0x1000,即 0x17000+0x1000=0x18000

于是要将Misc和SizeOfRawData都修改为0x00018000

此时**节内存对齐增加的大小** = 节内存对齐后大小 - SizeOfRawData(节文件对齐后的大小)

即**节内存对齐增加的大小** = 0x17000 - 0x16600 = 0x17000 - 0x16600 =0xA00

------

用WinHex找到对应的位置

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

------

修改数据,注意小端存储

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

------

### 修正SizeOfImage

用PE工具:DIE查看一下扩展PE头中SizeOfImage

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

------

得到原本SizeOfImage的大小为0x00298000

要修改为 原本大小+扩大的大小= 0x00298000 + 0x1000 = 0x00299000

在WinHex中找到SizeOfImage对应的位置

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

------

修改为:

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

------

### 分配新空间

使用WinHex打开EverEdit,直接拉到文件的末尾,并**选中**最后一个字节

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

------

然后 编辑→粘贴0字节

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

------

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

按"是"

------

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

选择插入的大小为:节内存对齐增加的大小 + 要扩大的大小 = 0xA00 + 0x1000=0x1A00 对应十进制为6656

------

添加完成

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

### 保存测试

将文件保存后再打开,发现仍然能够正常运行

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

------

# 代码实现扩大节

```c
//扩大最后一个节
//第一个参数为指向dos头的指针
//第二个参数为指向nt头的指针
//第三个参数为存储指向节指针的数组
//第四个参数为文件句柄
//第五个参数为要扩大的节的大小
void expandSection(_IMAGE_DOS_HEADER* dos, _IMAGE_NT_HEADERS* nt, _IMAGE_SECTION_HEADER** sectionArr, HANDLE hFile, UINT expandSize) {
      //获得最后一个节的实际大小
      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("Last Section SizeInMemory:%X\n", SizeInMemory);
      //根据内存对齐后大小 - 文件对齐后大小 得到 节内存对齐增加的大小
      UINT offset = SizeInMemory - sectionArr->SizeOfRawData;
      printf("offset:%X\n", offset);
      //根据节在文件中的偏移 + 文件对齐后的大小 得到节的末尾
      UINT end = sectionArr->PointerToRawData + sectionArr->SizeOfRawData;
      printf("end:%X\n", end);
      //根据要扩大的节的大小 + 节内存对齐增加的大小 得到 要分配的新空间大小
      UINT size = offset + expandSize;
      //设置要写入的地址为节末尾
      SetFilePointer(hFile, end, NULL, FILE_BEGIN);
      //申请要填充的空间
      INT* content = (INT*)malloc(expandSize + offset);
      //初始化为0
      ZeroMemory(content, expandSize + offset);
      //WriteFile用于接收实际写入的大小的参数
      DWORD dwWritenSize = 0;
      BOOL bRet = WriteFile(hFile, content, expandSize + offset, &dwWritenSize, NULL);
      if (bRet)
      {
                printf("expand Section success!\n");
                //修正节表成员
                sectionArr->Misc.VirtualSize = SizeInMemory + expandSize;
                sectionArr->SizeOfRawData = SizeInMemory + expandSize;
                //修正SizeOfImage
                nt->OptionalHeader.SizeOfImage += expandSize;
      }
      else {
                printf("%d\n", GetLastError());
      }
}

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);
                }

                expandSection(dos, nt, sectionArr, hFile, 0x1000);

                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-20210404230033412.png)

------

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

------

可以看到代码执行以后,达到了和前面手动操作一样的效果,并且程序仍然能够正常运行( •̀ ω •́ )✧

## 代码说明

代码中修改了先前打开文件的权限,修改其拥有写权限

```c
//读取文件,返回文件句柄
      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);
```

------

在扩大节的相关代码中用到了WriteFile和SetFilePointer;其它的就是单纯的PE内容了,代码中都有注释,这里也就不再赘述了

# 总结

- 扩大节一般选取最后一个节进行扩大
- 扩大最后一个节需要修正的节表成员:Misc和SizeOfRawData 以及修正扩展PE头中的SizeOfImage
- 扩大节后要分配的空间除了要扩大的大小外还要加上**节内存对齐增加的大小**(内存对齐后的大小-文件对齐后的大小)

# 附件

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

aswcy815174418 发表于 2021-4-5 21:52

为什么sizeofimagez是增加1000而不是增加2000(1A00的文件对齐)

pj10234 发表于 2021-12-9 14:17

我的理解:
1. 在文件末端增加0x1000字节的空间;
2. 取出最后一个节的SizeOfRawData和VirtualSize,取两者最大值,进行内存对齐(参考SectionAlignment),再用内存对其之后的值加上0x1000,最终的值替换SizeOfRawData和VirtualSize;
3. 修改SizeOfImage的值,在定义上SizeOfImage可比实际的值大,加上其本身就是内存对齐之后的大小,不用考虑那么多,直接在原值基础加上0x1000。

那么问题来了,这里需要考虑文件对齐和内存对齐是否一致么,这个影响我们的扩大节吗?

dreamfrog 发表于 2021-4-4 18:58

太高级,感谢分享,都是二进制?

sam喵喵 发表于 2021-4-4 20:08

不能用代码扩节吗

Dream漂移 发表于 2021-4-4 20:35

感谢分享,

xiheng_li 发表于 2021-4-4 20:46

谢谢楼主分享

wmwmpk110 发表于 2021-4-4 20:52

谢谢楼主分享

nmy124 发表于 2021-4-4 22:40

感谢楼主分享,用心听,用心学,再次谢谢

lyl610abc 发表于 2021-4-4 23:07

sam喵喵 发表于 2021-4-4 20:08
不能用代码扩节吗
本来想摸鱼偷个懒的,不过既然你要求了,我就特地去写了代码
已经在笔记中更新,请查收{:301_997:}

莫影轩 发表于 2021-4-4 23:46

十分感谢教导

cptw 发表于 2021-4-5 00:44

感谢分享,
页: [1] 2 3 4
查看完整版本: PE文件笔记十 扩大节