|
继续PE系列条记的更新
PE其它条记索引可前往:
PE文件条记一 PE介绍
前面在PE文件条记十 扩大节学习了关于节的操纵之扩大节,接下来继续学习节的操纵之新增节
新增节
为什么要新增节
新增节和扩大节一样,都是用来解决空白区不足的题目
但既然扩大节已经可以大概解决题目了,为什么还要新增节呢?或者说新增节比扩大节好在哪里?
在前面的扩大节中,只是扩大了节,并没有关注扩大出来的空白区的权限题目;每个节都有其对应的权限,由节.Characteristics决定
有关节的权限题目已经在PE文件条记六 节表和节中说过,这里不再赘述
如果扩大出来的空白区希望可以大概被用于执行代码,那么被扩大的节就必须具备IMAGE_SCN_CNT_CODE权限(该节包含可执行代码)
如果被扩大的节不具备这个权限,还得为此将整个节的权限修改
除此之外,扩大节还会使得原本的数据和我们扩大的空白区混在一起
相比之下新增节则完全拥有本身的权限,不依附于要扩大的节的权限,可以本身指定想要的权限
扩大节和新增节的差异
扩大节:权限取决于要被扩大的节的原本权限,如果不满足权限还需要去修改;原本数据和扩大的空白区混在一起
新增节:权限由本身来指定;空间独立没有数据混杂
新增节涉及的结构体成员
涉及的节表成员含义Name节名称VirtualAddress节在内存中的偏移 (RVA)Misc节的现实大小SizeOfRawData节在文件中对齐后的尺寸PointerToRawData节区在文件中的偏移Characteristics节的属性涉及的标准PE头成员含义NumberOfSections节的个数涉及的扩展PE头成员含义SizeOfImageImage(PE文件)大小新增节的位置
和扩大节一个道理,为了避免新增节后影响先前的节,选择在原本的最后一个节后面新增节
新增节的流程
- 判断是否有富足空间用于添加节表
- 修改标准PE头中节的数量
- 在节表中新增一个成员
- 修正SizeOfImage的大小
- 分配新空间
按流程新增节
此次仍旧以先前的EverEdit.exe为例进行新增节的演示
判断是否有空间能添加节表
用WinHex打开EverEdit.exe,找到最后一个节表,判断最后一个节表后面的40个字节(节表的大小)是否全为0
可以看到最后一个节区后40个字节全为0,因此是可以添加节表的
修改标准PE头中节的数量
之前都是利用WinHex来进行修改演示的,是为了更好地学习本质;到了现在对PE文件比较熟悉的时候,就可以用PE工具:DIE工具来取代WinHex进行修改了(看起来更直观)
在节表中新增一个成员
回到先前PE 基本信息的地方,点击节来查看节表信息
确定要修改的数值
涉及的节表成员值要求含义Name.lyl610小于等于8位节名称Misc.VirtualSize0x1000要新增的节的大小节的现实大小VirtualAddress0x298000上一个节.VirtualAddress+上一个节内存对齐后的大小节在内存中的偏移 (RVA)SizeOfRawData0x1000要新增的节的大小节在文件中对齐后的尺寸PointerToRawData0x258200上一个节.PointerToRawData+上一个节.SizeOfRawData节区在文件中的偏移Characteristics0xe0000000该节具备的权限,这里指定为节可读、可写、可执行节的属性这里重要讲一下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 = Name; } //大小赋值 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文件:点我下载
来源:http://www.12558.net
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
|