现在我们来介绍一下其实现部分,所有的实现细节都在CAPIHookImpl类中来完成,由于大部分代码,是《Windows核心技术编程》上的例子,因此这里仅对处理方式和不同点进行说明。
我们先来看CAPIHookImpl的声明:
class CAPIHookImpl
{
public:
CAPIHookImpl(PCSTR pszCalleeModName, PCSTR pszFuncName, PROC pfnHook);
~CAPIHookImpl();
operator PROC(){return (m_pfnOrig);};
static BOOL SetupHook(BOOL bInstall, DWORD dwThreadID = 0, HMODULE hModule = NULL);
static HMODULE ModuleFromAddress(PVOID pv);
private:
static PVOID sm_pvMaxAppAddr;
static CAPIHookImpl *sm_pHead;
CAPIHookImpl *m_pNext;
PCSTR m_pszCalleeModName;
PCSTR m_pszFuncName;
PROC m_pfnOrig;
PROC m_pfnHook;
private:
static void WINAPI ReplaceIATEntryInAllMods(PCSTR pszCalleeModName, PCSTR pszFuncName, PROC pfnCurrent, PROC pfnNew);
static void WINAPI ReplaceIATEntryInOneMod(PCSTR pszCalleeModName, PCSTR pszFuncName, PROC pfnCurrent, PROC pfnNew, HMODULE hModCaller);
static void WINAPI FixupNewlyLoadedModule(HMODULE hMod, DWORD dwFlags);
static HMODULE WINAPI LoadLibraryA(PCSTR pszModulePath);
static HMODULE WINAPI LoadLibraryW(PCWSTR pszModulePath);
static HMODULE WINAPI LoadLibraryExA(PCSTR pszModulePath, HANDLE hFile, DWORD dwFlags);
static HMODULE WINAPI LoadLibraryExW(PCWSTR pszModulePath, HANDLE hFile, DWORD dwFlags);
static FARPROC WINAPI GetProcAddress(HMODULE hModule,PCSTR pszProcName);
private:
static CAPIHookImpl sm_LoadLibraryA;
static CAPIHookImpl sm_LoadLibraryW;
static CAPIHookImpl sm_LoadLibraryExA;
static CAPIHookImpl sm_LoadLibraryExW;
static CAPIHookImpl sm_GetProcAddress;
};
我们从该类的声明中,可以看到定义了5各全局的静态变量,来自动挂接5个基本的操作函数。
其中sm_pHead是所有挂接API的链表的头指针,然后每一个类中,都定义指向下一个挂接类的指针,这样可以通过这两个变量就可以遍历所有的挂接函数。
实现细节:
2.1)CAPIHookImpl::CAPIHookImpl(PCSTR pszCalleeModName, PCSTR pszFuncName, PROC pfnHook)
2.1.1)取得原始的API函数指针。
2.1.2)将this链接到挂接函数链表中。
2.1.3)调用ReplaceIATEntryInAllMods内部函数,变量当前进程的所有模块,然后替换所有模块的输入节中对于本挂接函数的指针。注意APIHook.DLL本身要排除在外,否则没人干活了。
注意这里书上的代码中没有同步处理,因为其中对全局的链表头指针进行了操作,因此在多线程环境下会出问题。
2.2)CAPIHookImpl::~CAPIHookImpl()
2.2.1)调用ReplaceIATEntryInAllMods内部函数,变量当前进程的所有模块,然后还原所有模块的输入节中对于本挂接函数的指针。
2.2.2)将this从挂接函数链表中摘除。
这里我们看到处理顺序刚好和构造函数相反。
注意这里同样要考虑同步问题。
2.3)HMODULE CAPIHookImpl::ModuleFromAddress(PVOID pv)
HMODULE CAPIHookImpl::ModuleFromAddress(PVOID pv)
{
MEMORY_BASIC_INFORMATION mbi;
return ::VirtualQuery(pv, &mbi, sizeof(mbi)) != NULL ? (HMODULE)mbi.AllocationBase : NULL;
}
和书上一样,通过VirtualQuery函数来实现。
2.4)ReplaceIATEntryInAllMods和ReplaceIATEntryInOneMod函数
同书上一样,代码太长,不贴了。
2.5)void CAPIHookImpl::FixupNewlyLoadedModule(HMODULE hModule, DWORD dwFlags)
这个函数书上的代码有点小问题,就是没有对本身APIHook.DLL进行排除处理,这样会造成原始函数指针丢失,陷入死循环,最后堆栈溢出。
这里调试了好久才发现问题:(。
2.6)4个LoadLibrary
这里可以对书上的代码进行优化一下,即已经加载过的DLL就不用在修理了。代码如下:
HMODULE CAPIHookImpl::LoadLibraryA(PCSTR pszModulePath)
{
HMODULE hModule = ::GetModuleHandleA(pszModulePath);
if(hModule == NULL)
{
hModule = ::LoadLibraryA(pszModulePath);
FixupNewlyLoadedModule(hModule, 0);
}
else
{
hModule = ::LoadLibraryA(pszModulePath);
}
return hModule;
}
2.7)FARPROC CAPIHookImpl::GetProcAddress(HMODULE hModule, PCSTR pszProcName)
这里也和书上差不多,先取得原始函数指针,然后查找挂接函数列表后返回。
注意书上的代码也没有加上同步处理,这里也已经补上。代码如下:
FARPROC WINAPI CAPIHookImpl::GetProcAddress(HMODULE hModule, PCSTR pszProcName)
{
HANDLE hMutex = ::CreateMutex(NULL, FALSE, TEXT("APIHook_CAPIHook_Mutex"));
WaitForSingleObject(hMutex, INFINITE);
FARPROC pfn = ::GetProcAddress(hModule, pszProcName);
if(pfn != NULL)
{
for (CAPIHookImpl *p = sm_pHead; (p != NULL); p = p->m_pNext)
{
if (pfn == p->m_pfnOrig)
{
pfn = p->m_pfnHook;
break;
}
}
}
::ReleaseMutex(hMutex);
::CloseHandle(hMutex);
return (pfn);
}
写到这,回过头整理一下,其实也没有多少东西,难度也不是很大。看来也只有缺少人来整理而已。
好了,到现在基本APIHook.DLL基本上介绍差不多了,我们来说明一下,上一篇中卖的那个关子,还记得吧。
我们先来看看WindowsAPI【SetWindowsHookEx】的原型。
HHOOK SetWindowsHookEx( int idHook,
HOOKPROC lpfn,
HINSTANCE hMod,
DWORD dwThreadId
);
其中lpfn为钩子函数,hMod为钩子函数所在的模块句柄。
其实这里说的不太准确,hMod不一定非是钩子函数所在的模块句柄,只要是该句柄存在(即有效)的时候,钩子函数也有效即可。
比如说。钩子函数在DEMOA.DLL中定义,DEMOB.DLL又静态依赖DEMOA.DLL。这样我们就可以这样调用SetWindowsHookEx,如下:
::SetWindowsHookEx(WH_GETMESSAGE, GetMessageProc, hDemoB, dwThreadID);
其中GetMessageProc为DEMOA.DLL中定义的函数。
进过验证这样做完全是可行有效的,于是我们就有了在下面函数中使用hModule参数的高级应用了。
BOOL SetupHook(DWORD dwThreadID, HMODULE hModule);
因为例子中的GetMessageProc函数为APIHook.DLL中的函数,如果我们调用SetWindowsHookEx时,指定APIHook.DLL的模块句柄时,远程加载的仅仅是APIHook.DLL。远程加载仅实现5个基本挂接函数的模块是没有意义的。
所以我们在调用SetupHook时,将参数hModule指定成UserAPIHook.DLL的模块句柄,只要保证UserAPIHook.DLL静态依赖APIHook.DLL即可。
通常我们开发扩展APIHook.DLL时,会使用APIHook.DLL的导出类,因此也基本上可以保证的。
好了,谜题解开了。大家都明朗了吧。
最后,我们举一个UserAPIHook.DLL的实现样例来结束这个话题吧。
UserAPIHook.H
#ifdef USERAPIHOOK_EXPORTS
#define USERAPIHOOK_API __declspec(dllexport)
#else
#define USERAPIHOOK_API __declspec(dllimport)
#endif
#ifdef __cplusplus
extern "C"
{
#endif
USERAPIHOOK_API BOOL UserSetupHook(DWORD dwThreadID);
USERAPIHOOK_API BOOL UserSetupHookWithHWnd(HWND hWnd);
USERAPIHOOK_API BOOL UserSetupUnhook();
USERAPIHOOK_API HMODULE UserRemoteInject(DWORD dwProcessID);
USERAPIHOOK_API HMODULE UserRemoteInjectWithHWnd(HWND hWnd);
USERAPIHOOK_API BOOL UserRemoteEject(DWORD dwProcessID, HMODULE hModule);
USERAPIHOOK_API BOOL UserRemoteEjectWithHWnd(HWND hWnd, HMODULE hModule);
#ifdef __cplusplus
}
#endif
UserAPIHook.cpp
#define USERAPIHOOK_EXPORTS
#include "UserAPIHook.h"
#include "../APIHook/APIHook.h"
#ifdef _DEBUG
#pragma comment(lib, "../Debug/APIHook")
#else
#pragma comment(lib, "../Release/APIHook")
#endif
extern HMODULE g_hModule;
DEFINE_USERAPIHOOK4("user32.dll", int, MessageBoxA, HWND, hWnd, LPCSTR, lpText, LPCSTR, lpCaption, UINT, uType)
{
return CallBack_MessageBoxA(hWnd, lpText, lpCaption, uType);
}
DEFINE_USERAPIHOOK4("user32.dll", int, MessageBoxW, HWND, hWnd, LPCWSTR, lpText, LPCWSTR, lpCaption, UINT, uType)
{
return CallBack_MessageBoxW(hWnd, lpText, lpCaption, uType);
}
#ifdef __cplusplus
extern "C"
{
#endif
USERAPIHOOK_API BOOL UserSetupHook(DWORD dwThreadID)
{
return ::SetupHook(dwThreadID, g_hModule);
}
USERAPIHOOK_API BOOL UserSetupHookWithHWnd(HWND hWnd)
{
return ::SetupHookWithHWnd(hWnd, g_hModule);
}
USERAPIHOOK_API BOOL UserSetupUnhook()
{
return ::SetupUnhook();
}
USERAPIHOOK_API HMODULE UserRemoteInject(DWORD dwProcessID)
{
CHAR szModuleName[MAX_PATH];
::GetModuleFileNameA(g_hModule, szModuleName, sizeof(szModuleName));
return ::RemoteInject(dwProcessID, szModuleName);
}
USERAPIHOOK_API HMODULE UserRemoteInjectWithHWnd(HWND hWnd)
{
CHAR szModuleName[MAX_PATH];
::GetModuleFileNameA(g_hModule, szModuleName, sizeof(szModuleName));
return ::RemoteInjectWithHWnd(hWnd, szModuleName);
}
USERAPIHOOK_API BOOL UserRemoteEject(DWORD dwProcessID, HMODULE hModule)
{
return ::RemoteEject(dwProcessID, hModule);
}
USERAPIHOOK_API BOOL UserRemoteEjectWithHWnd(HWND hWnd, HMODULE hModule)
{
return ::RemoteEjectWithHWnd(hWnd, hModule);
}
#ifdef __cplusplus
}
#endif