最近一段时间由于使用MinHook的API挂钩不稳定,经常因为挂钩地址错误而导致宿主进程崩溃。听同事介绍了一款智能强大的挂钩引擎EasyHook。它比微软的detours好的一点是它的x64注入支持是免费开源的。不想微软的detours,想搞x64还得购买。
好了,闲话不多说,先下载EasyHook的开发库,当然有兴趣的同学可以下载源码进行学习。下载地址:http://easyhook.codeplex.com/releases/view/24401。我给的这个是2.6版本的。
EasyHook提供了两种模式的注入管理。一种是托管代码的注入,另一种是非托管代码的注入。我是学习C++的,所以直接学习了例子中的非托管项目UnmanagedHook。里面给了一个简单的挂钩MessageBeep API的示例。我需要将其改造成支持远程注入的。下面先给出钩子DLL代码:
// dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "stdafx.h" #include "HookApi.h" #include "easyhook.h" #include "ntstatus.h" ptrCreateFileW realCreateFileW = NULL; ptrCreateFileA realCreateFileA = NULL; HMODULE hKernel32 = NULL; TRACED_HOOK_HANDLE hHookCreateFileW = new HOOK_TRACE_INFO(); TRACED_HOOK_HANDLE hHookCreateFileA = new HOOK_TRACE_INFO(); NTSTATUS statue; ULONG HookCreateFileW_ACLEntries[1] = {0}; ULONG HookCreateFileA_ACLEntries[1] = {0}; int PrepareRealApiEntry() { OutputDebugString(L"PrepareRealApiEntry()\n"); // 获取真实函数地址 HMODULE hKernel32 = LoadLibrary(L"Kernel32.dll"); if (hKernel32 == NULL) { OutputDebugString(L"LoadLibrary(L\"Kernel32.dll\") Error\n"); return -6002; } OutputDebugString(L"LoadLibrary(L\"Kernel32.dll\") OK\n"); realCreateFileW = (ptrCreateFileW)GetProcAddress(hKernel32, "CreateFileW"); if (realCreateFileW == NULL) { OutputDebugString(L"(ptrCreateFileW)GetProcAddress(hKernel32, \"CreateFileW\") Error\n"); return -6007; } OutputDebugString(L"(ptrCreateFileW)GetProcAddress(hKernel32, \"CreateFileW\") OK\n"); realCreateFileA = (ptrCreateFileA)GetProcAddress(hKernel32, "CreateFileA"); if (realCreateFileA == NULL) { OutputDebugString(L"(ptrCreateFileA)GetProcAddress(hKernel32, \"CreateFileA\") Error\n"); return -6007; } OutputDebugString(L"(ptrCreateFileA)GetProcAddress(hKernel32, \"CreateFileA\") OK\n"); return 0; } void DoHook() { OutputDebugString(L"DoHook()\n"); statue = LhInstallHook(realCreateFileW, MyCreateFileW, /*(PVOID)0x12345678*/NULL, hHookCreateFileW); if(!SUCCEEDED(statue)) { switch (statue) { case STATUS_NO_MEMORY: OutputDebugString(L"STATUS_NO_MEMORY\n"); break; case STATUS_NOT_SUPPORTED: OutputDebugString(L"STATUS_NOT_SUPPORTED\n"); break; case STATUS_INSUFFICIENT_RESOURCES: OutputDebugString(L"STATUS_INSUFFICIENT_RESOURCES\n"); break; default: WCHAR dbgstr[512] = {0}; wsprintf(dbgstr, L"%d\n", statue); OutputDebugString(dbgstr); } OutputDebugString(L"LhInstallHook(GetProcAddress(hKernel32, \"CreateFileW\"),MyCreateFileW,(PVOID)0x12345678,hHookCreateFileW); Error\n"); return; } OutputDebugString(L"Hook CreateFileW OK\n"); statue = LhInstallHook(realCreateFileA, MyCreateFileA, /*(PVOID)0x12345678*/NULL, hHookCreateFileA); if(!SUCCEEDED(statue)) { switch (statue) { case STATUS_NO_MEMORY: OutputDebugString(L"STATUS_NO_MEMORY\n"); break; case STATUS_NOT_SUPPORTED: OutputDebugString(L"STATUS_NOT_SUPPORTED\n"); break; case STATUS_INSUFFICIENT_RESOURCES: OutputDebugString(L"STATUS_INSUFFICIENT_RESOURCES\n"); break; default: WCHAR dbgstr[512] = {0}; wsprintf(dbgstr, L"%d\n", statue); OutputDebugString(dbgstr); } OutputDebugString(L"LhInstallHook(GetProcAddress(hKernel32, \"CreateFileA\"),MyCreateFileA,(PVOID)0x12345678,hHookCreateFileA); Error\n"); return; } OutputDebugString(L"Hook CreateFileA OK\n"); // 一定要调用这个函数,否则注入的钩子无法正常运行。 LhSetExclusiveACL(HookCreateFileA_ACLEntries, 1, hHookCreateFileA); LhSetExclusiveACL(HookCreateFileW_ACLEntries, 1, hHookCreateFileW); } void DoneHook() { OutputDebugString(L"DoneHook()\n"); // this will also invalidate "hHook", because it is a traced handle... LhUninstallAllHooks(); // this will do nothing because the hook is already removed... LhUninstallHook(hHookCreateFileA); LhUninstallHook(hHookCreateFileW); // now we can safely release the traced handle delete hHookCreateFileA; hHookCreateFileA = NULL; delete hHookCreateFileW; hHookCreateFileW = NULL; // even if the hook is removed, we need to wait for memory release LhWaitForPendingRemovals(); } BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { OutputDebugString(L"DllMain::DLL_PROCESS_ATTACH\n"); // 准备好原始地址与目的地址 int errCode = PrepareRealApiEntry(); if (errCode != 0) { OutputDebugString(L"PrepareRealApiEntry() Error\n"); return FALSE; } // 开始挂钩 DoHook(); break; } case DLL_THREAD_ATTACH: { OutputDebugString(L"DllMain::DLL_THREAD_ATTACH\n"); break; } case DLL_THREAD_DETACH: { OutputDebugString(L"DllMain::DLL_THREAD_DETACH\n"); break; } case DLL_PROCESS_DETACH: { OutputDebugString(L"DllMain::DLL_PROCESS_DETACH\n"); // 卸载钩子 DoneHook(); break; } } return TRUE; }
// HookSvr.cpp #include "stdafx.h" #include "HookApi.h" #include "easyhook.h" HANDLE WINAPI MyCreateFileW( __in LPCWSTR lpFileName, __in DWORD dwDesiredAccess, __in DWORD dwShareMode, __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes, __in DWORD dwCreationDisposition, __in DWORD dwFlagsAndAttributes, __in_opt HANDLE hTemplateFile ) { HANDLE hHandle = NULL; // 执行钩子 if (realCreateFileW == NULL) { OutputDebugString(L"realCreateFileW is NULL\n"); return INVALID_HANDLE_VALUE; } else { OutputDebugString(L"realCreateFileW is not NULL\n"); hHandle = (realCreateFileW)(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); OutputDebugString(L"MyCreateFileW : "); OutputDebugString(lpFileName); OutputDebugString(L"\n"); } return hHandle; } HANDLE WINAPI MyCreateFileA( __in LPCSTR lpFileName, __in DWORD dwDesiredAccess, __in DWORD dwShareMode, __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes, __in DWORD dwCreationDisposition, __in DWORD dwFlagsAndAttributes, __in_opt HANDLE hTemplateFile ) { HANDLE hHandle = NULL; // 执行钩子 if (realCreateFileA == NULL) { OutputDebugString(L"realCreateFileA is NULL\n"); return INVALID_HANDLE_VALUE; } else { OutputDebugString(L"realCreateFileA is not NULL\n"); hHandle = (realCreateFileA)(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); OutputDebugString(L"MyCreateFileW : "); OutputDebugStringA(lpFileName); OutputDebugString(L"\n"); } return hHandle; }
钩子这一部分我弄了比较久,主要是API不熟悉,不过好在弄好了。
// HookSvr.h #pragma once #include <Windows.h> #ifndef _M_X64 #pragma comment(lib, "EasyHook32.lib") #else #pragma comment(lib, "EasyHook64.lib") #endif HANDLE WINAPI MyCreateFileW( __in LPCWSTR lpFileName, __in DWORD dwDesiredAccess, __in DWORD dwShareMode, __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes, __in DWORD dwCreationDisposition, __in DWORD dwFlagsAndAttributes, __in_opt HANDLE hTemplateFile ); typedef HANDLE (WINAPI *ptrCreateFileW)( __in LPCWSTR lpFileName, __in DWORD dwDesiredAccess, __in DWORD dwShareMode, __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes, __in DWORD dwCreationDisposition, __in DWORD dwFlagsAndAttributes, __in_opt HANDLE hTemplateFile ); extern ptrCreateFileW realCreateFileW; HANDLE WINAPI MyCreateFileA( __in LPCSTR lpFileName, __in DWORD dwDesiredAccess, __in DWORD dwShareMode, __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes, __in DWORD dwCreationDisposition, __in DWORD dwFlagsAndAttributes, __in_opt HANDLE hTemplateFile ); typedef HANDLE (WINAPI *ptrCreateFileA)( __in LPCSTR lpFileName, __in DWORD dwDesiredAccess, __in DWORD dwShareMode, __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes, __in DWORD dwCreationDisposition, __in DWORD dwFlagsAndAttributes, __in_opt HANDLE hTemplateFile ); extern ptrCreateFileA realCreateFileA;
接下来是注入工具,这里指提供核心代码。本来EasyHook还提供了一个叫RhInjectLibrary()方法直接注入,这种方法相当稳定,推荐使用。我本来也用它,但是发现注入会失败,所以就采用了比较通用的远程注入代码,如下:
BOOL RtlFileExists(WCHAR* InPath) { HANDLE hFile; if((hFile = CreateFileW(InPath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL)) == INVALID_HANDLE_VALUE) return FALSE; CloseHandle(hFile); return TRUE; } BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) { TOKEN_PRIVILEGES tp; HANDLE hToken; LUID luid; if( !OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) ) { return FALSE; } if( !LookupPrivilegeValue(NULL, // lookup privilege on local system lpszPrivilege, // privilege to lookup &luid) ) // receives LUID of privilege { return FALSE; } tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; if( bEnablePrivilege ) tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; else tp.Privileges[0].Attributes = 0; // Enable the privilege or disable all privileges. if( !AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES) NULL, (PDWORD) NULL) ) { return FALSE; } if( GetLastError() == ERROR_NOT_ALL_ASSIGNED ) { //The token does not have the specified privilege. return FALSE; } return TRUE; } typedef DWORD (WINAPI *PFNTCREATETHREADEX) ( PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, LPVOID ObjectAttributes, HANDLE ProcessHandle, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, BOOL CreateSuspended, DWORD dwStackSize, DWORD dw1, DWORD dw2, LPVOID Unknown ); BOOL MyCreateRemoteThread(HANDLE hProcess, LPTHREAD_START_ROUTINE pThreadProc, LPVOID pRemoteBuf) { HANDLE hThread = NULL; FARPROC pFunc = NULL; BOOL bHook; // 判断系统版本 OSVERSIONINFO osvi; //BOOL bIsWindowsXPorLater; ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); GetVersionEx(&osvi); if (osvi.dwMajorVersion == 6) { bHook = TRUE; } else { bHook = FALSE; } if(bHook) // Vista, 7, Server2008 { pFunc = GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtCreateThreadEx"); if( pFunc == NULL ) { //GetLastError()); return FALSE; } OutputDebugString(L"MyCreateRemoteThread"); ((PFNTCREATETHREADEX)pFunc)(&hThread, 0x1FFFFF, NULL, hProcess, pThreadProc, pRemoteBuf, FALSE, NULL, NULL, NULL, NULL); if( hThread == NULL ) { return FALSE; } } else // 2000, XP, Server2003 { hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL); if( hThread == NULL ) { return FALSE; } } if( WAIT_FAILED == WaitForSingleObject(hThread, INFINITE) ) { return FALSE; } return TRUE; } BOOL InjectDll(DWORD dwPID, const wchar_t *szDllName) { HANDLE hProcess = NULL; LPVOID pRemoteBuf = NULL; FARPROC pThreadProc = NULL; DWORD dwBufSize = wcslen(szDllName)*sizeof(wchar_t)+2; if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) ) { return FALSE; } pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE); WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllName, dwBufSize, NULL); pThreadProc = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW"); if( !MyCreateRemoteThread(hProcess, (LPTHREAD_START_ROUTINE)pThreadProc, pRemoteBuf) ) { return FALSE; } VirtualFreeEx(hProcess, pRemoteBuf, dwBufSize, MEM_RELEASE); CloseHandle(hProcess); return TRUE; } int DoInject(DWORD aPid, const WCHAR *aFullpath) { if (wcslen(aFullpath) <= 0) { return -1; } //判断dll是否存在 HANDLE hFile = CreateFile(aFullpath, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE,NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if(hFile != INVALID_HANDLE_VALUE) { DWORD dwsize = GetFileSize(hFile, NULL); CloseHandle(hFile); if (dwsize < 10) { return -2; } } else { return -3; } BOOL bSuc=SetPrivilege(SE_DEBUG_NAME, TRUE); bSuc=InjectDll((DWORD)aPid, aFullpath); if (bSuc) { return -4; } return 0; } // 真实注入的时候应该这样调用 DoInject(m_processId, L"E:\\src\\easyhook\\trunk\\Debug\\x86\\HookSvr.dll");
这样就能保证注入的钩子能正常工作了。