上文提到在代码节空白区内添加代码或SHELLCODE
如果空白区大小不够,可以考虑新增一个节来添加代码
现在的目标是添加0x1000个字节
整体流程如下:读取EXE文件,添加新节修改节表等信息后,保存到新EXE文件
xxxxxxxxxxinSize = ReadPEFile(FilePathIn, &pFileBuffer);if (inSize == 0 || !pFileBuffer){ return;}outSize = CopyFileBufferToNewImageBuffer(pFileBuffer, inSize, &pNewImageBuffer);if (outSize == 0 || !pFileBuffer){ free(pFileBuffer); return;}
isOK = MemeryToFile(pNewImageBuffer, outSize, FilePathOut);if (isOK){ return;}基本处理:判断DOS头和NT/PE头合法
xxxxxxxxxxif (!pFileBuffer){ return 0;}
if ((*(PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE){ return 0;}pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;if (*((PWORD)((DWORD)pFileBuffer + pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE){ return 0;}在当前文件基础上增加0x1000大小字节,并malloc分配内存,设置初始值为0
最后一行memcpy将目前的文件完整地复制过去(新增节在当前基础上新增)
xxxxxxxxxxsizeOfFile = fileSize + 0x1000;pTempNewImageBuffer = malloc(sizeOfFile);if (!pTempNewImageBuffer){ return 0;}memset(pTempNewImageBuffer, 0, sizeOfFile);memcpy(pTempNewImageBuffer, pFileBuffer, fileSize);解析PE头部分代码在之前文章中出现过多次:
e_lfanew记录了NT头偏移IMAGE_SIZEOF_FILE_HEADER得到可选PE头偏移SizeOfOptionalHeader中xxxxxxxxxxpDosHeader = (PIMAGE_DOS_HEADER)(pTempNewImageBuffer);pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pTempNewImageBuffer + pDosHeader->e_lfanew);pPEHeder = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 0x04);pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)(((DWORD)pPEHeder) + IMAGE_SIZEOF_FILE_HEADER);pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeder->SizeOfOptionalHeader);目前得到了第一个节表的信息,新增节并不是插入,而是在末尾加入节,需找到最后一节
xxxxxxxxxxpLastSectionHeader = &pSectionHeader[pPEHeder->NumberOfSections - 1];其实加一个括号或许更好理解,
xxxxxxxxxxpLastSectionHeader = &(pSectionHeader[pPEHeder->NumberOfSections - 1]);
在末尾添加新节的限制条件是:有足够的空间添加新节表

参考图片,节表位于SizeOfHeaders计算的内容中,包含了多个其他头,以及一部分的保留字节
新的节表信息应该写在这部分保留字节中,且需要确保能够最后保留一个全0的节表,据海哥说Windows中一般认为某个节表全0的情况是结束,需要多保留一份节表表示结束
节表的大小其实是固定的28H十进制为40字节,在宏中已经定义
xxxxxxxxxx回到计算空间的主题,其实这里用不到40长度这个信息,提到它仅是一个参考
参考上图,得到计算公式
xxxxxxxxxxSizeOfHeader - (DOS + 垃圾数据 + PE标记 + 标准PE头 + 可选PE头 + 已存在节表) >= 2个节表的大小对应代码如下(其中PE头大小等信息是固定的,也可以不使用sizeof)
xxxxxxxxxxokAddSections = (DWORD)(pOptionHeader->SizeOfHeaders - (pDosHeader->e_lfanew + 0x04 + sizeof(PIMAGE_FILE_HEADER) + pPEHeder->SizeOfOptionalHeader + sizeof(PIMAGE_SECTION_HEADER) * pPEHeder->NumberOfSections));如果小于两个节表的大小认为不可添加新节表
xxxxxxxxxxif (okAddSections < 2 * sizeof(PIMAGE_SECTION_HEADER)){ free(pTempNewImageBuffer); return 0;}
取出当前NumberOfSections字段作为偏移,NewSec指向末尾新节
xxxxxxxxxxnumberOfSection = pPEHeder->NumberOfSections;PIMAGE_SECTION_HEADER NewSec = pSectionHeader + numberOfSection;找到新节各种字段的指针,用于后续修改
xxxxxxxxxxPVOID pSecName = &NewSec->Name;PDWORD pSecMisc = &NewSec->Misc.VirtualSize;PDWORD pSecVirtualAddress = &NewSec->VirtualAddress;PDWORD pSecSizeOfRawData = &NewSec->SizeOfRawData;PDWORD pSecPointToRawData = &NewSec->PointerToRawData;PDWORD pSecCharacteristics = &NewSec->Characteristics;节表数量加一
xxxxxxxxxxPWORD pNumberOfSection = &pPEHeder->NumberOfSections;*pNumberOfSection = pPEHeder->NumberOfSections + 1;暂时认为新增的节是0x1000大,设置SizeOfImage
xxxxxxxxxxPDWORD pSizeOfImage = &pOptionHeader->SizeOfImage;*pSizeOfImage = pOptionHeader->SizeOfImage + 0x1000;设置新节名字(长度为8)
xxxxxxxxxxmemcpy(pSecName, ".newsec", 8);设置新节的Misc对其前大小为0x1000
xxxxxxxxxx*pSecMisc = 0x1000;新增节的VirtualAddress字段由最后一节计算得来
从最后节Misc.VirtualSize和SizeOfRawData中找到一个更大的,认为是实际需要的偏移
xxxxxxxxxxDWORD add_size = pLastSectionHeader->Misc.VirtualSize > pLastSectionHeader->SizeOfRawData ? pLastSectionHeader->Misc.VirtualSize : pLastSectionHeader->SizeOfRawData;设置新节VirtualAddress字段,参考上文的图片,得到新节的起始偏移
xxxxxxxxxx*pSecVirtualAddress = pLastSectionHeader->VirtualAddress + add_size;
当前pSecVirtualAddress没有检查是否内存对齐
如果和SectionAlignment除不尽,认为没有对齐,需要自行对齐。先做触发拿到舍弃余数的值,加一后乘以内存对齐参数,得到对齐后的VirtualAddress值
xxxxxxxxxxDWORD SecAlign = pOptionHeader->SectionAlignment;if (*pSecVirtualAddress % SecAlign){ *pSecVirtualAddress = (*pSecVirtualAddress / SecAlign + 1) * SecAlign;}以上解决了内存对齐的问题,接下来处理文件对齐
设置文件属性SizeOfRawData为0x1000
xxxxxxxxxx*pSecSizeOfRawData = 0x1000;根据上一个节算出下一个节的PointToRawData(参考开头的图)
xxxxxxxxxx*pSecPointToRawData = pLastSectionHeader->PointerToRawData + pLastSectionHeader->SizeOfRawData;检查对齐类似内存方式
xxxxxxxxxxDWORD RawAlign = pOptionHeader->FileAlignment;if (*pSecPointToRawData % RawAlign){ *pSecPointToRawData = (*pSecPointToRawData / RawAlign + 1) * RawAlign;}
属性设置,采用第一节(代码节)的属性内容
xxxxxxxxxx*pSecCharacteristics = pSectionHeader->Characteristics;返回新ImageBuffer指针(这里代码有一些歧义,实际上不是拉伸后的ImageBuffer而是FileBuffer)
xxxxxxxxxx*pNewImageBuffer = pTempNewImageBuffer;pTempNewImageBuffer = NULL;
现在新增了0x1000长度的节,可以随意地写入SHELLCODE了
修改上文的代码,即可将SHELLCODE写入到新增节中
xxxxxxxxxxVOID AddCodeInNewSec(IN CHAR* FilePath, DWORD MESSAGEBOXADDR, OUT CHAR* OutFilePath){ LPVOID pFileBuffer = NULL; LPVOID pImageBuffer = NULL; LPVOID pNewBuffer = NULL; PIMAGE_DOS_HEADER pDosHeader = NULL; PIMAGE_FILE_HEADER pPEHeader = NULL; PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL; PIMAGE_SECTION_HEADER pSectionHeader = NULL; PBYTE codeBegin = NULL; BOOL isOK = FALSE; DWORD size = 0;
ReadPEFile(FilePath, &pFileBuffer); if (!pFileBuffer) { return; } CopyFileBufferToImageBuffer(pFileBuffer, &pImageBuffer); if (!pImageBuffer) { free(pFileBuffer); return; }
DWORD SHELLCODELENGTH = sizeof(ShellCode) / sizeof(ShellCode[0]);
pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer; pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pImageBuffer + pDosHeader->e_lfanew) + 4); pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER); pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
DWORD LastSec = pPEHeader->NumberOfSections - 1; pSectionHeader += LastSec;
codeBegin = (PBYTE)((DWORD)pImageBuffer + pSectionHeader->VirtualAddress);
memcpy(codeBegin, ShellCode, SHELLCODELENGTH);
DWORD offset = (DWORD)codeBegin - (DWORD)pImageBuffer + (DWORD)0x0D; DWORD callAddr = (MESSAGEBOXADDR - (pOptionHeader->ImageBase + offset)); *(PDWORD)(codeBegin + 0x09) = callAddr;
DWORD jmpOffset = (DWORD)codeBegin - (DWORD)pImageBuffer + (DWORD)SHELLCODELENGTH; DWORD jmpAddr = ((pOptionHeader->ImageBase + pOptionHeader->AddressOfEntryPoint) - (pOptionHeader->ImageBase + jmpOffset)); *(PDWORD)(codeBegin + 0x0E) = jmpAddr;
pOptionHeader->AddressOfEntryPoint = (DWORD)codeBegin - (DWORD)pImageBuffer; size = CopyImageBufferToNewBuffer(pImageBuffer, &pNewBuffer); if (size == 0 || !pNewBuffer) { free(pFileBuffer); free(pImageBuffer); return; }
isOK = MemeryToFile(pNewBuffer, size, OutFilePath); if (isOK) { return; } free(pFileBuffer); free(pImageBuffer); free(pNewBuffer);}
截图:

本文已经默认了节表到第一节之间的垃圾数据足够写入两个节表,当这个空间不足时,应该如何解决问题?
注意到DOS头的e_lfanew记录了NT头地址,在DOS头和NT头之间的部分填充了垃圾数据,可以将NT头以及后续所有字节向上提到紧接着DOS头的地方,并修改e_lfanew为紧接的地址
如果这样还是不够,可以考虑下一篇的办法:扩大节