首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

跨進程遍歷與查找外部程式 TreeView 控制項項目:VirtualAllocEx/WriteProcessMemory/ReadProcessMemory 實戰

     編輯:關於VC++

前言

需在外部程式(如金山詞霸 2002 附錄)的 TreeView 中查找特定節點(古詩標題)。由於 TreeView 內容駐留於目標進程記憶體,必須跨進程傳遞 TVITEM 結構與字串緩衝區。本文分兩階段詳細實現:順序遍歷所有可見節點 與 指定名稱查找節點。

一、核心 API 與訊息

訊息/函數用途關鍵參數
TVM_GETNEXTITEM取得節點控制碼TVGN_ROOT 根節點, TVGN_NEXTVISIBLE 下一個可見節點
TVM_EXPAND展開/摺疊節點TVE_EXPAND 展開
TVM_SELECTITEM選中節點TVGN_CARET 焦點選中
TVM_GETITEM讀取節點資訊需 TVITEM* 指向目標進程記憶體
VirtualAllocEx在目標進程配置記憶體MEM_COMMIT | PAGE_READWRITE
WriteProcessMemory寫入結構/緩衝區位址-
ReadProcessMemory讀回字串內容-

二、階段一:順序遍歷所有節點 (Demo: TV_Demo_SeqShow)

關鍵步驟

  1. 取得目標 TreeView 視窗控制碼(Spy++ 獲取)。
  2. SendMessage(hwnd, TVM_GETNEXTITEM, TVGN_ROOT, 0) 取得根節點。
  3. 迴圈:TVM_EXPAND(TVE_EXPAND) 展開 → TVM_GETNEXTITEM(TVGN_NEXTVISIBLE) 取下一個 → TVM_SELECTITEM 選中 → 直到無下一個節點。

核心遍歷代碼

HTREEITEM hItem = (HTREEITEM)::SendMessage(hwnd, TVM_GETNEXTITEM, TVGN_ROOT, 0);
::SendMessage(hwnd, TVM_SELECTITEM, TVGN_CARET, (LPARAM)hItem);

while (hItem) {
    // 嘗試展開
    while (::SendMessage(hwnd, TVM_EXPAND, TVE_EXPAND, (LPARAM)hItem)) {
        hItem = (HTREEITEM)::SendMessage(hwnd, TVM_GETNEXTITEM, TVGN_NEXTVISIBLE, (LPARAM)hItem);
        ::SendMessage(hwnd, TVM_SELECTITEM, TVGN_CARET, (LPARAM)hItem);
    }
    // 無法展開,移至下一個可見
    hItem = (HTREEITEM)::SendMessage(hwnd, TVM_GETNEXTITEM, TVGN_NEXTVISIBLE, (LPARAM)hItem);
    ::SendMessage(hwnd, TVM_SELECTITEM, TVGN_CARET, (LPARAM)hItem);
}

三、階段二:跨進程查找指定名稱節點 (Demo: TV_Demo_SeqSearch)

跨進程記憶體佈局準備

// 1. 取得目標進程 ID 並開啟
DWORD dwPID; GetWindowThreadProcessId(hwnd, &dwPID);
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);

// 2. 在目標進程配置 TVITEM 結構
TVITEM* pRemoteItem = (TVITEM*)VirtualAllocEx(hProcess, NULL, sizeof(TVITEM), MEM_COMMIT, PAGE_READWRITE);

// 3. 在目標進程配置字串緩衝區 (512 bytes)
char* pRemoteText = (char*)VirtualAllocEx(hProcess, NULL, 512, MEM_COMMIT, PAGE_READWRITE);

// 4. 本地填寫 TVITEM,指定 pszText 指向遠端緩衝區
TVITEM localItem = {0};
localItem.mask = TVIF_TEXT;
localItem.cchTextMax = 512;
localItem.pszText = pRemoteText; // 關鍵:指向目標進程位址

// 5. 寫入目標進程
WriteProcessMemory(hProcess, pRemoteItem, &localItem, sizeof(TVITEM), NULL);

遍歷 + 查找整合邏輯

BOOL bFound = FALSE;
HTREEITEM hItem = (HTREEITEM)::SendMessage(hwnd, TVM_GETNEXTITEM, TVGN_ROOT, 0);
::SendMessage(hwnd, TVM_SELECTITEM, TVGN_CARET, (LPARAM)hItem);

// 更新遠端 hItem 並讀取根節點文字
localItem.hItem = hItem;
WriteProcessMemory(hProcess, pRemoteItem, &localItem, sizeof(TVITEM), NULL);
::SendMessage(hwnd, TVM_GETITEM, 0, (LPARAM)pRemoteItem);
ReadProcessMemory(hProcess, pRemoteText, szLocalBuf, 512, NULL);
if (_strnicmp(szLocalBuf, szTarget, strlen(szTarget)) == 0) { bFound = TRUE; goto END; }

while (hItem && !bFound) {
    while (::SendMessage(hwnd, TVM_EXPAND, TVE_EXPAND, (LPARAM)hItem)) {
        hItem = (HTREEITEM)::SendMessage(hwnd, TVM_GETNEXTITEM, TVGN_NEXTVISIBLE, (LPARAM)hItem);
        ::SendMessage(hwnd, TVM_SELECTITEM, TVGN_CARET, (LPARAM)hItem);

        localItem.hItem = hItem;
        WriteProcessMemory(hProcess, pRemoteItem, &localItem, sizeof(TVITEM), NULL);
        ::SendMessage(hwnd, TVM_GETITEM, 0, (LPARAM)pRemoteItem);
        ReadProcessMemory(hProcess, pRemoteText, szLocalBuf, 512, NULL);
        if (_strnicmp(szLocalBuf, szTarget, strlen(szTarget)) == 0) { bFound = TRUE; break; }
    }
    if (bFound) break;

    hItem = (HTREEITEM)::SendMessage(hwnd, TVM_GETNEXTITEM, TVGN_NEXTVISIBLE, (LPARAM)hItem);
    ::SendMessage(hwnd, TVM_SELECTITEM, TVGN_CARET, (LPARAM)hItem);

    localItem.hItem = hItem;
    WriteProcessMemory(hProcess, pRemoteItem, &localItem, sizeof(TVITEM), NULL);
    ::SendMessage(hwnd, TVM_GETITEM, 0, (LPARAM)pRemoteItem);
    ReadProcessMemory(hProcess, pRemoteText, szLocalBuf, 512, NULL);
    if (_strnicmp(szLocalBuf, szTarget, strlen(szTarget)) == 0) { bFound = TRUE; break; }
}

END:
if (bFound) MessageBox(_T("找到目標節點!"));
// 釋放資源
VirtualFreeEx(hProcess, pRemoteItem, 0, MEM_RELEASE);
VirtualFreeEx(hProcess, pRemoteText, 0, MEM_RELEASE);
CloseHandle(hProcess);

四、使用方法與效果

  1. Spy++ → Find Window → 取得目標 TreeView 控制碼 (Hex)。
  2. 輸入本工具「TreeView Handle」欄位 → 點擊 GO。
  3. 遍歷模式:TreeView 會自動展開並逐項選中;查找模式:輸入關鍵字,找到即停止並高亮該節點。

TreeView 結構示意

工具介面

查找成功效果

五、注意事項與優化方向

  • 需管理員權限執行(OpenProcess(PROCESS_ALL_ACCESS))。
  • 64 位目標進程需編譯 64 位工具,指標大小不同。
  • 大量節點時遍歷較慢,可優化:TVM_GETITEM 批量讀取、多執行緒分段遍歷。
  • 僅供學習/輔助用途,勿用於惡意竊取資料。

結語

跨進程 TreeView 操作的核心在於 VirtualAllocEx/WriteProcessMemory/ReadProcessMemory 構建遠端 TVITEM 與字串緩衝區,配合 TVM_GETNEXTITEM/EXPAND/GETITEM 訊息實現遍歷與查找。此技術同樣適用於 ListView、ListBox 等支援類似訊息的控制項。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved