前面在PE文件笔记十 扩大节学习了关于节的操作之扩大节,接下来继续学习节的操作之新增节

新增节

为什么要新增节

新增节和扩大节一样,都是用来解决空白区不足的问题

但既然扩大节已经能够解决问题了,为什么还要新增节呢?或者说新增节比扩大节好在哪里

在前面的扩大节中,只是扩大了节,并没有关注扩大出来的空白区的权限问题;每个节都有其对应的权限,由节.Characteristics决定

有关节的权限问题已经在PE文件笔记六 节表和节中说过,这里不再赘述

如果扩大出来的空白区希望能够被用于执行代码,那么被扩大的节就必须具备IMAGE_SCN_CNT_CODE权限(该节包含可执行代码)

如果被扩大的节不具备这个权限,还得为此将整个节的权限修改

除此之外,扩大节还会使得原本的数据和我们扩大的空白区混在一起


相比之下新增节则完全拥有自己的权限,不依附于要扩大的节的权限,可以自己指定想要的权限


扩大节和新增节的差异

扩大节:权限取决于要被扩大的节的原本权限,如果不满足权限还需要去修改;原本数据和扩大的空白区混在一起

新增节:权限由自己来指定;空间独立没有数据混杂


新增节涉及的结构体成员

涉及的节表成员 含义
Name 节名称
VirtualAddress 节在内存中的偏移 (RVA)
Misc 节的实际大小
SizeOfRawData 节在文件中对齐后的尺寸
PointerToRawData 节区在文件中的偏移
Characteristics 节的属性
涉及的标准PE头成员 含义
NumberOfSections 节的个数
涉及的扩展PE头成员 含义
SizeOfImage Image(PE文件)大小

新增节的位置

和扩大节一个道理,为了避免新增节后影响先前的节,选择在原本的最后一个节后面新增节


新增节的流程

  1. 判断是否有足够空间用于添加节表
  2. 修改标准PE头中节的数量
  3. 在节表中新增一个成员
  4. 修正SizeOfImage的大小
  5. 分配新空间

按流程新增节

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


判断是否有空间能添加节表

用WinHex打开EverEdit.exe,找到最后一个节表,判断最后一个节表后面的40个字节(节表的大小)是否全为0

image-20210405183220552

可以看到最后一个节区后40个字节全为0,因此是可以添加节表的


修改标准PE头中节的数量

之前都是使用WinHex来进行修改演示的,是为了更好地学习本质;到了现在对PE文件比较熟悉的时候,就可以用PE工具:DIE工具来代替WinHex进行修改了(看起来更直观)

image-20210405135448270


image-20210405135412956


image-20210405135628662


image-20210405135736239


在节表中新增一个成员

回到先前PE 基本信息的地方,点击节来查看节表信息

image-20210405135909642


image-20210405140219293


image-20210405140317837


image-20210405140624788


确定要修改的数值

涉及的节表成员 要求 含义
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


填充要修改的数值

image-20210405143548960


修正SizeOfImage的大小

image-20210405143951801


这里将SizeOfImage增加0x1000(与前面新增节中的Misc.VirtualSize和SizeOfRawData对应)

image-20210405144125519


分配新空间

分配新空间还是得交给WinHex,步骤和扩大节中的分配新空间没什么不同o( ̄▽ ̄)ブ

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

image-20210404142328069


然后 编辑→粘贴0字节

image-20210404142524635


image-20210404142611679

按”是”


image-20210405144551356

选择插入的大小为:0x1000(与前面新增节中的Misc.VirtualSize和SizeOfRawData对应),对应十进制为4096


添加完成

image-20210404143121957

保存测试

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

image-20210404165912116


代码实现新增节

 复制代码 隐藏代码//新增一个节
//第一个参数为指向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;
}

运行结果

image-20210405183729979


image-20210405160726667


image-20210405160800314

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


总结

  • 新增节的好处就是可以自己指定想要的权限,代码更加具有独立性
  • 无论是新增节还是扩大节都要注意文件对齐和内存对齐;分配的新空间大小最好为内存对齐的整数倍

附件

附上本笔记中分析的EverEdit文件:点我下载

转载自吾爱破解上的一个大佬