简介
有时候我们需要拦截用户双击启动的行为,比如用户运行A程序,我们拦截后改运行B程序,
可以在整个启动流程中进行HOOK操作,通常的办法都是针对 CreateProcess
进行处理,
我们这里改在更早的 CDefFolderMenu::InvokeCommand
处进行拦截
工作原理
我们使用 WinDbg
附加到 explorer.exe
中,并在 CreateProcess
处下断点,然后双击某程序
触发断点后,使用 k
指令查看函数调用栈,可以知道从双击开始到程序运行,中间经过了哪些操作,
经过测试 双击程序
右键运行程序
双击快捷方式
右键运行快捷方式
几种操作的函数调用栈,结合通用性
和时间早两点考虑,找到了 SHELL32!CDefFolderMenu::InvokeCommand
这个函数
这个函数是 CDefFolderMenu
类的虚函数,所以我们可以找到这个类在shell32中的地址,并HOOK
虚函数表对应的位置。在shell32中导出了 CDefFolderMenu_Create2
函数,我们可以由此函数获取到
CDefFolderMenu
类的虚函数表位置,而虚函数表中的 InvokeCommand
是继承自 IContextMenu
接口,
使用IDA反汇编 CDefFolderMenu
类可以找到 InvokeCommand
位于虚函数表的第 [4]
位置
1 2 3 4 5 6
| #include <shlobj.h>
typedef HRESULT(__stdcall *INVOKECOMMAND)(IContextMenu *pcm, CMINVOKECOMMANDINFO *pici); ULONG_PTR* gVtbl = NULL; ULONG_PTR* gAddr = NULL; INVOKECOMMAND pOldInvokeCommand = NULL;
|
查找虚函数表并进行HOOK
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void HookVirtualTable() { if (pOldInvokeCommand) return; IContextMenu *pcm = NULL; HRESULT hr = CDefFolderMenu_Create2(NULL, NULL, 0, NULL, NULL, NULL, 0, NULL, &pcm); if (FAILED(hr)) return; gVtbl = *(ULONG_PTR**)pcm; gAddr = (ULONG_PTR*)&(gVtbl[4]); pOldInvokeCommand = (INVOKECOMMAND)*gAddr; DWORD oldProtect = 0; VirtualProtect(gAddr, sizeof(ULONG_PTR), PAGE_READWRITE, &oldProtect); InterlockedExchange64((LONG64*)gAddr, (LONG64)MyInvokeCommand); VirtualProtect(gAddr, sizeof(ULONG_PTR), oldProtect, &oldProtect); }
|
1 2 3 4 5 6 7 8 9
| void UnHookVritualTable() { if (!pOldInvokeCommand) return; DWORD oldProtect = 0; VirtualProtect(gAddr, sizeof(ULONG_PTR), PAGE_READWRITE, &oldProtect); InterlockedExchange64((LONG64*)gAddr, (LONG64)pOldInvokeCommand); VirtualProtect(gAddr, sizeof(ULONG_PTR), oldProtect, &oldProtect); pOldInvokeCommand = NULL; }
|
我们自定义的 InvokeCommand
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| HRESULT MyInvokeCommand(IContextMenu *pcm, CMINVOKECOMMANDINFO *pici) { HRESULT hr = S_OK; char cmd[32] = { 0 }; do { if (!pcm || !pici) break; if (!IS_INTRESOURCE(pici->lpVerb)) break; hr = pcm->GetCommandString((UINT_PTR)pici->lpVerb, GCS_VERBA, NULL, cmd, 32); if (FAILED(hr)) break; if (_stricmp(cmd, "open") && _stricmp(cmd, "runas")) break; if (CheckDragFileList(pcm)) return S_OK; } while (0); if (pOldInvokeCommand) hr = pOldInvokeCommand(pcm, pici); return hr; }
|
检查操作的目标文件名称的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| BOOL CheckDragFileList(IContextMenu *pcm) { IObjectWithSelection *pSelection = NULL; HRESULT hr = pcm->QueryInterface(IID_IObjectWithSelection, (VOID**)&pSelection); if (SUCCEEDED(hr)) { IDataObject *pObject = NULL; hr = pSelection->GetSelection(IID_IDataObject, (VOID**)&pObject); if (SUCCEEDED(hr)) { STGMEDIUM stg = { TYMED_HGLOBAL }; FORMATETC etc = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; hr = pObject->GetData(&etc, &stg); if (SUCCEEDED(hr)) { HDROP hDrop = (HDROP)GlobalLock(stg.hGlobal); UINT n = DragQueryFileW(hDrop, 0xFFFFFFFF, NULL, 0); for (UINT i = 0; i < n; i++) { WCHAR szPath[MAX_PATH] = { 0 }; DragQueryFileW(hDrop, i, szPath, MAX_PATH); if (wcsstr(szPath, L"\\mytest.exe")) { system("calc.exe"); return TRUE; } } GlobalUnlock(stg.hGlobal); ReleaseStgMedium(&stg); } pObject->Release(); } pSelection->Release(); } return FALSE; }
|
注意:通常情况下用户不会同时选中多个程序运行,更严谨的做法是已处理过的文件名从列表中去除