本文主要介绍如何通过DLL注入的方式来实现在指定的窗口控件上挂载自定义窗口。
何谓挂载?
和舰载机挂载导弹类似,将我们自己的窗口挂到原有程序的窗口之上,可以实现对原有窗口功能的覆盖和扩展。
结合本实例的代码,挂载的实现原理大致如下:
DllMain
的DLL_PROCESS_ATTACH
条件分支中创建一个新线程NewThread,后面的处理逻辑将放到NewThread中,防止DllMain
阻塞。EnumWindows
结合FindWindowEx
实现,详见FindProcessWindow
函数)。SetWindowLong
修改窗体的默认的消息处理过程(假设将窗口处理过程修改为我们DLL中的WndProc_Trampoline
函数),然后向窗体发送一个自定义消息,这时WndProc_Trampoline
函数就可以获取到该消息通知,我们在收到该消息通知后就可以开始我们的挂载逻辑了。之所以要通过发送一个自定义消息的方式来做,而不是直接在新线程NewThread中开始我们的挂载逻辑,是因为这样做可以保证我们的挂载逻辑(如创建窗口)是在主线程中进行的。WS_CHILD
子窗口属性,并设置父窗体的WS_CLIPCHILDREN
属性来裁剪子窗口,防止我们挂载的子窗口闪烁。WndProc_Trampoline
函数的最后不要忘记调用之前老的消息处理过程,否则原窗口的消息将无法得到正确的响应。DllInjecter.exe
实现将DLL注入到指定的进程之中,是一个通用的DLL注入器。包含了对前面文章介绍的“使用远程线程的方式注入”和“使用钩子方式注入”这两种注入方式的实现。
Test.exe
是一个模拟的被挂载程序,程序非常简单,只包含一个窗体和大按钮,不包含任何逻辑。本实例主要是将我们的窗口挂载到这个大按钮之上。
Troy.dll
被注入到Test.exe
中的DLL文件(名称来源于“特洛伊木马”)。挂载的主要逻辑大都在该工程之中。
我们可以在WndProc_Trampoline
函数的WUM_CREATE_USER_WINDOW
消息处理分支中分别调用CreateUserWindow
和CreateUserWindowByDuilib
函数来创建挂载窗口:
- CreateUserWindow
使用原始的Windows API(CreateWindow
)创建挂载窗口。
- CreateUserWindowByDuilib
使用duilib
界面库来创建挂载窗口。
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);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message) {
case WM_PAINT:
RECT rect;
GetClientRect(hwnd, &rect);
DrawText(GetDC(hwnd), TEXT("test"), 4, &rect, DT_CENTER);
break;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
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;
}
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://gitee.com/china_jeffery/inject_sample
选择x86平台编译,x64平台未配置