目录
前言
一、研究 SetWindowsHookEx 的机制
二、概念验证
三、运行效果分析
四、总结与展望
参考文献
原文出处链接:[https://blog.csdn.net/qq_59075481/article/details/139206017]
前言
SetWindowsHookEx 函数帮助其他人员注入模块到我们的进程,或者对我们的消息进行拦截处理。如何屏蔽 SetWindowsHookEx 函数是一件让人头疼的事情,长期以来,我们关注于拦截 CreateRemoteThread 和 CreateThreadEx 等函数的模块注入。通过一个名为 ClientLoadLibrary 的指针覆盖,我们将接管默认的 Win32k 钩子链处理机构,并实现我们自定义的处理模式。这本身属于一种 API HOOK。
拦截 SetWindowsHookEx 函数的官方方法是在系统或程序运行的早期,安装一个 WH_DEBUG 类型的 Win32 钩子,这将允许你捕获后面几乎所有 Win32 钩子的挂钩过程(除了WH_KEYBOARD_LL 低级键盘钩子无法拦截以外),具体可以参考这里的文章以及微软的文档。
一、研究 SetWindowsHookEx 的机制
首先是一种触发操作时的调用堆栈,可以看出间接调用了 __ClientLoadLibrary 指针指向的函数,然后依次调用钩子回调队列中的函数。
kernel32!LoadLibraryExW
USER32!__ClientLoadLibrary
ntdll!KiUserCallbackDispatcher
nt!KiCallUserMode
nt!KeUserModeCallback
win32k!ClientLoadLibrary
win32k!xxxLoadHmodIndex
win32k!xxxCallHook2
win32k!xxxCallHook
... ...
方法就是修改 user32!apfnDispatch 数组,直接替换 __ClientLoadLibrary 指向的函数指针。
这个数组其实是 PEB 的 KernelCallbackTable,而他们都没有文档化,所以偏移需要区分操作系统的版本。
获取 __ClientLoadLibrary 在数组中的 index 将稍微繁琐点,我们需要把我们关心的系统用 windbg带上符号都看一眼才能知道是多少了。我这里提供 Win11 系统中 __ClientLoadLibrary 在数组中的 index:0x258。
PEB 中 KernelCallbackTable 的偏移可以通过 WinDbg 或者 Vergilius Project | Kernels 获取。但是 __ClientLoadLibrary 在数组中的 index 则只能通过 WinDbg 或者调试符号获取。
KernelCallbackTable 结构体
KernelCallbackTable 可以在 PEB 中找到, 在 0x058
偏移处:
lkd> dt_PEB
lkd> dt_PEB @$peb kernelcallbacktable
KernelCallbackTable 是函数指针表:
lkd> dqs 0x00007ffa`29123070 L60
00007ffa`29123070 00007ffa`290c2bd0 user32!_fnCOPYDATA
00007ffa`29123078 00007ffa`2911ae70 user32!_fnCOPYGLOBALDATA
00007ffa`29123080 00007ffa`290c0420 user32!_fnDWORD
00007ffa`29123088 00007ffa`290c5680 user32!_fnNCDESTROY
00007ffa`29123090 00007ffa`290c96a0 user32!_fnDWORDOPTINLPMSG
00007ffa`29123098 00007ffa`2911b4a0 user32!_fnINOUTDRAG
//....
通过重写 ClientLoadLibrary 函数并覆盖 __ClientLoadLibrary 指针来达到挂钩调用我们的例程。
此函数仅接受 1 个参数,即指向进程堆栈中分配的未记录结构的指针。它保存 DLL 的路径、指向通知函数的指针以及一些尚不清楚的数据。
typedef struct _CLientLoadLibraryParam
{
DWORD dwSize;//+0
DWORD dwStringLength; //+4
DWORD ReservedZero1;//+8
DWORD ReservedZero2;//+C
DWORD ReservedZero3;//+10
DWORD ReservedZero4;//+14
DWORD ReservedZero5;//+18 () +1A () //不需要!
DWORD ReservedZero6;//+1C
DWORD ReservedZero8;//+20
DWORD test[3]; // 占位
ULONG_PTR ptrDllString;//+30
DWORD ReservedZero11[5];
ULONG_PTR ptrApiString;//+40
WCHAR szDllName[MAX_PATH];
WCHAR szApiName[MAX_PATH];
}CLientLoadLibraryParam, * PCLientLoadLibraryParam;
关于这部分的早期的研究是这样的:
反 SetWindowsHookEx DLL 注入成为可能 😀 。
在花了一些时间逆向 user32 内部结构后,我发现了这个未记录的函数。此函数负责将 SetWindowsHookEx() 注册的 DLL 加载到您的进程中。本博客将仅关注实际 DLL 加载发生的用户模式。
user32.__ClientLoadLibrary(lpHook)
此函数仅接受 1 个参数,即指向进程堆栈中分配的未记录结构的指针。它保存 DLL 的路径、指向通知函数的指针以及一些尚不清楚的数据。
_USERHOOK struct unknown_00 DWORD ? ; 0x00 unknown_04 DWORD ? ; 0x04 nCount DWORD ? ; 0x08 numbers of pointer to fix up unknown_0C DWORD ? ; 0x0C offCbkPtrs DWORD ? ; 0x10 offset to callback pointers bFixed DWORD ? ; 0x14 indicates if the pointer is fixed lpDLLPath UNICODE_STRING {} ; 0x18 DLL path lpfnNotify DWORD ? ; 0x20 offset to notification procedure (called when DLL is injected) _USERHOOK ends
在函数的开头,它检查 _USERHOOK.nCount 和 _USERHOOK.bFixed 值。然后它调用 FixupCallbackPointers。
user32.FixupCallbackPointers(lpHook)
它只接受 1 个参数,与传递给 __ClientLoadLibrary 的参数相同。此函数以一种非常有趣的方式“修复”指针。首先,它定位回调指针的地址。
lpCbkPtrs = lpHook + offCbkPtrs
然后它循环遍历指针列表并通过解析实际地址的偏移量来修复它。
新地址 = lpHook + 偏移量
修复所有指针后,我们返回到__ClientLoadLibrary,然后调用InitUserApiHook。
user32.InitUserApiHook(hModule,lpfnNotify)
它调用 ResetUserApiHook,用参数 1 给出的函数地址填充数组。之后,它调用 (hModule + lpfnNotify) 指向的通知函数 __Unknown_DllInjected,并将地址数组作为参数 2 传递。
__Unknown_DllInjected(lpvReserved,lpFuncList)
user32.ResetUserApiHook(lpFuncList)
填充指针lpFuncList给出的数组。
__UserFuncList struct lSize DWORD ? ; size of the list ... ; array of function address __UserFuncList ends
大部分内容仍未说明。欢迎您在此处发布我未说明的任何内容。下面附上了一个示例,它挂钩 __ClientLoadLibrary,打印出试图加载到其中的 DLL,并拒绝加载它。反 SetWindowsHookEx DLL 注入,是吗?😀 需要使用 DebugView 才能查看转储。
二、概念验证
下面的 POC 代码创建一个普通窗口,只允许位于 "C:\Windows\System32" 目录下的模块加载,并构建一个模块加载白名单用于记录用户允许额外加载的模块。
#include <windows.h>
#include <string>
#include <Shlwapi.h>
#include <Softpub.h> // WinTrust.h 依赖于 Softpub.h
#include <wincrypt.h>
#include <iostream>
#include <unordered_map>
#include <mutex>
#pragma comment(lib, "Shlwapi.lib")
#pragma comment(lib, "Crypt32.lib")
#pragma comment(lib, "Wintrust.lib")
using namespace std;
typedef VOID(WINAPI* fct_clLoadLib)(VOID*);
fct_clLoadLib _ClientLoadLibrary;
ULONG_PTR _ClientLoadLibrary_addr;
unordered_map<wstring, bool> moduleWhitelist;
// 互斥锁
mutex mtx;
// 控制弹窗处理状态的全局变量
bool isMessageBoxHandling = false;
typedef struct _KERNELCALLBACKTABLE_T {
ULONG_PTR __fnCOPYDATA;
ULONG_PTR __fnCOPYGLOBALDATA;
ULONG_PTR __fnDWORD;
ULONG_PTR __fnNCDESTROY;
ULONG_PTR __fnDWORDOPTINLPMSG;
ULONG_PTR __fnINOUTDRAG;
ULONG_PTR __fnGETTEXTLENGTHS;
ULONG_PTR __fnINCNTOUTSTRING;
ULONG_PTR __fnPOUTLPINT;
ULONG_PTR __fnINLPCOMPAREITEMSTRUCT;
ULONG_PTR __fnINLPCREATESTRUCT;
ULONG_PTR __fnINLPDELETEITEMSTRUCT;
ULONG_PTR __fnINLPDRAWITEMSTRUCT;
ULONG_PTR __fnPOPTINLPUINT;
ULONG_PTR __fnPOPTINLPUINT2;
ULONG_PTR __fnINLPMDICREATESTRUCT;
ULONG_PTR __fnINOUTLPMEASUREITEMSTRUCT;
ULONG_PTR __fnINLPWINDOWPOS;
ULONG_PTR __fnINOUTLPPOINT5;
ULONG_PTR __fnINOUTLPSCROLLINFO;
ULONG_PTR __fnINOUTLPRECT;
ULONG_PTR __fnINOUTNCCALCSIZE;
ULONG_PTR __fnINOUTLPPOINT5_;
ULONG_PTR __fnINPAINTCLIPBRD;
ULONG_PTR __fnINSIZECLIPBRD;
ULONG_PTR __fnINDESTROYCLIPBRD;
ULONG_PTR __fnINSTRING;
ULONG_PTR __fnINSTRINGNULL;
ULONG_PTR __fnINDEVICECHANGE;
ULONG_PTR __fnPOWERBROADCAST;
ULONG_PTR __fnINLPUAHDRAWMENU;
ULONG_PTR __fnOPTOUTLPDWORDOPTOUTLPDWORD;
ULONG_PTR __fnOPTOUTLPDWORDOPTOUTLPDWORD_;
ULONG_PTR __fnOUTDWORDINDWORD;
ULONG_PTR __fnOUTLPRECT;
ULONG_PTR __fnOUTSTRING;
ULONG_PTR __fnPOPTINLPUINT3;
ULONG_PTR __fnPOUTLPINT2;
ULONG_PTR __fnSENTDDEMSG;
ULONG_PTR __fnINOUTSTYLECHANGE;
ULONG_PTR __fnHkINDWORD;
ULONG_PTR __fnHkINLPCBTACTIVATESTRUCT;
ULONG_PTR __fnHkINLPCBTCREATESTRUCT;
ULONG_PTR __fnHkINLPDEBUGHOOKSTRUCT;
ULONG_PTR __fnHkINLPMOUSEHOOKSTRUCTEX;
ULONG_PTR __fnHkINLPKBDLLHOOKSTRUCT;
ULONG_PTR __fnHkINLPMSLLHOOKSTRUCT;
ULONG_PTR __fnHkINLPMSG;
ULONG_PTR __fnHkINLPRECT;
ULONG_PTR __fnHkOPTINLPEVENTMSG;
ULONG_PTR __xxxClientCallDelegateThread;
ULONG_PTR __ClientCallDummyCallback;
ULONG_PTR __fnKEYBOARDCORRECTIONCALLOUT;
ULONG_PTR __fnOUTLPCOMBOBOXINFO;
ULONG_PTR __fnINLPCOMPAREITEMSTRUCT2;
ULONG_PTR __xxxClientCallDevCallbackCapture;
ULONG_PTR __xxxClientCallDitThread;
ULONG_PTR __xxxClientEnableMMCSS;
ULONG_PTR __xxxClientUpdateDpi;
ULONG_PTR __xxxClientExpandStringW;
ULONG_PTR __ClientCopyDDEIn1;
ULONG_PTR __ClientCopyDDEIn2;
ULONG_PTR __ClientCopyDDEOut1;
ULONG_PTR __ClientCopyDDEOut2;
ULONG_PTR __ClientCopyImage;
ULONG_PTR __ClientEventCallback;
ULONG_PTR __ClientFindMnemChar;
ULONG_PTR __ClientFreeDDEHandle;
ULONG_PTR __ClientFreeLibrary;
ULONG_PTR __ClientGetCharsetInfo;
ULONG_PTR __ClientGetDDEFlags;
ULONG_PTR __ClientGetDDEHookData;
ULONG_PTR __ClientGetListboxString;
ULONG_PTR __ClientGetMessageMPH;
ULONG_PTR __ClientLoadImage;
ULONG_PTR __ClientLoadLibrary;
ULONG_PTR __ClientLoadMenu;
ULONG_PTR __ClientLoadLocalT1Fonts;
ULONG_PTR __ClientPSMTextOut;
ULONG_PTR __ClientLpkDrawTextEx;
ULONG_PTR __ClientExtTextOutW;
ULONG_PTR __ClientGetTextExtentPointW;
ULONG_PTR __ClientCharToWchar;
ULONG_PTR __ClientAddFontResourceW;
ULONG_PTR __ClientThreadSetup;
ULONG_PTR __ClientDeliverUserApc;
ULONG_PTR __ClientNoMemoryPopup;
ULONG_PTR __ClientMonitorEnumProc;
ULONG_PTR __ClientCallWinEventProc;
ULONG_PTR __ClientWaitMessageExMPH;
ULONG_PTR __ClientWOWGetProcModule;
ULONG_PTR __ClientWOWTask16SchedNotify;
ULONG_PTR __ClientImmLoadLayout;
ULONG_PTR __ClientImmProcessKey;
ULONG_PTR __fnIMECONTROL;
ULONG_PTR __fnINWPARAMDBCSCHAR;
ULONG_PTR __fnGETTEXTLENGTHS2;
ULONG_PTR __fnINLPKDRAWSWITCHWND;
ULONG_PTR __ClientLoadStringW;
ULONG_PTR __ClientLoadOLE;
ULONG_PTR __ClientRegisterDragDrop;
ULONG_PTR __ClientRevokeDragDrop;
ULONG_PTR __fnINOUTMENUGETOBJECT;
ULONG_PTR __ClientPrinterThunk;
ULONG_PTR __fnOUTLPCOMBOBOXINFO2;
ULONG_PTR __fnOUTLPSCROLLBARINFO;
ULONG_PTR __fnINLPUAHDRAWMENU2;
ULONG_PTR __fnINLPUAHDRAWMENUITEM;
ULONG_PTR __fnINLPUAHDRAWMENU3;
ULONG_PTR __fnINOUTLPUAHMEASUREMENUITEM;
ULONG_PTR __fnINLPUAHDRAWMENU4;
ULONG_PTR __fnOUTLPTITLEBARINFOEX;
ULONG_PTR __fnTOUCH;
ULONG_PTR __fnGESTURE;
ULONG_PTR __fnPOPTINLPUINT4;
ULONG_PTR __fnPOPTINLPUINT5;
ULONG_PTR __xxxClientCallDefaultInputHandler;
ULONG_PTR __fnEMPTY;
ULONG_PTR __ClientRimDevCallback;
ULONG_PTR __xxxClientCallMinTouchHitTestingCallback;
ULONG_PTR __ClientCallLocalMouseHooks;
ULONG_PTR __xxxClientBroadcastThemeChange;
ULONG_PTR __xxxClientCallDevCallbackSimple;
ULONG_PTR __xxxClientAllocWindowClassExtraBytes;
ULONG_PTR __xxxClientFreeWindowClassExtraBytes;
ULONG_PTR __fnGETWINDOWDATA;
ULONG_PTR __fnINOUTSTYLECHANGE2;
ULONG_PTR __fnHkINLPMOUSEHOOKSTRUCTEX2;
} KERNELCALLBACKTABLE;
typedef struct _CLientLoadLibraryParam
{
DWORD dwSize;//+0
DWORD dwStringLength; //+4
DWORD ReservedZero1;//+8
DWORD ReservedZero2;//+C
DWORD ReservedZero3;//+10
DWORD ReservedZero4;//+14
DWORD ReservedZero5;//+18 () +1A () //不需要!
DWORD ReservedZero6;//+1C
DWORD ReservedZero8;//+20
DWORD test[3]; // 占位
ULONG_PTR ptrDllString;//+30
DWORD ReservedZero11[5];
ULONG_PTR ptrApiString;//+40
WCHAR szDllName[MAX_PATH];
WCHAR szApiName[MAX_PATH];
}CLientLoadLibraryParam, * PCLientLoadLibraryParam;
// 验证数字签名
BOOL VerifyEmbeddedSignature(LPCWSTR pwszSourceFile)
{
LONG lStatus;
DWORD dwLastError;
WINTRUST_FILE_INFO FileData;
memset(&FileData, 0, sizeof(FileData));
FileData.cbStruct = sizeof(WINTRUST_FILE_INFO);
FileData.pcwszFilePath = pwszSourceFile;
FileData.hFile = NULL;
FileData.pgKnownSubject = NULL;
GUID WVTPolicyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
WINTRUST_DATA WinTrustData;
memset(&WinTrustData, 0, sizeof(WinTrustData));
WinTrustData.cbStruct = sizeof(WinTrustData);
WinTrustData.dwUIChoice = WTD_UI_NONE;
WinTrustData.fdwRevocationChecks = WTD_REVOKE_NONE;
WinTrustData.dwUnionChoice = WTD_CHOICE_FILE;
WinTrustData.dwStateAction = WTD_STATEACTION_VERIFY;
WinTrustData.pFile = &FileData;
WinTrustData.dwProvFlags = WTD_SAFER_FLAG;
lStatus = WinVerifyTrust(NULL, &WVTPolicyGUID, &WinTrustData);
if (lStatus != ERROR_SUCCESS) {
dwLastError = GetLastError();
return FALSE;
}
return TRUE;
}
// 不区分大小写的字符串比较函数
bool CaseInsensitiveStringCompare(const wstring& str1, const wstring& str2) {
return _wcsicmp(str1.c_str(), str2.c_str()) == 0;
}
VOID Fake__ClientLoadLibrary(CLientLoadLibraryParam* lpHook)
{
if (lpHook == nullptr || lpHook->szDllName == nullptr) return;
wstring modulePath = lpHook->szDllName;
wstring system32Path = L"C:\\Windows\\System32\\";
printf("正在加载模块:%ws\n", modulePath.c_str());
// 检查模块路径是否在 C:/Windows/System32/ 下
if (CaseInsensitiveStringCompare(modulePath.substr(0, system32Path.size()), system32Path) == false)
{
// 如果弹窗处理状态为 true,则直接返回,不进行弹窗处理
if (isMessageBoxHandling)
return;
// 检查白名单
unordered_map<wstring, bool>::iterator it_find;
it_find = moduleWhitelist.find(modulePath);
if (it_find != moduleWhitelist.end())
{
// 调用原始例程加载模块
if (it_find->second)
_ClientLoadLibrary(lpHook);
}
else
{
// 加锁
mtx.lock();
// 设置弹窗处理状态为 true
isMessageBoxHandling = true;
// 解锁
mtx.unlock();
// 否则,弹出消息框询问用户是否加载模块
int result = MessageBox(NULL, L"是否加载此模块?", L"加载模块", MB_YESNO | MB_ICONQUESTION);
bool loadModule = (result == IDYES);
// 加锁
mtx.lock();
pair<wstring, bool> new_item(modulePath, loadModule);
moduleWhitelist.insert(new_item); // 记录用户选择
// 解锁
mtx.unlock();
if (loadModule)
{
// 如果用户选择加载模块,调用原始例程加载模块
_ClientLoadLibrary(lpHook);
}
else
{
printf("用户拒绝加载模块。\n");
}
// 加锁
mtx.lock();
// 恢复弹窗处理状态为 false
isMessageBoxHandling = false;
// 解锁
mtx.unlock();
}
}
else {
// 调用原始例程加载模块
_ClientLoadLibrary(lpHook);
}
}
ULONG_PTR Get__ClientLoadLibrary()
{
KERNELCALLBACKTABLE* kbl = NULL;
kbl = (KERNELCALLBACKTABLE*)*(ULONG_PTR*)(__readgsqword(0x60) + 0x58);
printf("Offest: 0x%I64X\n", (ULONG_PTR)&(kbl->__ClientLoadLibrary) - (ULONG_PTR)kbl);
return (ULONG_PTR)&(kbl->__ClientLoadLibrary);
}
BOOL Hook__ClientLoadLibrary()
{
DWORD dwOldProtect;
if (!VirtualProtect((LPVOID)_ClientLoadLibrary_addr, 0x1000, PAGE_READWRITE, &dwOldProtect))
return FALSE;
*(ULONG_PTR*)_ClientLoadLibrary_addr = (ULONG_PTR)Fake__ClientLoadLibrary;
if (!VirtualProtect((LPVOID)_ClientLoadLibrary_addr, 0x1000, dwOldProtect, &dwOldProtect))
return FALSE;
return TRUE;
}
BOOL UnHook__ClientLoadLibrary()
{
DWORD dwOldProtect;
if (!VirtualProtect((LPVOID)_ClientLoadLibrary_addr, 0x1000, PAGE_READWRITE, &dwOldProtect))
return FALSE;
*(ULONG_PTR*)_ClientLoadLibrary_addr = (ULONG_PTR)0;
if (!VirtualProtect((LPVOID)_ClientLoadLibrary_addr, 0x1000, dwOldProtect, &dwOldProtect))
return FALSE;
return TRUE;
}
DWORD CreateWindowThread(LPVOID p) {
// 创建窗口
HWND hwnd;
WNDCLASSEX wc = { 0 };
// 初始化窗口类
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = DefWindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = GetModuleHandle(NULL);
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = L"ConsoleWindowClass";
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
// 注册窗口类
RegisterClassEx(&wc);
// 创建窗口
hwnd = CreateWindowEx(WS_EX_CLIENTEDGE, L"ConsoleWindowClass", L"Console Window",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 480, 240,
NULL, NULL, GetModuleHandle(NULL), NULL);
// 显示窗口
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd);
// 消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
int main()
{
setlocale(0, "chs");
_ClientLoadLibrary_addr = Get__ClientLoadLibrary();
_ClientLoadLibrary = (fct_clLoadLib) * (ULONG_PTR*)_ClientLoadLibrary_addr;
Hook__ClientLoadLibrary();
// 创建一个线程来运行创建窗口的函数
CreateThread(0, 0, CreateWindowThread, 0, 0, 0);
std::cin.get();
UnHook__ClientLoadLibrary();
system("pause");
return 0;
}
下面的代码实现一个全局消息钩子,按照道理,模块将不能被受保护的进程加载。
DLL大致需要实现以下内容:
- 设置钩子
- 取消钩子
- 钩子过程函数
- 导出相关函数
DLL的主要实现代码如下:
#include "pch.h"
HHOOK g_hHook;
HMODULE g_hModule;
LRESULT CALLBACK GetMsgProc(
_In_ int code,
_In_ WPARAM wParam,
_In_ LPARAM lParam
)
{
return CallNextHookEx(g_hHook, code, wParam, lParam);
}
BOOL LoadHook(void)
{
g_hHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hModule, 0);
if (g_hHook){
MessageBox(NULL, TEXT("钩子加载成功!"), TEXT("提示"), MB_OK);
return TRUE;
}
else
return FALSE;
}
VOID UnloadHook(void)
{
if(g_hHook)
UnhookWindowsHookEx(g_hHook);
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
g_hModule = hModule;
MessageBox(NULL, TEXT("加载DLL!"), TEXT("提示"), MB_OK);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
新建def文件导出相关函数:
LIBRARY
EXPORTS
LoadHook
UnloadHook
编译生成 TestDLL.dll 文件
编写调用程序:DLL不能主动执行,因此需要编写调用程序:
#include <windows.h>
#include <stdio.h>
#define DLL_NAME "TestDLL.dll"
int main(int argc, char* argv[])
{
do{
HMODULE hModule = LoadLibraryA(DLL_NAME);
if(hModule == NULL)
break;
FARPROC pfnLoadHook = GetProcAddress(hModule, "LoadHook");
FARPROC pfnUnloadHook = GetProcAddress(hModule, "UnloadHook");
if(pfnLoadHook==NULL || pfnUnloadHook==NULL)
break;
if (pfnLoadHook())
printf("全局钩子加载成功!\r\n");
else{
printf("全局钩子加载失败!\r\n");
break;
}
printf("按任意键卸载全局钩子!\r\n");
getchar();
pfnUnloadHook();
printf("全局钩子卸载完成!\r\n");
}while(FALSE);
getchar();
return 0;
}
注:为了简化出错处理,使用了 do-while(0) 结构。
三、运行效果分析
全局钩子启用前:
启用全局钩子注入器时:
检查一下:
他本身进程没有注入,但是他的子进程 conhost.exe 还是被注入了:
四、总结与展望
本文从角度分析讲解了用户态下屏蔽 SetWindowsHookEx 函数注入模块的方法。此外,还可以通过 win32k.sys 中的内核结构来卸载进程的 Windows 钩子以及阻止消息模块注入,这涉及到内核补丁相关知识。具体可以参考:Windows hooks detector | Prekageo's Blog。
参考文献
- user32.__ClientLoadLibrary(x) | Opcode0x90's Blog
- CVE-2015-0057 POC · GitHub
- 防止Global Windows Hooks注入的一个方法 | 0CCh Blog
- Win7x64通过ClientLoadLibrary注入DLL-CSDN博客
- ring3-USER32!__ClientLoadLibrary定位-CSDN博客
- Windows hooks detector | Prekageo's Blog
- 利用WH_DEBUG消息进行反HOOK-看雪|kanxue.com
- Windows DebugProc 函数 - Win32 apps | Microsoft Learn
原文出处链接:[https://blog.csdn.net/qq_59075481/article/details/139206017]。
本文发布于:2024.05.26,更新于:2024.05.26。