返回> 网站首页
由 BCG 谈到 API Hook
yoours2011-03-10 12:44:46
简介一边听听音乐,一边写写文章。
BCG 就无需介绍了,业界相当有名的 Visual C++ 换肤专家,能完美模拟 Outlook、Office、Visual Studio 的界面,连 Microsoft 也是他们的客户,其实力可见一斑。
来张界面,这是他自己的 Outlook Demo 编译之后运行的效果:
上面那个 SysCommand 我用红色圈出来了,这么漂亮的一个 UI,居然还是那么老土的菜单(这个菜单在 MFC 的基本控件,比如 CEdit 等,点右键时也会出来),本来 VS2008 sp1 已经内置 BCG 的 CMFCPopupMenu 了,可是在这些地方没有换过来,算是点小瑕疵吧。于是我想给他换过来,和整体 UI 界面风格保持一致。
要达到这样的效果,基本思路有两个:
1、安装消息钩子,处理 WM_INITMENUPOPUP 和 WM_UNINITMENUPOPUP,自绘。一般采用 CMenu 的派生类;
2、Hook 系统 API 函数,处理 TrackPopupMenu 和 TrackPopupMenuEx,用自定义的其他菜单类完成显示。
像常见的一些第三方 skin 包,比如 Skin++,采用的就是第一种方式。
这里我想换成 CMFCPopupMenu,而 CMFCPopupMenu 并非派生于 CMenu,而是派生于 CWnd,所以第一条路行不通,走第二条路。
提到 API Hook,也是个老生常谈的话题了。要实现它,也有两种思路:
一种是找到该API在内存的入口地址,修改其入口前八字节数据为了一汇编的 jmp xxxxxxxx 无条件跳转指令,使其调用该API时会跳到指定的另一处函数入口处,即与这个API有着相同的参数与返回值的回调处理程序。
第二种是在进程的输入表(IAT 表)找到保存在这里的要调用的那个API地址,修改这个地址,使程序在输入表里找该API地址时取的是我们修改后的回调程序入口地址。
关于第一种方法,有例子:http://www.vckbase.com/document/viewdoc/?id=1378
两种方法的利弊也是显而易见的:采用第一种方式,功能上说可以确保万无一失,但是需要频繁的 hook 和 unhook,于效率和稳定性上有所欠缺;第二种在由于只修改一次 IAT 表,所以在效率很稳定性上很有优势,但是缺点就是:某些程序要调用某个 API 可以不通过输入表,造成 hook 不到。
好在 Microsoft 的核心 API 函数库 kernel32、user32、ntdll 好像并没有采用(或者是我理解有问题,至少我们这里 TrackPopupMenu 肯定不会漏),所以我还是采用第二种方式,通过修改进程每个模块的输入表来实现(用 Psapi 枚举进程模块就没必要细说了,都会)。
于是创建新类(比如 CXMenuHooker),用我以前写好的 Hook API 包,添加了两个公开的静态入口函数:
public:
static void HookPopupMenu();
static void RestorePopupMenu();
以便于安装和卸载 API 钩子。然后再给 Menu 加上自定义的弹出菜单函数:
protected:
static BOOL WINAPI TrackPopupMenu(HMENU hMenu, UINT uFlags, int x, int y, int nReserved, HWND hWnd, CONST RECT* prcRect);
static BOOL WINAPI TrackPopupMenuEx(HMENU hmenu, UINT fuFlags, int x, int y, HWND hwnd, LPTPMPARAMS lptpm);
注意参数需要和 SDK 的一致就是了。
关键函数:
- BOOL CXMenuHooker::TrackPopupMenuEx(HMENU hmenu, UINT fuFlags, int x, int y, HWND hwnd, LPTPMPARAMS lptpm)
- {
- BOOL bResult = FALSE;
- TRACE0("============== in TrackPopupMenuEx ==== CXMenuHooker\n");
- #if _MSC_VER < 1500
- //系统默认的处理过程
- typedef BOOL (WINAPI *pfnTrackPopupMenuEx)(HMENU, UINT, int, int, HWND, LPTPMPARAMS);
- bResult = ((pfnTrackPopupMenuEx)(PROC)m_pahTrackPopupMenuEx)(hmenu, fuFlags, x, y, hwnd, lptpm);
- #else
- //自定义的处理过程(代码有待修正)
- CWnd* pWnd = CWnd::FromHandle(hwnd);
- CMFCPopupMenu* pPopupMenu = new CMFCPopupMenu();
- pPopupMenu->Create(pWnd, x, y, hmenu, FALSE, TRUE);
- pPopupMenu->PostMessage(WM_NULL);
- #endif
- return bResult;
- }
这些代码工作的很好,成功 Hook 了 TrackPopupMenu 和 TrackPopupMenuEx 两个 API 函数,弹出了我想要的 CMFCPopupMenu,看图:
可是,等等,似乎有了新问题,这里的 CMFCPopupMenu 不能自动关闭,当我在其他地方点鼠标的时候应该要关闭这个菜单的,仔细翻了一遍 CMFCPopupMenu 的源码,也没发现我这里调用有啥不对的,极其郁闷中...
回头看看最上面的第一种方案,处理 WM_INITMENUPOPUP 的,可行倒是可行,但是自绘的话,效果没办法和 CMFC... 一套东西很好的保持一致,除非派生 CMenu 实现 2000/XP/Office2003/VisualStudio2005 的各种效果,这个代价就太大了,时间不够用啊...