前面在PE文件笔记十 扩大节学习了关于节的操作之扩大节,接下来继续学习节的操作之新增节
新增节
为什么要新增节
新增节和扩大节一样,都是用来解决空白区不足的问题
但既然扩大节已经能够解决问题了,为什么还要新增节呢?或者说新增节比扩大节好在哪里?
在前面的扩大节中,只是扩大了节,并没有关注扩大出来的空白区的权限问题;每个节都有其对应的权限,由节.Characteristics决定
有关节的权限问题已经在PE文件笔记六 节表和节中说过,这里不再赘述
如果扩大出来的空白区希望能够被用于执行代码,那么被扩大的节就必须具备IMAGE_SCN_CNT_CODE权限(该节包含可执行代码)
如果被扩大的节不具备这个权限,还得为此将整个节的权限修改
除此之外,扩大节还会使得原本的数据和我们扩大的空白区混在一起
相比之下新增节则完全拥有自己的权限,不依附于要扩大的节的权限,可以自己指定想要的权限
扩大节和新增节的差异
扩大节:权限取决于要被扩大的节的原本权限,如果不满足权限还需要去修改;原本数据和扩大的空白区混在一起
新增节:权限由自己来指定;空间独立没有数据混杂
新增节涉及的结构体成员
涉及的节表成员 |
含义 |
Name |
节名称 |
VirtualAddress |
节在内存中的偏移 (RVA) |
Misc |
节的实际大小 |
SizeOfRawData |
节在文件中对齐后的尺寸 |
PointerToRawData |
节区在文件中的偏移 |
Characteristics |
节的属性 |
涉及的标准PE头成员 |
含义 |
NumberOfSections |
节的个数 |
涉及的扩展PE头成员 |
含义 |
SizeOfImage |
Image(PE文件)大小 |
新增节的位置
和扩大节一个道理,为了避免新增节后影响先前的节,选择在原本的最后一个节后面新增节
新增节的流程
- 判断是否有足够空间用于添加节表
- 修改标准PE头中节的数量
- 在节表中新增一个成员
- 修正SizeOfImage的大小
- 分配新空间
按流程新增节
此次依旧以先前的EverEdit.exe为例进行新增节的演示
判断是否有空间能添加节表
用WinHex打开EverEdit.exe,找到最后一个节表,判断最后一个节表后面的40个字节(节表的大小)是否全为0
可以看到最后一个节区后40个字节全为0,因此是可以添加节表的
修改标准PE头中节的数量
之前都是使用WinHex来进行修改演示的,是为了更好地学习本质;到了现在对PE文件比较熟悉的时候,就可以用PE工具:DIE工具来代替WinHex进行修改了(看起来更直观)
在节表中新增一个成员
回到先前PE 基本信息的地方,点击节来查看节表信息
确定要修改的数值
涉及的节表成员 |
值 |
要求 |
含义 |
Name |
.lyl610 |
小于等于8位 |
节名称 |
Misc.VirtualSize |
0x1000 |
要新增的节的大小 |
节的实际大小 |
VirtualAddress |
0x298000 |
上一个节.VirtualAddress+上一个节内存对齐后的大小 |
节在内存中的偏移 (RVA) |
SizeOfRawData |
0x1000 |
要新增的节的大小 |
节在文件中对齐后的尺寸 |
PointerToRawData |
0x258200 |
上一个节.PointerToRawData+上一个节.SizeOfRawData |
节区在文件中的偏移 |
Characteristics |
0xe0000000 |
该节具备的权限,这里指定为节可读、可写、可执行 |
节的属性 |
这里主要讲一下VirtualAddress和PointerToRawData的计算,其余的都是自己指定的
VirtualAddress:
VirtualAddress为该节在内存中的偏移 = 上一个节.VirtualAddress+上一个节内存对齐后的大小 = 0x281000 + 0x17000 = 0x298000
关于节内存对齐后的大小如何计算在PE文件笔记十 扩大节中已经说明,这里也就不再赘述
PointerToRawData:
PointerToRawData为该节在文件中的偏移 = 上一个节在文件中的偏移 + 上一个节文件对齐后的大小
即PointerToRawData = 上一个节.PointerToRawData + 上一个节.SizeOfRawData = 0x241c00+0x16600=0x258200
填充要修改的数值
修正SizeOfImage的大小
这里将SizeOfImage增加0x1000(与前面新增节中的Misc.VirtualSize和SizeOfRawData对应)
分配新空间
分配新空间还是得交给WinHex,步骤和扩大节中的分配新空间没什么不同o( ̄▽ ̄)ブ
使用WinHex打开EverEdit,直接拉到文件的末尾,并选中最后一个字节
然后 编辑→粘贴0字节
按”是”
选择插入的大小为:0x1000(与前面新增节中的Misc.VirtualSize和SizeOfRawData对应),对应十进制为4096
添加完成
保存测试
将文件保存后再打开,发现仍然能够正常运行
代码实现新增节
复制代码 隐藏代码//新增一个节 //第一个参数为指向dos头的指针 //第二个参数为指向nt头的指针 //第三个参数为存储指向节指针的数组 //第四个参数为文件句柄 //第五个参数为要新增的节的大小 //第六个参数为新增节的名称 //第七个参数为新增节的权限 void addSection(_IMAGE_DOS_HEADER* dos, _IMAGE_NT_HEADERS* nt, _IMAGE_SECTION_HEADER** sectionArr, HANDLE hFile, UINT addSize, BYTE Name[IMAGE_SIZEOF_SHORT_NAME], DWORD Characteristics) {
//判断最后一个节表的后40个字节是否全为0
//通过最后一个节表+节表大小 到达新节表 _IMAGE_SECTION_HEADER* newSection = (_IMAGE_SECTION_HEADER*)((UINT)§ionArr[nt->FileHeader.NumberOfSections - 1]->Name + sizeof(_IMAGE_SECTION_HEADER)); //判断新节表是否有被填充 UINT* tmp = (UINT*)newSection; int i; //标志 判断新节表是否全为0 BOOL flag = false; for (i = 0; i < sizeof(_IMAGE_SECTION_HEADER) / sizeof(INT); i++) { if (*tmp != 0) { flag = true; } tmp++; } if (flag) { printf("空间不足,无法新增节\n"); return; }
//Name赋值 for (i = 0; i < IMAGE_SIZEOF_SHORT_NAME; i++) { newSection->Name[i] = Name[i]; } //大小赋值 newSection->Misc.VirtualSize = addSize; newSection->SizeOfRawData = addSize; //权限赋值 newSection->Characteristics = Characteristics;
//获得最后一个节的实际大小 DWORD VirtualSize = sectionArr[nt->FileHeader.NumberOfSections - 1]->Misc.VirtualSize; //获得最后一个节的文件对齐后的大小 DWORD SizeOfRawData = sectionArr[nt->FileHeader.NumberOfSections - 1]->SizeOfRawData; //计算上一个节内存对齐后的大小 UINT SizeInMemory = (UINT)ceil((double)max(VirtualSize, SizeOfRawData) / double(nt->OptionalHeader.SectionAlignment)) * nt->OptionalHeader.SectionAlignment; //RVA赋值 newSection->VirtualAddress = sectionArr[nt->FileHeader.NumberOfSections - 1]->VirtualAddress + SizeInMemory; printf("newSection->VirtualAddress:%X\n", newSection->VirtualAddress); //FOA赋值 newSection->PointerToRawData = sectionArr[nt->FileHeader.NumberOfSections - 1]->PointerToRawData + sectionArr[nt->FileHeader.NumberOfSections - 1]->SizeOfRawData; printf("newSection->PointerToRawData:%X\n", newSection->PointerToRawData);
//分配新空间 //根据节在文件中的偏移 + 文件对齐后的大小 得到节的末尾 UINT end = sectionArr[nt->FileHeader.NumberOfSections - 1]->PointerToRawData + sectionArr[nt->FileHeader.NumberOfSections - 1]->SizeOfRawData; printf("end:%X\n", end); //设置要写入的地址为节末尾 SetFilePointer(hFile, end, NULL, FILE_BEGIN); //申请要填充的空间 INT* content = (INT*)malloc(addSize); //初始化为0 ZeroMemory(content, addSize); DWORD dwWritenSize = 0; BOOL bRet = WriteFile(hFile, content, addSize, &dwWritenSize, NULL); if (bRet) { //修改标准PE头中节的数量 nt->FileHeader.NumberOfSections += 1; //修正SizeOfImage大小 nt->OptionalHeader.SizeOfImage += addSize; printf("add Section success!\n"); } else { printf("分配新空间失败\n"); } }
int main(int argc, char* argv[]) { //创建DOS对应的结构体指针 _IMAGE_DOS_HEADER* dos; //读取文件,返回文件句柄 HANDLE hFile = CreateFileA("C:\\Users\\sixonezero\\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[nt->FileHeader.NumberOfSections],声明了一个动态数组 _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[cnt++] = section; //输出块表名称 printf("%s\n", section->Name); }
BYTE Name[IMAGE_SIZEOF_SHORT_NAME] = ".lyl610"; addSection(dos, nt, sectionArr, hFile, 0x1000, Name, 0xe0000000); 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[nt->FileHeader.NumberOfSections],声明了一个动态数组 _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[cnt++] = section; //输出块表名称 printf("%s\n", section->Name); } break; }
default: { printf("error!\n"); break; }
} return 0; }
|
运行结果
可以看到代码执行以后,结果和前面手动操作一致,并且程序仍然能够正常运行( •̀ ω •́ )✧
总结
- 新增节的好处就是可以自己指定想要的权限,代码更加具有独立性
- 无论是新增节还是扩大节都要注意文件对齐和内存对齐;分配的新空间大小最好为内存对齐的整数倍
附件
附上本笔记中分析的EverEdit文件:点我下载
转载自吾爱破解上的一个大佬