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) 为什么sizeofimagez是增加1000而不是增加2000(1A00的文件对齐) 我的理解:
1. 在文件末端增加0x1000字节的空间;
2. 取出最后一个节的SizeOfRawData和VirtualSize,取两者最大值,进行内存对齐(参考SectionAlignment),再用内存对其之后的值加上0x1000,最终的值替换SizeOfRawData和VirtualSize;
3. 修改SizeOfImage的值,在定义上SizeOfImage可比实际的值大,加上其本身就是内存对齐之后的大小,不用考虑那么多,直接在原值基础加上0x1000。
那么问题来了,这里需要考虑文件对齐和内存对齐是否一致么,这个影响我们的扩大节吗? 太高级,感谢分享,都是二进制? 不能用代码扩节吗 感谢分享, 谢谢楼主分享 谢谢楼主分享
感谢楼主分享,用心听,用心学,再次谢谢 sam喵喵 发表于 2021-4-4 20:08
不能用代码扩节吗
本来想摸鱼偷个懒的,不过既然你要求了,我就特地去写了代码
已经在笔记中更新,请查收{:301_997:} 十分感谢教导 感谢分享,