0%

劫持用户双击目标到启动的流程(1)

简介

有时候我们需要拦截用户双击启动的行为,比如用户运行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>
// CDefFolderMenu::InvokeCommand
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 };
// 获取文件列表指针,它们存储在HDROP格式中
hr = pObject->GetData(&etc, &stg);
if (SUCCEEDED(hr))
{
// 取得HDROP句柄
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;
}

注意:通常情况下用户不会同时选中多个程序运行,更严谨的做法是已处理过的文件名从列表中去除