还记得节与节之间无论是文件中还是内存中都会存在一定的空白区(空隙)。那么可不可以往这里面注入代码呢?
节表 VirtualSize(Misc)是指节的真实大小,暂且相信这个值的真实性。(后面有更可靠的方法)
那么如果注入代码(硬编码)长度 加上 VirtualSize 仍然没有超过(覆盖)下一个节在文件中的位置 ,
即 ( VirtualSize + 即注入代码长度 ) < SizeOfRawData
那么就可以往PE文件中注入代码了。在此之前,还需要知道一点其他的知识
为了效果显著且操作简单,就在程序运行前调用一个MessageBoxA弹窗作为注入的代码。首先在调用函数前,需要push参数,MessageBoxA有四个参数,可以全部写0. push完四个后就可以call 调用函数了,那怎么在PE文件一打开就运行我们注入的代码呢?
在可选头有个AddressOfEntryPoint字段,就是程序开始的入口点,把这个值改了指向我们的注入代码。那么一打开程序就会执行我们的代码了,如果还要保证程序正常运行,执行完我们的代码还得指回去,所以call之后还得 jmp 回原来的AddressOfEntryPoint地址
并且注意上述操作涉及的地址全都是RVA,而且由于我们是直接对文件操作(不用FileBuffer2ImageBuffer 再改再转回来了,那样太麻烦)我们直接用上次写好的FOA2RVA的直接在未拉伸的文件中转换即可。
至于push 和 call jmp 的硬编码,可以直接查询x86下他们的硬编码或者拖个PE文件进IDA或者OD看。这里直接给出push四个0参数的硬编码 0x6A,0x00,0x6A,0x00,0x6A,0x00,0x6A,0x00, call的硬编码0xE8,后跟四字节,需要跳转地址进行一定计算,jmp为0xE9,后四字节也一样的方式。具体为:
call X
X = 要跳转的地址 - call指令下一条指令的地址
由于call指令占5字节,所以上式化为 X = 要跳转的地址 - ( E8所在地址 + 5 )
E8为call指令的第一个字节,固定值
那么要跳转的地址即MessageboxA地址,但这个地址有点麻烦,目前先体验一下简单的做法,这里可能需要点逆向基础。打开OD拖入任一exe,Ctrl+G搜索MessageboxA,记录下MessageboxA起始地址,可以写入代码中,但下次开机需要重新这个操作,当然这只是个实例,不一定添加MessageboxA的调用。具体细节在代码具体应用中有更好理解,有详细注释。
jmp同理。 那么附上上述思路实现的简单代码(找个个节表空隙够的exe,这里shellcode弹窗很短一般都够的,这个代码只找了第一个节,可以写个循环一个节不够判断下一个节之间的空隙)
(MemeryToFile 函数已在上一章中贴出)
#include "windows.h"
#include "stdio.h"
#define MESSAGEBOXADDR 0x76AF39A0 //这个值需要将任一exe文件拖入OD打开,搜索 MessageBoxA 记录它的地址到这里(每次开机都不同)
unsigned char ShellCode317[] =
{
0x6A,0x00,0x6A,0x00,0x6A,0x00,0x6A,0x00, //这行是push 四个0 作为 MessageBox 的参数
0xE8,0x00,0x00,0x00,0x00,
0xE9,0x00,0x00,0x00,0x00
};
void h317()
{
char FilePath[] = "CrackHead.exe";
char CopyFilePath[] = "CrackHeadcopy.exe";
LPVOID pFileBuffer = NULL; //会被函数改变的 函数输出之一
LPVOID* ppFileBuffer = &pFileBuffer; //传进函数的形参
int SizeOfFileBuffer;
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pFileHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
DWORD CallX = NULL; //即E8后跟的4字节
DWORD JmpX = NULL; //即E9后跟的4字节
SizeOfFileBuffer = ReadPEFile(FilePath, ppFileBuffer); //pFileBuffer即指向已装载到内存中的exe首部
/*pFileBuffer = *ppFileBuffer;*/
if (!SizeOfFileBuffer)
{
printf("文件读取失败\n");
return;
}
//Dos头
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer; // 强转 DOS_HEADER 结构体指针
//NT头
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
//PE头
pFileHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4); //NT头地址 + 4 为 FileHeader 首址
//可选PE头
pOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER);//SIZEOF_FILE_HEADER为固定值且不存在于PE文件字段中
//首个节表
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);
if ( pSectionHeader->Misc.VirtualSize + sizeof(ShellCode317) > pSectionHeader->SizeOfRawData)
{
printf("空间不足");
free(pFileBuffer);
return;
}
//X即E8后的数 = 要跳转的地址 - (E8所在地址 + 5) (E8 所在地址+5 即 call指令的下一条指令的地址)
//那么要跳转的地址即messageboxA地址。E8所在地址即 ImageBase内存运行基址 + VirtualAddress节所在偏移 + VirtualSize 节真正长度即节结束的偏移 再加上8才到E8
CallX = MESSAGEBOXADDR - (pOptionalHeader->ImageBase + pSectionHeader->VirtualAddress + pSectionHeader->Misc.VirtualSize +8 +5);
//jump 要跳转的地址即OEP程序入口点, X = 程序入口点 - (E9所在地址 + 5)
//这里程序入口点即ImageBase基址 + AddressOfEntryPoint E9所在地址计算同上
JmpX = pOptionalHeader->ImageBase + pOptionalHeader->AddressOfEntryPoint - (pOptionalHeader->ImageBase + pSectionHeader->VirtualAddress + pSectionHeader->Misc.VirtualSize + 13 + 5);
//将上述计算后的值放入ShellCode317
*(PDWORD)(ShellCode317 + 9) = CallX;
*(PDWORD)(ShellCode317 + 14) = JmpX;
for(int i = 0;i<sizeof(ShellCode317);i++)
{
printf("%x ", ShellCode317[i]);
}
printf("\n");
//拷贝ShellCode317到内存中
memcpy((void*)((DWORD)pFileBuffer + pSectionHeader->PointerToRawData + pSectionHeader->Misc.VirtualSize), ShellCode317,sizeof(ShellCode317));
//修改OEP的值
pOptionalHeader->AddressOfEntryPoint = pSectionHeader->VirtualAddress + pSectionHeader->Misc.VirtualSize;
MemeryToFile(pFileBuffer, SizeOfFileBuffer, CopyFilePath);
}
程序运行输出:
6a 0 6a 0 6a 0 6a 0 e8 b9 24 6f 76 e9 14 fb ff ff
success
打开生成的另一份exe
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLyUjM2QDN0EjMyITOwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
点击确定后才是原来的程序