使用Detours对LoadLibraryExW进行HOOK屏蔽全局钩子

全局钩子都是做为DLL挂接注入进程,假如应用程序A.exe安装了WH_GETMESSAGE的全局钩子,钩子函数在B.dll中,那么当其它程序在调用GetMessage函数从自己的消息队列中取消息的时候,系统发现程序A安装了WH_GETMESSAGE的全局钩子,就会检查调用GetMessage的进程是否加载了B.dll,如果没有,就调用LoadLibrary进行加载,然后调用B.dll中的钩子过程。这样,钩子dll就会在所有调用GetMessage的进程中加载。

如果程序本身能够对LoadLibrary进行处理,就能够防范其他DLL模块的注入。这里未经任何判断直接返回NULL,有点武断,会影响系统效率。

PS:上面这句话说了等于白说,现在谁还用这种方式啊,都是自己解析加载,要不就走LDR那套,这些都是老的不能再老的东西了,不过这里作为基础学习,还是粘下代码作为备忘了。

#include <Windows.h>
#include <stdio.h>
#include "detours.h"

#pragma comment(lib, "detours.lib")
#pragma warning(disable : 4098)

static HMODULE (WINAPI* _LoadLibraryExW)(LPCTSTR lpFileName,HANDLE hFile,DWORD dwFlags) = LoadLibraryExW;

VOID OutputDebugStringExW(const wchar_t* szFormat, ...)
{
	WCHAR Buffer[2048] = {0};

	va_list argList;
	va_start(argList, szFormat);
	vswprintf(Buffer, sizeof(Buffer), szFormat, argList);
	va_end(argList);

	OutputDebugStringW(Buffer);
}

HMODULE WINAPI NEW_LoadLibraryExW(LPCTSTR lpFileName,HANDLE hFile,DWORD dwFlags)
{
	OutputDebugStringExW(L"%s", lpFileName);
	return NULL;
	//return _LoadLibraryExW(lpFileName, hFile, dwFlags);
}

VOID Hook()
{
	DetourRestoreAfterWith();
	DetourTransactionBegin();
	DetourUpdateThread(GetCurrentThread());

	DetourAttach(&(PVOID&)_LoadLibraryExW, NEW_LoadLibraryExW ) ;

	DetourTransactionCommit();
}

VOID UnHook()
{
	DetourTransactionBegin();
	DetourUpdateThread(GetCurrentThread());

	DetourDetach(&(PVOID&)_LoadLibraryExW, NEW_LoadLibraryExW );

	DetourTransactionCommit();
}

BOOL APIENTRY DllMain(HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
	if (ul_reason_for_call == DLL_PROCESS_ATTACH)
	{  
		Hook(); 
	}  
	else if (ul_reason_for_call == DLL_PROCESS_DETACH) 
	{  
		UnHook();
	}  
	return TRUE;
}

以上是自己的DLL,如果需要对某个进程进行保护,只需要插入相关进程就好。当然相关进程如果不想麻烦,可以直接把HOOK过程写入程序代码。



经过调试发现,正常的加载dll函数调用都是从kernel32.dll中来的,而只有加载钩子过程是在user32.dll中进行的。可以对上面的函数进行修改,判断LoadLibrary函数的返回地址,如果是在user32.dll的地址空间,就认为是钩子dll的加载,直接返回NULL就可以了。

首先获取user32.dll的基地址

static DWORD m_dwUser32Low;    //user32.dll 的加载基址
static DWORD m_dwUser32Hi;     //user32.dll 的加载基址+ImageSize

MODULEINFO user32ModInfo = {0};

//获取user32.dll的加载基址和映象大小  
GetModuleInformation(GetCurrentProcess(), GetModuleHandle("user32.dll"), 
					 &user32ModInfo, sizeof(user32ModInfo));
m_dwUser32Low = (DWORD)user32ModInfo.lpBaseOfDll;
m_dwUser32Hi = (DWORD)user32ModInfo.lpBaseOfDll+user32ModInfo.SizeOfImage;

下面进行判断,如果来自user32.dll的调用直接进行返回。

HMODULE WINAPI NEW_LoadLibraryExW(LPCTSTR lpFileName,HANDLE hFile,DWORD dwFlags)
{
	//获取函数的返回地址
	DWORD dwCaller;
	__asm push dword ptr [ebp+4]
	__asm pop  dword ptr [dwCaller] 

	//判断是否是从User32.dll调用的
	if(dwCaller > m_dwUser32Low && dwCaller < m_dwUser32Hi)
	{
		OutputDebugStringExW(L"%s", lpFileName);
		return NULL;
	}
	
	return _LoadLibraryExW(lpFileName, hFile, dwFlags);
}

PS:函数在调用的时候,先要把参数入栈,然后把返回地址入栈,esp指向的应该就是函数的返回地址了。但是为了返回函数时恢复原来的栈和在函数中方便引用传递的参数,编译器一般都会产生两条指令。

push ebp
mov ebp,esp
先把ebp入栈,把原来的esp保存在ebp寄存器中,这样,我们的返回地址就是[ebp+4],第一个参数是[ebp+8],第二个是[ebp+0xC]。



版权声明:本文为SR0ad原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。