上一篇中我以手动的方式将硬编码的SHELLCODE
添加到.text
节空白区,并手动修改OEP
地址,成功弹框。这一节将以代码的方式计算偏移以及跳转地址,自动完成之前的任务
定义需要的SHELLCODE
(地址为空,后续添加)
xxxxxxxxxx
BYTE ShellCode[] =
{
0x6A,0x00,0x6A,0x00,0x6A,0x00,0x6A,0x00,
0xE8,0x00,0x00,0x00,0x00,
0xE9,0x00,0x00,0x00,0x00
};
将FileBuffer
拉伸转换到ImageBuffer
xxxxxxxxxx
ReadPEFile(FilePath, &pFileBuffer);
if (!pFileBuffer)
{
return;
}
CopyFileBufferToImageBuffer(pFileBuffer, &pImageBuffer);
if (!pImageBuffer)
{
free(pFileBuffer);
return;
}
计算SHELLCODE
长度后续使用(18 0x12)
xxxxxxxxxx
DWORD SHELLCODELENGTH = sizeof(ShellCode) / sizeof(ShellCode[0]);
NT头以DWORD Signature
开始,通过e_lfanew
得到NT头偏移,加4得到PE头,而PE头大小是固定的IMAGE_SIZEOF_FILE_HEADER
,相加得到可选PE头首地址
节表偏移需要可选PE头的大小信息,可以通过两种方式获得:
IMAGE_SIZEOF_NT_OPTIONAL32_HEADER 224
(在VS2022
中找不到)PEHeader
内的SizeOfOptionalHeader
获得(之前的方式)xxxxxxxxxx
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);
计算剩余空间:使用SIzeOfRawData-Misc
拿到可用的空白区
代码如下,确保空白区可以写得下硬编码的SHELLCODE
xxxxxxxxxx
DWORD space = (pSectionHeader->SizeOfRawData) - (pSectionHeader->Misc.VirtualSize);
if (space < SHELLCODELENGTH)
{
printf("当前节剩余空间不足");
free(pFileBuffer);
free(pImageBuffer);
return;
}
通过VirtualAddress
与Misc
得到拉伸后ImageBuffer
中可用空白区偏移
xxxxxxxxxx
codeBegin = (PBYTE)((DWORD)pImageBuffer + pSectionHeader->VirtualAddress + pSectionHeader->Misc.VirtualSize);
使用memcpy
将SHELLCODE
复制到对应的位置
xxxxxxxxxx
memcpy(codeBegin, ShellCode, SHELLCODELENGTH);
到这里我们已经成功将写入SHELLCODE
到ImageBuffer
第一个节的空白区中
xxxxxxxxxx
6A 00 6A 00 6A 00 6A 00 E8 00 00 00 00 E9 00 00 00 00
上文提到,每台机器的MessageBox
函数地址不一样,通过某些方式我找到自己机器是0x75789350
修正方式是:X = 要跳转的地址 - 下一条指令地址
MESSAGEBOXADDR=0x75789350
ImageBase
加ImageBuffer
中插入的E9
偏移注意实际跳转偏移必须用ImageBuffer
计算,这是加载到内存中的数据,而FileBuffer
仅是文件
目前得到的codeBegin
是相对于当前pImageBuffer
地址6A
的偏移,使用codeBegin - pImageBuffer
得到从0开始计算的6A
偏移,而E9
相对于6A
偏移是0x0D
,相加得到下一条指令从0开始的偏移
xxxxxxxxxx
DWORD offset = (DWORD)codeBegin - (DWORD)pImageBuffer + (DWORD)0x0D;
最后E8
这里CALL
的参数计算方式如下:
xxxxxxxxxx
DWORD callAddr = (MESSAGEBOXADDR - (pOptionHeader->ImageBase + offset));
修改E8
之后CALL
的参数:注意这里是修改了ImageBuffer
中之前占位的00
(偏移9)
xxxxxxxxxx
*(PDWORD)(codeBegin + 0x09) = callAddr;
下一步应该修改E9
后JMP
的参数
目标是实现JMP AddressOfEntryPoint
,参考公式X = 要跳转的地址 - 下一条指令地址
ImageBase + AddressOfEntryPoint
ImageBase + jmpOffset
其中jmpOffset
为E9
下一条指令从0开始的偏移所以这里的jmpOffset
计算方式如下,用相对于ImageBuffer
的空白区偏移codeBegin
减去ImageBuffer
的地址,得到从0开始的空白区偏移地址,加上SHELLCODE
长度得到下一条指令的地址(虽然为空)
xxxxxxxxxx
DWORD jmpOffset = (DWORD)codeBegin - (DWORD)pImageBuffer + (DWORD)SHELLCODELENGTH;
使用公式得到EP
后JMP
真正的参数
xxxxxxxxxx
DWORD jmpAddr = ((pOptionHeader->ImageBase + pOptionHeader->AddressOfEntryPoint) -
(pOptionHeader->ImageBase + jmpOffset));
修改E9
参数(E9
在SHELLCODE
的偏移是0x0E
)
xxxxxxxxxx
*(PDWORD)(codeBegin + 0x0E) = jmpAddr;
现在已经实现了正确可用的SHELLCODE
但还需要修改程序入口,使其跳转到空白区新增的代码,即从0开始的空白区偏移地址,也就是SHELLCODE
第一个字节6A
的地址,比较简单
xxxxxxxxxx
pOptionHeader->AddressOfEntryPoint = (DWORD)codeBegin - (DWORD)pImageBuffer;
以上我们的操作都是在ImageBuffer
中完成的,下一步需要利用之前的FileBuffer
与ImageBuffer
互转代码,得到一个可用的EXE
数据指针pNewBuffer
,再将它保存到文件即可
xsize = CopyImageBufferToNewBuffer(pImageBuffer, &pNewBuffer);
if (size == 0 || !pNewBuffer)
{
free(pFileBuffer);
free(pImageBuffer);
return;
}
isOK = MemeryTOFile(pNewBuffer, size, OutFilePath);
如果代码节内不够写怎么办?虽然这种情况不太常见,但可以做一个思考
这个问题的解决办法如下,节表指针加一得到下一个节
xxxxxxxxxx
pSectionHeader++;
// 加两次取第三个节
pSectionHeader++;
如果发现修改后无法执行,可以尝试复制代码节的属性(我没有修改属性就触发了)
xxxxxxxxxx
// 第一个节:代码节的属性
DWORD x = pSectionHeader->Characteristics;
// ...
// 第n个节的属性
DWORD y = pSectionHeader->Characteristics;
pSectionHeader->Characteristics = x | y;
FileBuffer
与ImageBuffer
互转代码在上文中已经提到,所以省略
添加任意代码到节空白区的函数如下
xxxxxxxxxx
VOID AddCodeInCodeSec(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 space = (pSectionHeader->SizeOfRawData) - (pSectionHeader->Misc.VirtualSize);
if (space < SHELLCODELENGTH)
{
free(pFileBuffer);
free(pImageBuffer);
return;
}
codeBegin = (PBYTE)((DWORD)pImageBuffer + pSectionHeader->VirtualAddress + pSectionHeader->Misc.VirtualSize);
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);
}
读写文件代码
xxxxxxxxxx
DWORD ReadPEFile(IN LPSTR lpszFile, OUT LPVOID* pFileBuffer)
{
FILE* pFile = NULL;
DWORD fileSize = 0;
LPVOID pTempFileBuffer = NULL;
fopen_s(&pFile, lpszFile, "rb");
if (!pFile)
{
return 0;
}
fseek(pFile, 0, SEEK_END);
fileSize = ftell(pFile);
fseek(pFile, 0, SEEK_SET);
pTempFileBuffer = malloc(fileSize);
if (!pTempFileBuffer)
{
fclose(pFile);
return 0;
}
size_t n = fread(pTempFileBuffer, fileSize, 1, pFile);
if (!n)
{
free(pTempFileBuffer);
fclose(pFile);
return 0;
}
*pFileBuffer = pTempFileBuffer;
pTempFileBuffer = NULL;
fclose(pFile);
return fileSize;
}
BOOL MemeryTOFile(IN LPVOID pMemBuffer, IN size_t size, OUT LPSTR lpszFile)
{
FILE* fp = NULL;
fopen_s(&fp, lpszFile, "wb+");
if (!fp)
{
return FALSE;
}
fwrite(pMemBuffer, size, 1, fp);
fclose(fp);
fp = NULL;
return TRUE;
}