browser icon
You are using an insecure version of your web browser. Please update your browser!
Using an outdated browser makes your computer unsafe. For a safer, faster, more enjoyable user experience, please update your browser today or try a newer browser.

替换 Win + E 热键的默认处理

Posted by on 2009 年 10 月 13 日

你可以任意转载本文,但请在转载后的文章中注明作者和原始链接。
媒体约稿请联系 titilima_AT_163.com(把“_AT_”换成“@”)。

现在有很多比 Windows 资源管理器功能强大的资源管理程序,如 Total Commander、xplorer2 和 Ultra Explorer 等等。于是乎,我们就会有了一个新的需求——在使用系统热键 Win + E 的时候能呼出我们自定义的资源管理程序,而不是默认的 Windows 资源管理器。但是,我一直没有找到一个合适的辅助工具来实现这个功能,网上大多的热键工具不是无法替换默认的 Win + E,就是不够完美——例如键入后仍会弹出资源管理器,或弹出开始菜单,甚至有时候会导致 explorer.exe 崩溃退出。
于是我决定自己实现这一功能,最初我选择了键盘钩子,但我发现钩子虽然能捕获到这个热键,但是并不能对这个热键的处理进行重新定向,除我的程序之外,系统仍然会收到这个消息。当然,这样就可以解释为什么大多能够拦截 Win + E 的热键工具不够完美的原因了。
于是我决定使用更猥琐的方法,从 explorer.exe 的内部开始探究。

我的切入点是弹出的资源管理器窗口,既然这个窗口是新建的,那么必然会经过 CreateWindow,于是我选择了 NtUserCreateWindowEx 作为我的断点:

1
bp USER32!NtUserCreateWindowEx

接下来按下 Win + E,断点如约命中。检查其堆栈,发现:

1
2
3
4
5
6
7
8
9
10
11
12
ChildEBP RetAddr
01c1f3cc 77d2e442 USER32!NtUserCreateWindowEx
01c1f478 77d2d0d6 USER32!_CreateWindowEx+0x1ed
01c1f4b4 77f596af USER32!CreateWindowExW+0x33
01c1f51c 7d6984e2 SHLWAPI!SHCreateWorkerWindowW+0xac
01c1f53c 7d698ea0 SHELL32!CShellExecute::_CreateHiddenDDEWindow+0x1e
01c1f560 7d61d9fc SHELL32!CShellExecute::_DDEExecute+0x79
01c1f57c 7d5d9fea SHELL32!CShellExecute::_TryExecDDE+0x2d
01c1f588 7d5d9b04 SHELL32!CShellExecute::_TryInvokeApplication+0x29
01c1f59c 7d5d9a36 SHELL32!CShellExecute::ExecuteNormal+0xb1
01c1f5b0 7d5d99d2 SHELL32!ShellExecuteNormal+0x30
01c1f5cc 7d5e619b SHELL32!ShellExecuteExW+0x8d

这就是说,资源管理器窗口是通过 ShellExecuteEx 启动的。现在清除原有断点,重新在 ShellExecuteExW 处下断点,并再次按下 Win + E。断点命中后,堆栈信息如下:

1
2
3
4
5
6
ChildEBP RetAddr
01c1fccc 0103ae0f SHELL32!ShellExecuteExW
01c1fd14 01021599 Explorer!_ShowFolder+0x62
01c1fd30 01021691 Explorer!CTray::_HandleGlobalHotkey+0xb4
01c1fde8 01001b5c Explorer!CTray::v_WndProc+0xb6d
01c1fe0c 77d18734 Explorer!CImpWndProc::s_WndProc+0x65

这是一个典型的窗口过程流程,于是检查 CImpWndProc::s_WndProc 的各个参数,如下图所示。

于是,我的想法初步形成:子类化 Shell_TrayWnd 这个窗口,处理其 WM_HOTKEY 消息即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define ID_EXPLORER     504
 
