視窗子類化是 Windows GUI 程式設計核心技術,可攔截、修改視窗訊息行為。本文實作一個 DLL (Explorer.dll),注入 explorer.exe 進程,子類化 SysListView32 控制項,過濾隱藏包含 "VPN" 字串的圖示(不分大小寫),達到隱藏特定網路連線圖示的目的。
| API | 作用範圍 | 說明 |
|---|---|---|
SetWindowLong | 單一視窗實例 | 修改特定視窗的視窗程序 |
SetClassLong | 進程內同類別所有視窗 | 修改視窗類別的視窗程序(User32 類別,非 MFC/VCL 封裝類) |
CallWindowProc | 呼叫原視窗程序 | 處理完自訂邏輯後轉交原程序,保留控制項原生行為 |
本文採用 SetClassLong(GCL_WNDPROC) 修改 SysListView32 類別程序,影響該進程所有列表視圖控制項。超類化(如 Delphi VCL)亦受影響,因其基於 GetClassInfo 取得原類別資訊再註冊新類別。
#define MAGIC_NUMBER 0x20 // 32 項門檻,避免掃描大型資料夾影響效能
WNDPROC g_lpfnSuperClassProc = NULL;
__forceinline void HideItem(HWND hListCtrl, DWORD dwCount) {
LVITEM li = {0}; char text[50] = {0};
li.pszText = text; li.cchTextMax = 50;
for (DWORD i = 0; i < dwCount; i++) {
CallWindowProc(g_lpfnSuperClassProc, hListCtrl, LVM_GETITEMTEXT, i, (LPARAM)&li);
if (StrStrI(li.pszText, _T("vpn")) != NULL)
CallWindowProc(g_lpfnSuperClassProc, hListCtrl, LVM_DELETEITEM, i, 0);
}
}
LRESULT CALLBACK ListFilterProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
if (uMsg == WM_PAINT) {
DWORD dwCount = CallWindowProc(g_lpfnSuperClassProc, hwnd, LVM_GETITEMCOUNT, 0, 0);
if (dwCount < MAGIC_NUMBER) HideItem(hwnd, dwCount);
}
return CallWindowProc(g_lpfnSuperClassProc, hwnd, uMsg, wParam, lParam);
}
BOOL InstallListFilter() {
INITCOMMONCONTROLSEX icc = {sizeof(icc), ICC_WIN95_CLASSES};
if (!InitCommonControlsEx(&icc)) return FALSE;
HWND hWnd = CreateWindow(_T("SysListView32"), _T(""), WS_CAPTION|LVS_REPORT, 0,0,0,0, NULL,NULL,NULL,NULL);
g_lpfnSuperClassProc = (WNDPROC)GetClassLong(hWnd, GCL_WNDPROC);
SetClassLong(hWnd, GCL_WNDPROC, (LONG)ListFilterProc);
DestroyWindow(hWnd);
return TRUE;
}
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason, LPVOID lpReserved) {
if (DLL_PROCESS_ATTACH == ul_reason) InstallListFilter();
return TRUE;
}
// 取得進程 PID
DWORD Process2PID(LPCTSTR lpszProcess) {
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe = {sizeof(pe)};
Process32First(hSnap, &pe);
do { if (!lstrcmpi(lpszProcess, pe.szExeFile)) return pe.th32ProcessID; }
while (Process32Next(hSnap, &pe));
return 0;
}
// 遠端執行緒注入 LoadLibrary
BOOL InjectDLL(DWORD dwPid, LPCTSTR lpszDll) {
EnablePrivilege(SE_DEBUG_NAME);
HANDLE hProcess = OpenProcess(PROCESS_CREATE_THREAD|PROCESS_VM_OPERATION|PROCESS_VM_WRITE, FALSE, dwPid);
LPVOID pAddr = VirtualAllocEx(hProcess, NULL, MAX_PATH, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProcess, pAddr, lpszDll, lstrlen(lpszDll)+1, NULL);
LPTHREAD_START_ROUTINE pfnLoadLibrary = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
BOOL bRet = CreateRemoteThread(hProcess, NULL, 1024, pfnLoadLibrary, pAddr, 0, NULL);
CloseHandle(hProcess);
return bRet;
}
Explorer.dll、InjectDLL.exe(VC++ 6.0 SP5,Win2000 測試通過)InjectDLL explorer.exe d:\path\explorer.dll(DLL 路徑必須為絕對路徑)explorer.exe 新開啟/建立的視窗生效「Magic Number 32」假設網路連線不超過 32 個,避免掃描大型資料夾(如系統目錄)拖慢重繪。作者偏好「視窗子類化」而非聳動的「視窗劫持」,技術本質相同,術語應精準。