tag: Hook, Detours,Windows,CreateRemoteThread,MessageBox
前言
项目开发中需要跟踪其它程序的API调用情况。但厂商又无源码提供,故只好自己动手去Trace了。
Google/Baidu了许久,也搜集了很多代码。也经过实验和测试,总结了本文供大家参考。
本文针对Windows Hook技术在编程中的应用进行讨论,并着重对应用比较广泛的Detours使用方法做了阐述。
Hook/钩子的基本原理
Windows 钩子的本质是一段用以处理系统消息的程序,通过系统调用,将其挂入到系统。钩子的种类有很多,每一种钩子负责截获并处理相应的消息。钩子机制允许应用程序截获并处理发往指定窗口的消息或特定事件,其监视的窗口即可以是本进程内的也可以是由其他进程所创建的。在特定的消息发出,并在到达目的窗口之前,钩子程序先行截获此消息并得到对其的控制权。此时在钩子函数中就可以对截获的消息进行各种修改处理,甚至强行终止该消息的继续传递。
Detours库是Microsoft研究院的一个开发库,最新版本为3.0。它可以拦截任意的API调用,拦截代码是在动态运行时加载的,替换目标API最前面的几条指令,使其无条件的跳转到用户提供的拦截函数。
Hook/钩子的安装与卸载
开始代码了,真的非常easy!以Hook MessageBox为例:
准备工作:Detours lib/h files, VC++ 6/2005/2008....
A. 封装Dll.
1. VC++向导创建一个MFC Dll项目,一路向西,默认缺省选项。
改写DllMain如下:
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
::OutputDebugString( "DLL_PROCESS_ATTACH\n" );
InstallHook();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
::OutputDebugString( "DLL_PROCESS_DETACH\n" );
UnInstallHook();
break;
}
return TRUE;
}
2. 增加InstallHook/UnInstallHook函数:
BOOL APIENTRY InstallHook()
{
DetourTransactionBegin();
DetourUpdateThread( GetCurrentThread() );
g_pOldMessageBoxA = DetourFindFunction( "User32.dll","MessageBoxA" );
g_pOldMessageBoxW = DetourFindFunction( "User32.dll","MessageBoxW" );
DetourAttach( &g_pOldMessageBoxA, MyMessageBoxA );
DetourAttach( &g_pOldMessageBoxW, MyMessageBoxW );
LONG ret = DetourTransactionCommit();
return ret==NO_ERROR;
}
BOOL APIENTRY UnInstallHook()
{
DetourTransactionBegin();
DetourUpdateThread( GetCurrentThread() );
DetourDetach(&g_pOldMessageBoxA, MyMessageBoxA);
DetourDetach(&g_pOldMessageBoxW, MyMessageBoxW);
LONG ret=DetourTransactionCommit();
return ret==NO_ERROR;
}
注意上文代码中的MessageBoxA和MessageBoxW字样,这就是我们所要拦截的API函数咯。至于为何不是MessageBox,却是MessageBoxA和MessageBoxW这样的怪样子,新手们去看看Windows核心编程啦。
3. 我们的拦截函数
typedef int (WINAPI *PfuncMessageBoxA)(HWND hWnd,LPCSTR lpText,LPCSTR lpCaption,UINT uType);
typedef int (WINAPI *PfuncMessageBoxW)( HWND hWnd, LPCWSTR lpText,LPCWSTR lpCaption,UINT uType);
int WINAPI MyMessageBoxA(HWND hWnd,LPCSTR lpText,LPCSTR lpCaption,UINT uType)
{
return ((PfuncMessageBoxA)g_pOldMessageBoxA)(hWnd, "Hook This!","My hook",uType);
}
int WINAPI MyMessageBoxW(HWND hWnd, LPCWSTR lpText,LPCWSTR lpCaption,UINT uType)
{
return ((PfuncMessageBoxW)g_pOldMessageBoxW)(hWnd,L"Hook This!",L"My hook",uType);
}
此例中我们只是更改了MessageBox的Text和Caption。
4. 记得保留原版的MessageBox。
build一下,发现错误:g_pOldMessageBoxA还未定义? 还记得上文中的
g_pOldMessageBoxA = DetourFindFunction( "User32.dll","MessageBoxA" );
这个函数指针需要保留。
PVOID g_pOldMessageBoxW=NULL;
PVOID g_pOldMessageBoxA=NULL;
DLL就万事大吉,只欠东风了。
B. 注射器
所谓的注射器,就是东风了,要不然光有dll也用,还需要用注射器把我们的dll注入到肉鸡中。老生常谈,还是老一套的CreateRemoteThread。这个方法是如此的简单而且优雅。
HANDLE hProcess = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
FALSE, pid);
if (hProcess != NULL)
{
TRACE( "InjectHook \n" );
HANDLE hThread;
char szLibPath [_MAX_PATH];
void* pLibRemote = 0;
DWORD hLibModule = 0;
HMODULE hKernel32 = ::GetModuleHandle("Kernel32");
if( !::GetSystemDirectory(szLibPath, _MAX_PATH))
return;
strcat(szLibPath, "C:\\windows\\HookDll.dll");
pLibRemote = ::VirtualAllocEx( hProcess, NULL, sizeof(szLibPath), MEM_COMMIT, PAGE_READWRITE );
if( pLibRemote == NULL )
return;
::WriteProcessMemory(hProcess, pLibRemote, (void*)szLibPath,sizeof(szLibPath),NULL);
hThread = ::CreateRemoteThread( hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE) ::GetProcAddress(hKernel32,"LoadLibraryA"),
pLibRemote, 0, NULL );
if( hThread != NULL )
{
::WaitForSingleObject( hThread, INFINITE );
::GetExitCodeThread( hThread, &hLibModule );
::CloseHandle( hThread );
}
}
解释一下以上的szLibPath, VirtualAllocEx, WriteProcessMemory, CreateRemoteThread。
这是远线程,不是在你的进程里,而szLibPath指向的是你的进程里的数据,到了目标进程,这个指针都不知道指向哪儿去了,同样CreateRemoteThread中的pfnStartAddr这个地址上的代码到了目标进程里也不知道是什么了,不知道是不是你想要的LoadLibraryA了。但是,问题总是可以解决的,Windows有些很强大的API函数,他们可以在目标进程里分配内存,可以将你的进程中的数据拷贝到目标进程中。
抛砖引玉。欢迎臭鸡蛋!