GetWindowLong(hWnd, GWL_WNDPROC) 只能獲取「同進程」視窗的視窗程序。跨進程調用會直接返回 0。本文詳述一種不依賴全域 Hook、輕量級的方案:在目標進程建立遠程線程,執行機器碼級別的 GetWindowLong + PostThreadMessage 回報結果。
系統內部會檢查調用進程 ID 與視窗所屬進程 ID 是否一致,不一致則返回 0。Spy++ 使用全域 Hook (WH_GETMESSAGE) 讓 DLL 注入所有進程繞過此限制,但為單一查詢裝載 Hook 過於沉重。
GetWindowThreadProcessId(hWndTarget, &dwProcessId)。OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId),失敗時需 AdjustTokenPrivileges 提權。IsWindowUnicode(hWndTarget) 決定呼叫 GetWindowLongW 仍是 GetWindowLongA。VirtualAllocEx(hProcess, NULL, 34, MEM_COMMIT, PAGE_EXECUTE_READWRITE),預留 34 位元組 (指令碼 + ret)。// 假設 hWndTarget=0x12345678, dwThreadId=0x5678, 自訂訊息 WM_MYMSG=0x1234
// GetWindowLongA(hWndTarget, GWL_WNDPROC) ; GWL_WNDPROC = -4 = 0xFC
6A FC push 0xFC
68 78 56 34 12 push 0x12345678
E8 xx xx xx xx call GetWindowLongA ; 相對偏移
50 push eax ; lParam = 返回的 WNDPROC
68 78 56 34 12 push 0x12345678 ; wParam = hWndTarget
68 34 12 00 00 push 0x1234 ; Msg
68 78 56 00 00 push 0x5678 ; dwThreadId
E8 xx xx xx xx call PostThreadMessageA
C3 ret
WriteProcessMemory 寫入指令碼 → CreateRemoteThread(hProcess, NULL, 0, pRemoteCode, NULL, 0, NULL)。MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
if (msg.message == WM_MYMSG) {
WNDPROC proc = (WNDPROC)msg.lParam; // 取得目標視窗程序
break;
}
}
VirtualFreeEx 釋放遠程記憶體。E8 [目標地址 - (當前指址+5)],需在本地精確計算遠程函數地址 (GetProcAddress 取本地 kernel32/user32 地址,假設系統 DLL 基址相同,或需額外解析)。push 0xFC 即可 (符號擴展)。| 優點 | 缺點 |
|---|---|
| 無需全域 Hook,輕量、針對性強 | 需高權限 (OpenProcess)、依賴機器碼構造,維護難度高 |
| 適用單次查詢場景 | 64 位元進程無法從 32 位元注入 (需同位元編譯)、ASLR 可能導致系統 DLL 基址不同 |
完整可編譯範例碼請參考原文配套源碼。