本文主要介绍如何通过 DLL 注入的方式来实现在指定窗口的控件上挂载一个自定义窗口。

何谓挂载?和舰载机挂载导弹类似,将我们自己的窗口挂到原有程序的窗口之上,可以实现对原有窗口功能的覆盖和扩展。

一、实现过程

挂载的实现过程如下:

  1. 通过远程线程的方式(其他方式也可以)将 DLL 注入到指定的进程(当然是被挂载窗口所属的进程)。
  2. DllMainDLL_PROCESS_ATTACH条件分支中创建一个新线程 NewThread,后面的处理逻辑将放到 NewThread 中,防止DllMain阻塞。
  3. 在 NewThread 线程中查找被挂载的窗口(注意是窗口不是窗口上的控件)的句柄(查找方式:EnumWindows结合FindWindowEx实现,详见FindProcessWindow函数)。
  4. 使用SetWindowLong修改窗体的默认的消息处理过程(假设将窗口处理过程修改为我们 DLL 中的WndProc_Trampoline函数),然后向窗体发送一个自定义消息,这时WndProc_Trampoline函数就可以获取到该消息通知,我们在收到该消息通知后就可以开始我们的挂载逻辑了。之所以要通过发送一个自定义消息的方式来做,而不是直接在新线程 NewThread 中开始我们的挂载逻辑,是因为这样做可以保证我们的挂载逻辑(如创建窗口)是在主线程中进行的。
  5. 挂载逻辑主要包含:查找需要挂载控件的句柄、创建挂载窗口。创建挂载窗口的时候要设置WS_CHILD子窗口属性,并设置父窗体的WS_CLIPCHILDREN属性来裁剪子窗口,防止我们挂载的子窗口闪烁。
  6. 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
2
3
4
5
6
7
8
9
LRESULT CALLBACK WndProc_Trampoline(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message) {
case WUM_CREATE_USER_WINDOW:
CreateUserWindowByDuilib();
break;
}

return CallWindowProc(g_oldProc, hwnd, message, wParam, lParam);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// 线程处理函数
unsigned int __stdcall PluginProc(LPVOID pArg) {
MessageBox(NULL, TEXT("我已经被注入啦"), TEXT("信息"), MB_OK | MB_ICONASTERISK);

HWND hMainWindow = InjectHelper::FindProcessWindow(GetCurrentProcessId(), TEXT("#32770"), TEXT("Test"), TRUE);

g_oldProc = (WNDPROC)SetWindowLong(hMainWindow, DWL_DLGPROC, (LONG)WndProc_Trampoline);


::PostMessage(hMainWindow, WUM_CREATE_USER_WINDOW, 0, 0);

return 0;
}
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
BOOL APIENTRY DllMain(HMODULE hModule, DWORD  fdwReason, LPVOID lpReserved) {
HANDLE hThread = NULL;

switch(fdwReason) {
case DLL_PROCESS_ATTACH:
{
g_hDllModule = hModule;
// 使用注册表方式和CreateRemoteThread方式注入时,一般在此处创建线程
//

hThread = (HANDLE)_beginthreadex(NULL, 0, PluginProc, NULL, 0, NULL);
if (hThread) {
CloseHandle(hThread); // 关闭句柄,防止句柄泄漏
}
break;
}
case DLL_THREAD_ATTACH:
{
break;
}
case DLL_THREAD_DETACH:
{
break;
}
case DLL_PROCESS_DETACH:
{
break;
}
}
return TRUE;
}

完整的示例代码见:

https://github.com/winsoft666/InjectSample