常見需求:監控工具、除錯器、自動化腳本需取得其他進程的命令列參數。GetCommandLine() 僅能取得自身進程參數,傳統做法是 DLL 注入 + 遠端呼叫,但這觸發防毒軟體、提高複雜度。本文揭示一個「零注入」技巧:直接讀取目標進程記憶體中 GetCommandLine 函式內部儲存命令列字串指標的位置。
核心原理
kernel32.dll!GetCommandLineA/W 在各進程中的虛擬位址相同 (ASLR 下系統 DLL 基址一致)。
- 反組譯發現該函式極簡單:
mov eax, [固定位址] / ret,該固定位址即儲存命令列字串指標。
- 我們在自身進程解析該指令取得「指標的位址」,再用
ReadProcessMemory 遠端讀取兩次:
第一次拿到字串指標,第二次拿到字串內容。
不同 Windows 版本的指令碼差異
| OS | GetCommandLineA 反組譯 (關鍵部分) | 解析邏輯 |
| NT/2K/XP/2003/Vista/7/10/11 | A1 xx xx xx xx mov eax, [addr] | 第 2-5 位元組即為 addr (儲存字串指標的全域變數位址) |
| 95/98/Me | 多層間接:mov eax, [addr1] → mov ecx, [eax] → mov eax, [ecx+0xC0] → 可能再跳 [ecx+0x40] → [eax+8] | 需模擬指令流程,逐層 ReadProcessMemory 解析 |
完整跨平台實作 (Unicode 就緒)
#include
#include
DWORD GetRemoteCommandLine(DWORD dwPID, LPTSTR lpBuffer, DWORD cchBuffer)
{
HANDLE hProc = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_LIMITED_INFORMATION, FALSE, dwPID);
if (!hProc) return GetLastError();
// 1. 取得自身 GetCommandLineW 位址
FARPROC pfn = GetProcAddress(GetModuleHandle(_T("kernel32.dll")), _T("GetCommandLineW"));
if (!pfn) { CloseHandle(hProc); return ERROR_PROC_NOT_FOUND; }
// 2. 解析指令取得「儲存字串指標的全域變數位址」
// x86: mov eax, [imm32] -> 0xA1 + 4 bytes
// x64: mov rax, [rip+disp32] -> 0x48 0xA1 + 4 bytes disp
// 這裡示範 x86;生產碼需判斷目標進程位元數並解析對應指令
BYTE* code = (BYTE*)pfn;
DWORD_PTR dwAddrOfPtr = 0;
#ifdef _WIN64
// 簡化:假設前 2 bytes 為 48 A1
if (code[0] == 0x48 && code[1] == 0xA1) {
INT32 disp = *(INT32*)(code + 2);
dwAddrOfPtr = (DWORD_PTR)(code + 6) + disp; // RIP-relative
}
#else
if (code[0] == 0xA1) {
dwAddrOfPtr = *(DWORD_PTR*)(code + 1);
}
#endif
if (!dwAddrOfPtr) { CloseHandle(hProc); return ERROR_INVALID_FUNCTION; }
// 3. 遠端讀取兩次
DWORD_PTR remoteStrPtr = 0;
SIZE_T bytesRead = 0;
if (!ReadProcessMemory(hProc, (LPCVOID)dwAddrOfPtr, &remoteStrPtr, sizeof(remoteStrPtr), &bytesRead) ||
bytesRead != sizeof(remoteStrPtr)) {
CloseHandle(hProc); return ERROR_PARTIAL_COPY;
}
if (!ReadProcessMemory(hProc, (LPCVOID)remoteStrPtr, lpBuffer, cchBuffer * sizeof(TCHAR), &bytesRead)) {
CloseHandle(hProc); return ERROR_PARTIAL_COPY;
}
lpBuffer[cchBuffer - 1] = 0; // 保底終止
CloseHandle(hProc);
return ERROR_SUCCESS;
}
實戰注意事項
- 位元數匹配:32-bit 進程讀 64-bit 進程需
PROCESS_VM_READ + PROCESS_QUERY_LIMITED_INFORMATION 並在 WoW64 下正確解析 x64 指令;建議編譯為 64-bit 專案統一處理。
- ASLR 與 DLL 重新基址:系統 DLL (
kernel32.dll, kernelbase.dll) 在同一啟動會話中跨進程基址一致,重開機後才會變,故可直接用自身位址。
- Unicode/ANSI:
GetCommandLineW 回傳 wchar_t*,讀取時以 WCHAR 緩衝區接收,再 WideCharToMultiByte 轉碼。
- 權限:需目標進程同用戶或具
SeDebugPrivilege;UAC 高完整性進程需以管理員執行。
- 防禦規避:純
ReadProcessMemory 無執行緒建立、無代碼注入,行為較隱蔽,但仍可能被 EDR 監控跨進程讀取行為。
替代方案對比
| 方法 | 優點 | 缺點 |
| 本文法 (讀記憶體) | 無注入、速度快、程式碼簡潔 | 需解析機器碼、跨位元數麻煩、可能被 EDR 攔截 |
WMI Win32_Process.CommandLine | 官方 API、支援遠端機器 | 需 WMI 服務、權限高、查詢較慢 |
| NtQueryInformationProcess + PEB | 核心級、穩定 | 非公開 API、結構跨版本變動 |
| DLL 注入 + GetCommandLine | 通用、簡單 | 觸發防毒、注入失風險、需管理載入/卸載 |
結語
「讀取 GetCommandLine 內部指標」是一項經典且實用的逆向工程技巧,適用於除錯器、進程監控、自動化測試等場景。掌握 x86/x64 指令解析、ReadProcessMemory 跨進程讀取、PEB 結構等基礎知識,即可靈活變通、舉一反三。完整專案原始碼 (含 x86/x64 自動偵測、Unicode 支援、錯誤處理) 請參考文章附件。
- 上一篇文章: Clone 模式在平衡二元樹與複雜資料結構中的深度應用
- 下一篇文章: Base64 編解碼完全指南:原理、填補、換行與高效 C++ 實作
Copyright ©
程式碼之家 All Rights Reserved