上一篇中我以手动的方式将硬编码的SHELLCODE添加到.text节空白区,并手动修改OEP地址,成功弹框。这一节将以代码的方式计算偏移以及跳转地址,自动完成之前的任务
定义需要的SHELLCODE(地址为空,后续添加)
xxxxxxxxxxBYTE ShellCode[] ={ 0x6A,0x00,0x6A,0x00,0x6A,0x00,0x6A,0x00, 0xE8,0x00,0x00,0x00,0x00, 0xE9,0x00,0x00,0x00,0x00};将FileBuffer拉伸转换到ImageBuffer
xxxxxxxxxxReadPEFile(FilePath, &pFileBuffer);if (!pFileBuffer){ return;}CopyFileBufferToImageBuffer(pFileBuffer, &pImageBuffer);if (!pImageBuffer){ free(pFileBuffer); return;}计算SHELLCODE长度后续使用(18 0x12)
xxxxxxxxxxDWORD 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获得(之前的方式)xxxxxxxxxxpDosHeader = (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
xxxxxxxxxxDWORD space = (pSectionHeader->SizeOfRawData) - (pSectionHeader->Misc.VirtualSize);if (space < SHELLCODELENGTH){ printf("当前节剩余空间不足"); free(pFileBuffer); free(pImageBuffer); return;}通过VirtualAddress与Misc得到拉伸后ImageBuffer中可用空白区偏移
xxxxxxxxxxcodeBegin = (PBYTE)((DWORD)pImageBuffer + pSectionHeader->VirtualAddress + pSectionHeader->Misc.VirtualSize);使用memcpy将SHELLCODE复制到对应的位置
xxxxxxxxxxmemcpy(codeBegin, ShellCode, SHELLCODELENGTH);到这里我们已经成功将写入SHELLCODE到ImageBuffer第一个节的空白区中
xxxxxxxxxx6A 00 6A 00 6A 00 6A 00 E8 00 00 00 00 E9 00 00 00 00
上文提到,每台机器的MessageBox函数地址不一样,通过某些方式我找到自己机器是0x75789350
修正方式是:X = 要跳转的地址 - 下一条指令地址
MESSAGEBOXADDR=0x75789350ImageBase加ImageBuffer中插入的E9偏移注意实际跳转偏移必须用ImageBuffer计算,这是加载到内存中的数据,而FileBuffer仅是文件
目前得到的codeBegin是相对于当前pImageBuffer地址6A的偏移,使用codeBegin - pImageBuffer得到从0开始计算的6A偏移,而E9相对于6A偏移是0x0D,相加得到下一条指令从0开始的偏移
xxxxxxxxxxDWORD offset = (DWORD)codeBegin - (DWORD)pImageBuffer + (DWORD)0x0D;最后E8这里CALL的参数计算方式如下:
xxxxxxxxxxDWORD callAddr = (MESSAGEBOXADDR - (pOptionHeader->ImageBase + offset));修改E8之后CALL的参数:注意这里是修改了ImageBuffer中之前占位的00(偏移9)
xxxxxxxxxx*(PDWORD)(codeBegin + 0x09) = callAddr;下一步应该修改E9后JMP的参数
目标是实现JMP AddressOfEntryPoint,参考公式X = 要跳转的地址 - 下一条指令地址
ImageBase + AddressOfEntryPointImageBase + jmpOffset其中jmpOffset为E9下一条指令从0开始的偏移所以这里的jmpOffset计算方式如下,用相对于ImageBuffer的空白区偏移codeBegin减去ImageBuffer的地址,得到从0开始的空白区偏移地址,加上SHELLCODE长度得到下一条指令的地址(虽然为空)
xxxxxxxxxxDWORD jmpOffset = (DWORD)codeBegin - (DWORD)pImageBuffer + (DWORD)SHELLCODELENGTH;使用公式得到EP后JMP真正的参数
xxxxxxxxxxDWORD jmpAddr = ((pOptionHeader->ImageBase + pOptionHeader->AddressOfEntryPoint) - (pOptionHeader->ImageBase + jmpOffset));修改E9参数(E9在SHELLCODE的偏移是0x0E)
xxxxxxxxxx*(PDWORD)(codeBegin + 0x0E) = jmpAddr;
现在已经实现了正确可用的SHELLCODE但还需要修改程序入口,使其跳转到空白区新增的代码,即从0开始的空白区偏移地址,也就是SHELLCODE第一个字节6A的地址,比较简单
xxxxxxxxxxpOptionHeader->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);
如果代码节内不够写怎么办?虽然这种情况不太常见,但可以做一个思考
这个问题的解决办法如下,节表指针加一得到下一个节
xxxxxxxxxxpSectionHeader++;// 加两次取第三个节pSectionHeader++;
如果发现修改后无法执行,可以尝试复制代码节的属性(我没有修改属性就触发了)
xxxxxxxxxx// 第一个节:代码节的属性DWORD x = pSectionHeader->Characteristics;// ...// 第n个节的属性DWORD y = pSectionHeader->Characteristics;pSectionHeader->Characteristics = x | y;
FileBuffer与ImageBuffer互转代码在上文中已经提到,所以省略
添加任意代码到节空白区的函数如下
xxxxxxxxxxVOID 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);}
读写文件代码
xxxxxxxxxxDWORD 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;}