LRESULT WINAPI TrayWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    if (WM_HOTKEY == uMsg)
    {
        if (ID_EXPLORER == wParam)
        {
            MessageBox(NULL, _T("Hello, World!"), _T("Hello"), MB_TOPMOST);
            return 0;
        }
    }
    // …
    return CallWindowProc(g_pfnTrayWnd, hWnd, uMsg, wParam, lParam);
}

但是,实际操作起来却没有成功,原因是我使用 SetWindowLong 替换 Shell_TrayWnd 的窗口过程后,原有的窗口过程又会被瞬间替换回去。也许这是 explorer.exe 做的特殊保护处理?继续调查下去是一件很不靠谱的事情,于是我选择了曲线救国——不改动窗口过程,而是直接将窗口过程做内联挂钩(inline hook)处理。最终的挂钩代码如下,使用了 Detours。

1
2
3
4
5
6
7
8
HWND hWnd = FindWindow(_T("Shell_TrayWnd"), NULL);
if (NULL != hWnd)
{
    g_pfnTrayWnd = (WNDPROC)GetWindowLong(hWnd, GWL_WNDPROC);
    DetourTransactionBegin();
    DetourAttach((PVOID*)&g_pfnTrayWnd, TrayWndProc);
    DetourTransactionCommit();
}

由于挂钩后的 TrayWndProc 必须位于 explorer.exe 的地址空间之中,因此我将完整的示例代码写在了一个 DLL 之中。测试的时候,只需要设法(比如远程注入)让 explorer.exe 加载该 DLL 就可以看到效果了。
当然,这还没有结束。接下来的问题是——如何让 explorer.exe 自动加载这个 DLL?方法很多,这里就不详述了,诸君可任选其一:

  1. 利用注册表的 AppInit_DLLs 键;
  2. 将 DLL 写成 BHO;
  3. 将 DLL 写成外壳扩展(Shell Extension)。

示例代码见附件。

附件:winkey.zip

订阅本站

10 Comments

  • At 2009.10.13 13:43, callee said:

    好,顶个

    • At 2009.11.11 09:40, fangkm said:

      李马大侠,万一开始时是在不知道该在哪个函数处设断点,该怎么调试呢?比如打开IE窗口 在NtUserCreateWindowEx处设断点就没啥用啊

      • At 2009.11.11 09:49, 李马 said:

        具体情况具体分析。另外,我不太清楚你要挂什么钩子,又和 NtUserCreateWindowEx 有什么关系。

        • At 2009.11.11 11:28, fangkm said:

          我想截获每次打开IE窗口的事件,该挂接哪个函数啊

          • At 2009.11.11 12:27, 李马 said:

            试试 BHO。

            • At 2009.11.12 12:58, 天津婚纱摄影 said:

              貌似都被人给遗忘了哟….

              • At 2010.01.10 23:11, haokeyy said:

                还是用 autohotkey 简单

                #e::
                run, D:\totalcmd\TOTALCMD.EXE
                return

                run, 后边设置想要启动的资源管理器程序路径即可。

                • At 2010.01.11 09:14, 李马 said:

                  我这里讨论的并不是“如何替换 Win + E 键”,而是“如何更好的替换 Win + E 键”。实际动手试一下就会发现,键盘钩子并不是一个很好的方案。

                  • At 2010.06.04 22:13, cdut-boy said:

                    创建远程线程注射到explorer.exe,子类化Shell_TrayWnd,
                    拦截WM_HOTKEY即可成功。不需要你说的这么复杂。
                    在我写的clock++力已经实现
                    http://hi.baidu.com/cdutboy/blog/item/3440813973f53528b9998f78.html

                    case WM_HOTKEY:
                    {
                    //拦截win+E
                    if( MOD_WIN==LOWORD(lParam) && (‘E’==HIWORD(lParam)) )
                    {
                    //API_MessageBox(hWnd,L”拦截win+E”,L”提示”,MB_OK);
                    return TRUE; //拦截了
                    }
                    break;
                    }

                    • At 2010.06.04 22:57, 李马 said:

                      也许你并没仔细看我这篇文章?另,我并不认为远线程是一个好的解决方案。

                      (Required)
                      (Required, will not be published)