合并为一个节的目的是缩小节表的大小,以在节表剩余空间不足时完成新增节的操作
整体思路:
FileBuffer
转为ImageBuffer
ImageBuffer
中SizeOfHeaders
的对齐值(第四步使用)ImageBuffer
中的真实大小(第四步使用)ImageBuffer
中所有节的总大小SizeOfRawData
等字段实际上可以不进行拉伸,直接在FileBuffer
层面进行操作,但本文还是按照海哥要求来做
继续摆上老图
通过之前文章的代码,先将FileBuffer
转为ImageBuffer
xxxxxxxxxx
ImageBufferSize = CopyFileBufferToImageBuffer(pFileBuffer, &pImageBuffer);
和之前的文章一样,对ImageBuffer
进行头部的解析
xxxxxxxxxx
pNTHeader = (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);
拿到节表中的最后一节的指针
xxxxxxxxxx
pLastSectionHeader = &(pSectionHeader[numberOfSection - 1]);
计算内存中的SizeOfHeaders
的对齐值,这个值的意义是后续计算finalSectionsSize
如果这里不按照SectionAlignment
对齐,那么拿不到第一节之前的头部总大小,因为只能在PE头里拿到FileBuffer
的SizeOfHeaders
,这个值在拉伸对齐后会变化
xxxxxxxxxx
pOptionHeader->SizeOfHeaders = AlignLength(pOptionHeader->SizeOfHeaders, pOptionHeader->SectionAlignment);
xxxxxxxxxx
DWORD 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
有可能会更大
找到两者中更大的一个值,作为最后一节的有效内存大小,下一步计算新节的有效大小
xxxxxxxxxx
DWORD VirtualSize = pLastSectionHeader->Misc.VirtualSize;
DWORD SizeOfRawData = pLastSectionHeader->SizeOfRawData;
lastSectionsMaxSize = (SizeOfRawData > VirtualSize ? SizeOfRawData : VirtualSize);
finalSectionsSize
变量记录了从内存中第一节到最后一节的有效内存之前的总大小
xxxxxxxxxx
finalSectionsSize = pLastSectionHeader->VirtualAddress + lastSectionsMaxSize - pOptionHeader->SizeOfHeaders;
遍历节表,通过|
操作将所有节表拥有的属性记录到NewSecCharacteristics
确保新的合并节拥有以前所有节的属性,以防止出现意外的情况。另外仅保留第一个节表的内容,从第二个节表开始,全部设置为0
xxxxxxxxxx
for (DWORD i = 0; i < numberOfSection; i++)
{
NewSecCharacteristics |= pTempSectionHeader->Characteristics;
if (i > 0)
{
memset(&(pSectionHeader[i]), 0, IMAGE_SIZEOF_SECTION_HEADER);
}
pTempSectionHeader++;
}
将第一个节表的内存对齐前大小和SizeOfRawData
设为所有节一共的大小,设置第一个节属性Characteristics
为所有节共有的属性,修改节数量为1
xxxxxxxxxx
pSectionHeader->Misc.VirtualSize = finalSectionsSize;
pSectionHeader->SizeOfRawData = finalSectionsSize;
pSectionHeader->Characteristics = NewSecCharacteristics;
pPEHeader->NumberOfSections = 0x01;
分配内存,初始化为0,并复制头部
xxxxxxxxxx
pTempMergeSection = malloc(pOptionHeader->SizeOfImage);
if (!pTempMergeSection)
{
return 0;
}
memset(pTempMergeSection, 0, pOptionHeader->SizeOfImage);
memcpy(pTempMergeSection, pDosHeader, pOptionHeader->SizeOfHeaders);
已经计算并设置了大节表的SizeOfRawData
,从内存中第一个VirtualAddress
偏移开始,按照大节表的SizeOfRawData
复制,一次复制就可以将所有节内容复制过去
xxxxxxxxxx
memcpy((PDWORD)((DWORD)pTempMergeSection + pSectionHeader->PointerToRawData),
(PDWORD)((DWORD)pImageBuffer + pSectionHeader->VirtualAddress),
pSectionHeader->SizeOfRawData);
接下来可以直接把pTempMergeSection
写入新EXE
可以看到,这里把原来内存中的所有节信息固定了,导致最终写入的新EXE变大了不少。合并节的意思是合并节表,但不能压缩节的内容,内存中的偏移是不能动的,否则将无法执行
因此合并成一个节后,需要把拉伸后的所有节的字节拿过来,当成现有唯一节的有效字节
但存在的疑问是,计算出的finalSectionsSize
忽略了原来最后一节的对齐部分,这个问题在分配新内存时使用了原有的SizeOfImage
大小,这个大小是完整的节大小,可以弥补忽略的部分
最后的问题是,复制的字节从PointerToRawData
偏移开始写入完整的节,但是定义了总大小为SizeOfImage
,这样导致了末尾可能会多出了一部分垃圾数据
对合并前后的EXE进行对比,发现末尾确实多出了一堆0