合并为一个节的目的是缩小节表的大小,以在节表剩余空间不足时完成新增节的操作
整体思路:
FileBuffer转为ImageBufferImageBuffer中SizeOfHeaders的对齐值(第四步使用)ImageBuffer中的真实大小(第四步使用)ImageBuffer中所有节的总大小SizeOfRawData等字段实际上可以不进行拉伸,直接在FileBuffer层面进行操作,但本文还是按照海哥要求来做
继续摆上老图

通过之前文章的代码,先将FileBuffer转为ImageBuffer
xxxxxxxxxxImageBufferSize = CopyFileBufferToImageBuffer(pFileBuffer, &pImageBuffer);和之前的文章一样,对ImageBuffer进行头部的解析
xxxxxxxxxxpNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pImageBuffer + pDosHeader->e_lfanew);pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 0x04);pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);numberOfSection = pPEHeader->NumberOfSections;pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);拿到节表中的最后一节的指针
xxxxxxxxxxpLastSectionHeader = &(pSectionHeader[numberOfSection - 1]);计算内存中的SizeOfHeaders的对齐值,这个值的意义是后续计算finalSectionsSize
如果这里不按照SectionAlignment对齐,那么拿不到第一节之前的头部总大小,因为只能在PE头里拿到FileBuffer的SizeOfHeaders,这个值在拉伸对齐后会变化
xxxxxxxxxxpOptionHeader->SizeOfHeaders = AlignLength(pOptionHeader->SizeOfHeaders, pOptionHeader->SectionAlignment);xxxxxxxxxxDWORD AlignLength(DWORD ActualSize, DWORD AlignSize){ if (ActualSize % AlignSize == 0) { return ActualSize; } else { DWORD n = ActualSize / AlignSize; return AlignSize * (n + 1); }}比较 内存中最后一节对齐前大小VirtualSize 和 文件中对齐后大小SizeOfRawData
计算得到最后一节在内存中的真正大小,虽然图片中看起来Misc.VirtualSize小于SizeOfRawData,但某些节的Misc.VirtualSize包含了未初始化的变量,所以VirtualSize有可能会更大

找到两者中更大的一个值,作为最后一节的有效内存大小,下一步计算新节的有效大小
xxxxxxxxxxDWORD VirtualSize = pLastSectionHeader->Misc.VirtualSize;DWORD SizeOfRawData = pLastSectionHeader->SizeOfRawData;
lastSectionsMaxSize = (SizeOfRawData > VirtualSize ? SizeOfRawData : VirtualSize);finalSectionsSize变量记录了从内存中第一节到最后一节的有效内存之前的总大小
xxxxxxxxxxfinalSectionsSize = pLastSectionHeader->VirtualAddress + lastSectionsMaxSize - pOptionHeader->SizeOfHeaders;
遍历节表,通过|操作将所有节表拥有的属性记录到NewSecCharacteristics确保新的合并节拥有以前所有节的属性,以防止出现意外的情况。另外仅保留第一个节表的内容,从第二个节表开始,全部设置为0
xxxxxxxxxxfor (DWORD i = 0; i < numberOfSection; i++){ NewSecCharacteristics |= pTempSectionHeader->Characteristics; if (i > 0) { memset(&(pSectionHeader[i]), 0, IMAGE_SIZEOF_SECTION_HEADER); } pTempSectionHeader++;}将第一个节表的内存对齐前大小和SizeOfRawData设为所有节一共的大小,设置第一个节属性Characteristics为所有节共有的属性,修改节数量为1
xxxxxxxxxxpSectionHeader->Misc.VirtualSize = finalSectionsSize;pSectionHeader->SizeOfRawData = finalSectionsSize;pSectionHeader->Characteristics = NewSecCharacteristics;pPEHeader->NumberOfSections = 0x01;
分配内存,初始化为0,并复制头部
xxxxxxxxxxpTempMergeSection = malloc(pOptionHeader->SizeOfImage);if (!pTempMergeSection){ return 0;}memset(pTempMergeSection, 0, pOptionHeader->SizeOfImage);memcpy(pTempMergeSection, pDosHeader, pOptionHeader->SizeOfHeaders);已经计算并设置了大节表的SizeOfRawData,从内存中第一个VirtualAddress偏移开始,按照大节表的SizeOfRawData复制,一次复制就可以将所有节内容复制过去
xxxxxxxxxxmemcpy((PDWORD)((DWORD)pTempMergeSection + pSectionHeader->PointerToRawData), (PDWORD)((DWORD)pImageBuffer + pSectionHeader->VirtualAddress), pSectionHeader->SizeOfRawData);接下来可以直接把pTempMergeSection写入新EXE
可以看到,这里把原来内存中的所有节信息固定了,导致最终写入的新EXE变大了不少。合并节的意思是合并节表,但不能压缩节的内容,内存中的偏移是不能动的,否则将无法执行
因此合并成一个节后,需要把拉伸后的所有节的字节拿过来,当成现有唯一节的有效字节
但存在的疑问是,计算出的finalSectionsSize忽略了原来最后一节的对齐部分,这个问题在分配新内存时使用了原有的SizeOfImage大小,这个大小是完整的节大小,可以弥补忽略的部分
最后的问题是,复制的字节从PointerToRawData偏移开始写入完整的节,但是定义了总大小为SizeOfImage,这样导致了末尾可能会多出了一部分垃圾数据
对合并前后的EXE进行对比,发现末尾确实多出了一堆0
