用VC编程阻止全局钩子的加载
2007-03-15 21:52:19 来源:WEB开发网核心提示: 修改LoadLibraryExW函数的前5个字节,用一个jmp 指令跳到我们的newLoadLibraryExW函数起始处,用VC编程阻止全局钩子的加载(2),虽然这里只用了5个字节,但是我们先看一下LoadLibraryExW函数的前两条指令: //你机器上的版本具体的数字可能和我的不一
//你机器上的版本具体的数字可能和我的不一样
push 34h?//6A 34
push 7C80E288h?//68 88 E2 80 7C
一共有7个字节,我们不能只修改前5个字节,然后从fakeLoadLibraryExW函数跳到第6个字节处开始执行,而要跳到第三条指令即第8个字节开始处,这就是上一步N为什么取7的原因。画个图示意一下,修改前:
修改后:
以下是封装的一个类,使用时定义一个该类的全局变量,调用一下PatchLoadLibrary函数即可。
//***********************************************************************************//
// FileName : GBlockHookDll.h
// Author :耿海增
// Date : 2006.10.07
//***********************************************************************************//
#pragma once
#include
#pragma comment(lib,"psapi.lib")
class GBlockHookDll
{
public:
GBlockHookDll()
{
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;
}
void PatchLoadLibrary()
{
//LoadLibraryExW
//7C801AF1 6A 34 push 34h
//7C801AF3 68 88 E2 80 7C push 7C80E288h
LPVOID* pfnRaw = (LPVOID*)&rawLoadLibraryExW;
LPVOID fnNew = (LPVOID)newLoadLibraryExW;
BYTE* fnRaw = (BYTE*)*pfnRaw;
//1 save the first 7 bytes
const int nFirstBytes = 7;
BYTE* fnFake = (BYTE*)fakeLoadLibraryExW;
memcpy(fnFake,*pfnRaw,nFirstBytes);
fnFake[nFirstBytes] = 0xE9; //jmp to rawAddr+nFirstBytes
*(UINT32*)(fnFake + nFirstBytes+1) = (UINT32)fnRaw+nFirstBytes - (UINT32)(fnFake + nFirstBytes + 5);
//2 modify the raw to jmp to fnNew
DWORD dwOldProtect = 0;
VirtualProtect(fnRaw,nFirstBytes,PAGE_READWRITE,&dwOldProtect); //修改该代码段的属性为可写
*fnRaw = 0xE9;
*(UINT32*)(fnRaw+1) = (UINT32)fnNew - (UINT32)(fnRaw + 5);
VirtualProtect(fnRaw,nFirstBytes,dwOldProtect,0);
//3 change the rawPointer
*pfnRaw = fnFake;
}
private:
static HMODULE WINAPI newLoadLibraryExW(LPCWSTR lpLibFileName,HANDLE hFile,DWORD dwFlags)
{
//get the return address
DWORD dwCaller;
__asm push dword ptr [ebp+4]
__asm pop dword ptr [dwCaller]
if(dwCaller > m_dwUser32Low && dwCaller < m_dwUser32Hi)
{
#ifdef _DEBUG
UINT uLenWide = lstrlenW(lpLibFileName);
char* pNewChar = new char[uLenWide + 1];
memset(pNewChar,0,uLenWide+1);
WideCharToMultiByte(CP_ACP,0,lpLibFileName,-1,pNewChar,uLenWide,NULL,NULL);
TRACE2(".......................LoadLibrary:return addr 0x%x,%s ",dwCaller,pNewChar);
TRACE("Blocked.......................
");
delete []pNewChar;
#endif
return 0;
}
return rawLoadLibraryExW(lpLibFileName,hFile,dwFlags);
}
private:
static DWORD m_dwUser32Low; //user32.dll 的加载基址
static DWORD m_dwUser32Hi; //user32.dll 的加载基址+ImageSize
static BYTE fakeLoadLibraryExW[12]; //save first bytes of the raw function,and jmp back to that function
//保存LoadLibraryExW的指针,然后修改为fakeLoadLibraryExW
static HMODULE (WINAPI *rawLoadLibraryExW)( LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags );
};
DWORD GBlockHookDll::m_dwUser32Low = 0;
DWORD GBlockHookDll::m_dwUser32Hi = 0;
BYTE GBlockHookDll::fakeLoadLibraryExW[12] = {0};
HMODULE (WINAPI *GBlockHookDll::rawLoadLibraryExW)(LPCWSTR lpLibFileName,HANDLE hFile,DWORD dwFlags) = LoadLibraryExW;
注1:怎么知道函数的返回地址呢?我们都知道,函数调用的时候,先要把参数入栈,然后把返回地址入栈,这样,在我们的函数里,esp指向的应该就是函数的返回地址了。但是为了返回函数时恢复原来的栈和在函数中方便引用传递的参数,编译器一般都会产生两条指令:
push ebp
mov ebp,esp
先把ebp入栈,把原来的esp保存在ebp寄存器中,这样,我们的返回地址就是[ebp+4],第一个参数是[ebp+8],第二个是[ebp+0xC]
注2:如果想写一个通用一点儿的API Hook,就不能简单的patch前5个或者前7字节了,需要根据不同的指令分析需要patch多少字节。可以参考微软的 Detours 的实现。
更多精彩
赞助商链接