本文主要介绍如何通过 DLL 注入的方式来实现在指定窗口的控件上挂载一个自定义窗口。
何谓挂载?和舰载机挂载导弹类似,将我们自己的窗口挂到原有程序的窗口之上,可以实现对原有窗口功能的覆盖和扩展。
一、实现过程
挂载的实现过程如下:
- 通过远程线程的方式(其他方式也可以)将 DLL 注入到指定的进程(当然是被挂载窗口所属的进程)。
- 在
DllMain
的DLL_PROCESS_ATTACH
条件分支中创建一个新线程 NewThread,后面的处理逻辑将放到 NewThread 中,防止DllMain
阻塞。 - 在 NewThread 线程中查找被挂载的窗口(注意是窗口不是窗口上的控件)的句柄(查找方式:EnumWindows结合FindWindowEx实现,详见FindProcessWindow函数)。
- 使用
SetWindowLong
修改窗体的默认的消息处理过程(假设将窗口处理过程修改为我们 DLL 中的WndProc_Trampoline
函数),然后向窗体发送一个自定义消息,这时WndProc_Trampoline
函数就可以获取到该消息通知,我们在收到该消息通知后就可以开始我们的挂载逻辑了。之所以要通过发送一个自定义消息的方式来做,而不是直接在新线程 NewThread 中开始我们的挂载逻辑,是因为这样做可以保证我们的挂载逻辑(如创建窗口)是在主线程中进行的。 - 挂载逻辑主要包含:查找需要挂载控件的句柄、创建挂载窗口。创建挂载窗口的时候要设置
WS_CHILD
子窗口属性,并设置父窗体的WS_CLIPCHILDREN
属性来裁剪子窗口,防止我们挂载的子窗口闪烁。 - 在
WndProc_Trampoline
函数的最后不要忘记调用之前老的消息处理过程,否则原窗口的消息将无法得到正确的响应。
二、实例代码
2.1 DllInjecter 工程
DllInjecter.exe 实现将 DLL 注入到指定的进程之中,该 exe 是一个通用的 DLL 注入器。包含了对前面文章介绍的“使用远程线程的方式注入”和“使用钩子方式注入”这两种注入方式的实现。
2.2 Test 工程
Test.exe是一个模拟的被挂载程序,程序非常简单,只包含一个窗体和大按钮,不包含任何逻辑。本实例主要是将我们的窗口挂载到这个大按钮之上。
挂载前如图:
挂载后效果如图:
2.3 Troy 工程
Troy.dll 就是被注入到 Test.exe 中的 DLL 文件,挂载的主要逻辑大都在该工程之中。
在WndProc_Trampoline
函数的WUM_CREATE_USER_WINDOW
消息处理分支中调用CreateUserWindowByDuilib
函数来创建挂载窗口,CreateUserWindowByDuilib 使用duilib
界面库来创建挂载窗口。
1 | LRESULT CALLBACK WndProc_Trampoline(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { |
1 | // 线程处理函数 |
1 | BOOL APIENTRY DllMain(HMODULE hModule, DWORD fdwReason, LPVOID lpReserved) { |
完整的示例代码见: