深入分析PE结构(三)
字数 1357 2025-08-07 08:22:20
PE结构深入分析:新增节与扩大节技术详解
0x0 新增节技术
前言
手动新增一个节表和节,保证修改后的程序能正确执行。
PE结构整体过程
- SizeOfHeaders:DOS头 + DOS stub(垃圾数据) + NT头(PE标记 + 标准PE头 + 可选PE头) + 已存在节表 --> 对齐之后的大小
- 重要原则:SizeOfHeaders不能随便改变,代价太大
新增节步骤
- 新增一个节
- 新增一个节表(40个字节)
判断条件:确保新增节表后有足够空间(至少80字节)
计算公式:
SizeOfHeader - (DOS + DOS stub + PE标记 + 标准PE头 + 可选PE头 + 已存在节表) >= 2个节表的大小(80字节)
- 需要修改的数据:
- 添加一个新节(可以复制现有节)
- 在新增节后面填充一个节大小的000
- 修改标准PE头中的节数量(NumberOfSections参数)
- 修改内存中整个PE文件的映射尺寸(可选PE头中sizeOfImage参数)
- 在原有数据的最后,新增一个节的数据(内存对齐的整数倍)
- 修正新增节表的属性
手动分析-1:常规情况
- 复制要操作的exe作为参考
- 判断空间是否足够
- 计算现有节表数量(每个节表40字节)
- 在空白区域添加新节表(复制.text节表)
- 修改NumberOfSections参数
- 修改sizeOfImage参数(在可选PE头后56字节)
- 填充新节(1000h字节=4096字节)
- 修改新增节表的属性:
- Name(8字节)
- VirtualSize(内存中大小,对齐前长度)
- VirtualAddress(内存中偏移/RVA)
- SizeOfRawData(文件中大小,对齐后长度)
- PointerToRawData(文件中偏移)
- Characteristics(节属性)
手动分析-2:极端情况(节表后无足够空间)
当节表后跟了一堆有用数据时:
- 不能移动或删除这些数据
- 解决方案:利用PE结构中间的垃圾数据
- 将PE标记上移
- 修改DOS头中的e_lfanew参数指向新位置
- 原位置数据补0
核心代码实现
DWORD CopyFileBufferToNewImageBuffer(IN LPVOID pFileBuffer, IN size_t fileSize, OUT LPVOID* pNewImageBuffer)
{
// 检查空间是否足够
okAddSections = (DWORD)(pOptionHeader->SizeOfHeaders - (pDosHeader->e_lfanew + 0x04 +
sizeof(PIMAGE_FILE_HEADER) + pPEHeder->SizeOfOptionalHeader +
sizeof(PIMAGE_SECTION_HEADER) * pPEHeder->NumberOfSections));
if (okAddSections < 2 * sizeof(PIMAGE_SECTION_HEADER)) {
printf("这个exe文件头不剩余空间不够\r\n");
free(pTempNewImageBuffer);
return 0;
}
// 修改节数量
*pNumberOfSection = pPEHeder->NumberOfSections + 1;
// 修改SizeOfImage
*pSizeOfImage = pOptionHeader->SizeOfImage + 0x1000;
// 设置新节表属性
memcpy(pSecName, ".newSec", 8);
*pSecMisc = 0x1000;
*pSecVirtualAddress = pLastSectionHeader->VirtualAddress + add_size;
*pSecSizeOfRawData = 0x1000;
*pSecPointToRawData = pLastSectionHeader->PointerToRawData + pLastSectionHeader->SizeOfRawData;
*pSecCharacteristics = 0xFFFFFFFF;
}
0x2 扩大节技术
前言
当空白区域无法满足新增节要求时,需要扩大现有节。
整体流程
- 拉伸到内存
- 分配新空间:SizeOfImage + Ex(要扩大的大小)
- 修改最后一个节的SizeOfRawData和VirtualSize:
- 取两者中较大的值N
- 设置SizeOfRawData = VirtualSize = N + Ex
- 修改SizeOfImage大小:SizeOfImage = SizeOfImage + Ex
核心代码实现
DWORD FileBufferToModifyImageBuffer(IN LPVOID pFileBuffer, OUT LPVOID* pNewImageBuffer)
{
// 扩大VirtualSize
*pVirtualSize = AlignLength(*pVirtualSize, pOptionHeader->SectionAlignment) + 0x1000;
// 扩大SizeOfRawData
*pSizeOfRawData = AlignLength(*pSizeOfRawData, pOptionHeader->SectionAlignment) + 0x1000;
// 修改SizeOfImage
*pSizeOfImage += 0x1000;
}
对齐计算函数
DWORD AlignLength(DWORD Actuall_size, DWORD Align_size)
{
if (Actuall_size % Align_size == 0) {
return Actuall_size;
} else {
DWORD n = Actuall_size / Align_size;
return Align_size * (n + 1);
}
}
关键点总结
- 新增节表空间计算:必须确保有至少80字节连续空间
- 节表属性设置:
- VirtualAddress = 上一个节的VirtualAddress + max(上一个节的VirtualSize, SizeOfRawData)
- PointerToRawData = 上一个节的PointerToRawData + 上一个节的SizeOfRawData
- 对齐处理:所有地址和大小必须按照SectionAlignment和FileAlignment对齐
- 极端情况处理:当节表后无空间时,可调整PE标记位置
- 扩大节技术:当无法新增节时,可扩大最后一个节的空间
通过以上技术,可以灵活地修改PE文件结构,为后续的代码注入、数据添加等操作提供空间基础。