返回> 网站首页 

由 BCG 谈到 API Hook

yoours2011-03-10 12:44:46 阅读 1325

简介一边听听音乐,一边写写文章。

BCG 就无需介绍了,业界相当有名的 Visual C++ 换肤专家,能完美模拟 Outlook、Office、Visual Studio 的界面,连 Microsoft 也是他们的客户,其实力可见一斑。

来张界面,这是他自己的 Outlook Demo 编译之后运行的效果:

截图1

上面那个 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 的一致就是了。

关键函数:

  1. BOOL CXMenuHooker::TrackPopupMenuEx(HMENU hmenu, UINT fuFlags, int x, int y, HWND hwnd, LPTPMPARAMS lptpm)  
  2. {  
  3.     BOOL bResult = FALSE;  
  4.   
  5.     TRACE0("============== in TrackPopupMenuEx ==== CXMenuHooker\n");  
  6.     #if _MSC_VER < 1500  
  7.      //系统默认的处理过程  
  8.      typedef BOOL (WINAPI *pfnTrackPopupMenuEx)(HMENUUINTintintHWND, LPTPMPARAMS);  
  9.      bResult = ((pfnTrackPopupMenuEx)(PROC)m_pahTrackPopupMenuEx)(hmenu, fuFlags, x, y, hwnd, lptpm);  
  10.     #else  
  11.         //自定义的处理过程(代码有待修正)  
  12.         CWnd* pWnd = CWnd::FromHandle(hwnd);  
  13.         CMFCPopupMenu* pPopupMenu = new CMFCPopupMenu();  
  14.         pPopupMenu->Create(pWnd, x, y, hmenu, FALSE, TRUE);  
  15.         pPopupMenu->PostMessage(WM_NULL);  
  16.     #endif  
  17.   
  18.     return bResult;  
  19. }  

这些代码工作的很好,成功 Hook 了 TrackPopupMenu 和 TrackPopupMenuEx 两个 API 函数,弹出了我想要的 CMFCPopupMenu,看图:

截图2

可是,等等,似乎有了新问题,这里的 CMFCPopupMenu 不能自动关闭,当我在其他地方点鼠标的时候应该要关闭这个菜单的,仔细翻了一遍 CMFCPopupMenu 的源码,也没发现我这里调用有啥不对的,极其郁闷中...

回头看看最上面的第一种方案,处理 WM_INITMENUPOPUP 的,可行倒是可行,但是自绘的话,效果没办法和 CMFC... 一套东西很好的保持一致,除非派生 CMenu 实现 2000/XP/Office2003/VisualStudio2005 的各种效果,这个代价就太大了,时间不够用啊...

微信小程序扫码登陆

文章评论

1325人参与,0条评论