上文提到在代码节空白区内添加代码或SHELLCODE
如果空白区大小不够,可以考虑新增一个节来添加代码
现在的目标是添加0x1000
个字节
整体流程如下:读取EXE文件,添加新节修改节表等信息后,保存到新EXE文件
xxxxxxxxxx
inSize = 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
头合法
xxxxxxxxxx
if (!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
将目前的文件完整地复制过去(新增节在当前基础上新增)
xxxxxxxxxx
sizeOfFile = 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
中xxxxxxxxxx
pDosHeader = (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);
目前得到了第一个节表的信息,新增节并不是插入,而是在末尾加入节,需找到最后一节
xxxxxxxxxx
pLastSectionHeader = &pSectionHeader[pPEHeder->NumberOfSections - 1];
其实加一个括号或许更好理解,
xxxxxxxxxx
pLastSectionHeader = &(pSectionHeader[pPEHeder->NumberOfSections - 1]);
在末尾添加新节的限制条件是:有足够的空间添加新节表
参考图片,节表位于SizeOfHeaders
计算的内容中,包含了多个其他头,以及一部分的保留字节
新的节表信息应该写在这部分保留字节中,且需要确保能够最后保留一个全0的节表,据海哥说Windows
中一般认为某个节表全0的情况是结束,需要多保留一份节表表示结束
节表的大小其实是固定的28H十进制为40字节,在宏中已经定义
xxxxxxxxxx
回到计算空间的主题,其实这里用不到40长度这个信息,提到它仅是一个参考
参考上图,得到计算公式
xxxxxxxxxx
SizeOfHeader - (DOS + 垃圾数据 + PE标记 + 标准PE头 + 可选PE头 + 已存在节表) >= 2个节表的大小
对应代码如下(其中PE头大小等信息是固定的,也可以不使用sizeof
)
xxxxxxxxxx
okAddSections = (DWORD)(pOptionHeader->SizeOfHeaders - (pDosHeader->e_lfanew + 0x04 +
sizeof(PIMAGE_FILE_HEADER) + pPEHeder->SizeOfOptionalHeader + sizeof(PIMAGE_SECTION_HEADER)
* pPEHeder->NumberOfSections));
如果小于两个节表的大小认为不可添加新节表
xxxxxxxxxx
if (okAddSections < 2 * sizeof(PIMAGE_SECTION_HEADER))
{
free(pTempNewImageBuffer);
return 0;
}
取出当前NumberOfSections
字段作为偏移,NewSec
指向末尾新节
xxxxxxxxxx
numberOfSection = pPEHeder->NumberOfSections;
PIMAGE_SECTION_HEADER NewSec = pSectionHeader + numberOfSection;
找到新节各种字段的指针,用于后续修改
xxxxxxxxxx
PVOID pSecName = &NewSec->Name;
PDWORD pSecMisc = &NewSec->Misc.VirtualSize;
PDWORD pSecVirtualAddress = &NewSec->VirtualAddress;
PDWORD pSecSizeOfRawData = &NewSec->SizeOfRawData;
PDWORD pSecPointToRawData = &NewSec->PointerToRawData;
PDWORD pSecCharacteristics = &NewSec->Characteristics;
节表数量加一
xxxxxxxxxx
PWORD pNumberOfSection = &pPEHeder->NumberOfSections;
*pNumberOfSection = pPEHeder->NumberOfSections + 1;
暂时认为新增的节是0x1000
大,设置SizeOfImage
xxxxxxxxxx
PDWORD pSizeOfImage = &pOptionHeader->SizeOfImage;
*pSizeOfImage = pOptionHeader->SizeOfImage + 0x1000;
设置新节名字(长度为8)
xxxxxxxxxx
memcpy(pSecName, ".newsec", 8);
设置新节的Misc
对其前大小为0x1000
xxxxxxxxxx
*pSecMisc = 0x1000;
新增节的VirtualAddress
字段由最后一节计算得来
从最后节Misc.VirtualSize
和SizeOfRawData
中找到一个更大的,认为是实际需要的偏移
xxxxxxxxxx
DWORD add_size = pLastSectionHeader->Misc.VirtualSize > pLastSectionHeader->SizeOfRawData ?
pLastSectionHeader->Misc.VirtualSize : pLastSectionHeader->SizeOfRawData;
设置新节VirtualAddress
字段,参考上文的图片,得到新节的起始偏移
xxxxxxxxxx
*pSecVirtualAddress = pLastSectionHeader->VirtualAddress + add_size;
当前pSecVirtualAddress
没有检查是否内存对齐
如果和SectionAlignment
除不尽,认为没有对齐,需要自行对齐。先做触发拿到舍弃余数的值,加一后乘以内存对齐参数,得到对齐后的VirtualAddress
值
xxxxxxxxxx
DWORD SecAlign = pOptionHeader->SectionAlignment;
if (*pSecVirtualAddress % SecAlign)
{
*pSecVirtualAddress = (*pSecVirtualAddress / SecAlign + 1) * SecAlign;
}
以上解决了内存对齐的问题,接下来处理文件对齐
设置文件属性SizeOfRawData
为0x1000
xxxxxxxxxx
*pSecSizeOfRawData = 0x1000;
根据上一个节算出下一个节的PointToRawData
(参考开头的图)
xxxxxxxxxx
*pSecPointToRawData = pLastSectionHeader->PointerToRawData + pLastSectionHeader->SizeOfRawData;
检查对齐类似内存方式
xxxxxxxxxx
DWORD 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
写入到新增节中
xxxxxxxxxx
VOID 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
为紧接的地址
如果这样还是不够,可以考虑下一篇的办法:扩大节