继续PE系列笔记的更新
PE其它笔记索引可前往:
PE文件笔记一 PE介绍
前面在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文件:点我下载