DLL注入是指向运行中的其它进程强制插入特定的DLL文件。从技术细节来说,DLL注入命令其它进程自行调用LoadLibrary()API,加载用户指定的DLL文件。DLL注入与一般DLL加载的区别在于,加载的目标进程是其自身或其他进程。
从上图可以看到,test.dll已被强制插入进程(本来notepad并不会加载test.dll)。加载到某一进程中的test.dll与已经加载到某一进程中的dll一样,拥有访问notepad.exe进程内存的权限。
DLL被加载到进程后会自动运行DLLMain()函数,用户可以把想执行的代码放到DLLMain()函数,每当加载DLL时,添加的代码就会得到执行。利用这种特性可以修复程序Bug以及添加新功能 DllMain 函数是DLL模块的默认 入口点。当Windows加载DLL模块时调用这一函数。系统首先调用全局对象的 构造函数,然后调用 全局函数DLLMain。DLLMain 函数不仅在将DLL链接加载到进程时被调用,在DLL模块与进程分离时(以及其它时候)也被调用。 BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved){ switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: //添加想要执行的代码 //当dll被进程加载时DLLMain被调用 //printf(" process attach of dll"); break; case DLL_THREAD_ATTACH: //添加想要执行的代码 //当有线程被创建时,DLLMain被调用 printf(" thread attach of dll"); break; case DLL_THREAD_DETACH: //添加想要执行的代码 //当有线程结束时,DLLMain被调用 printf(" thread detach of dll"); break; case DLL_PROCESS_DETACH: //添加想要执行的代码 //当dll被进程卸载时,DLLMain被调用 printf(" process detach of dll"); break; } return TRUE;}
一、DLL注入示例
使用LoadLibrary()API加载某个DLL时,该DLL中的DLLMain()函数会被调用执行。DLL注入的工作原理就是从外部促使目标进程调用LoadLibrary()API,所以会强制调用执行DLL的DLLMain函数。并且被注入的DLL拥有目标进程内存的访问权限,用户可以随意操作。
二、实现DLL注入的方法
1、创建远程线程(CreatRemoteThread)
2、使用注册表(AppInit_DLLs值)
3、消息钩取(SetWindowsHookEx()API)
三、创建远程线程(CreatRemoteThread)
3.1、效果示例
运行process explorer(或者火绒剑,任务管理器)获取notepad.exe进程的pid。
可以看见process explorer.exe的pid为2788。
运行InjectDll.exe将myhack.dll注入到notepad.exe进程当中。可以看到dll文件已经被注入到里面。
要想在process explorer中看见注入的dll文件,需要依次选择view->Lower Pane view->DLLS选项。
进行注入时需要注意:
1.LoadLibraryA 和 LoadLibraryW 不同字符表示之前一直没有成功,没有使用L,但是使用了LoadLibraryW,导致加载dll失败,如果不使用L,请用LoadLibraryA 2.注册的时候注意DLL完整路径,除非被注入程序和dll在同一个文件夹InjectDll.exe 3480 D:\test\myhack.dll
同时可以看见文件内已经多了一个html文件,此文件是dll中所指定的文件。
3.2、分析示例源码
在DLLMmain()函数中可以看到,这个dll被加载(DLL_PROCESS_ATTACH)时,先输出一个字符串(" Injection!!!"),然后再创建线程调用函数(ThreadProc)。在ThreadProc函数中通过调用URLDownloadToFile来下载指定网站的index.html文件。前面提到过,向进程注入dll后会调用dll的DLLMain函数。所以当dll文件注入到exe进程后,会调用URLDownloadToFile下载文件。
//myhack.cpp#include "windows.h"#include "tchar.h" #pragma comment(lib, "urlmon.lib") #define DEF_URL (L"http://www.naver.com/index.html")#define DEF_FILE_NAME (L"index.html") HMODULE g_hMod = NULL; DWORD WINAPI ThreadProc(LPVOID lParam){ TCHAR szPath[_MAX_PATH] = {0,}; if( !GetModuleFileName( g_hMod, szPath, MAX_PATH ) ) return FALSE; TCHAR *p = _tcsrchr( szPath, '\\' ); if( !p ) return FALSE; //下载指定网站的index.html文件 _tcscpy_s(p+1, _MAX_PATH, DEF_FILE_NAME); URLDownloadToFile(NULL, DEF_URL, szPath, 0, NULL); return 0;} BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved){ HANDLE hThread = NULL; g_hMod = (HMODULE)hinstDLL; switch( fdwReason ) { case DLL_PROCESS_ATTACH : //加载时 OutputDebugString(L" Injection!!!"); //输出调试字符串 hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL); //创建线程 CloseHandle(hThread); break; } return TRUE;}
main函数的主要功能时检查输入程序的参数,然后调用InjectDLL函数。InjectDLL函数是用来进行dll注入的核心,其作用是使目标进程自行调用LoadLibrary这个api。
//InjectDLL.cpp#include "windows.h"#include "tchar.h" BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) { TOKEN_PRIVILEGES tp; HANDLE hToken; LUID luid; if( !OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) ) { _tprintf(L"OpenProcessToken error: %u", GetLastError()); return FALSE; } if( !LookupPrivilegeValue(NULL, // lookup privilege on local system lpszPrivilege, // privilege to lookup &luid) ) // receives LUID of privilege { _tprintf(L"LookupPrivilegeValue error: %u", GetLastError() ); 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) ) { _tprintf(L"AdjustTokenPrivileges error: %u", GetLastError() ); return FALSE; } if( GetLastError() == ERROR_NOT_ALL_ASSIGNED ) { _tprintf(L"The token does not have the specified privilege. "); return FALSE; } return TRUE;} BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath){ HANDLE hProcess = NULL, hThread = NULL; HMODULE hMod = NULL; LPVOID pRemoteBuf = NULL; DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR); LPTHREAD_START_ROUTINE pThreadProc; // #1. 使用 dwPID 获取目标进程(notepad.exe)句柄(PROCESS_ALL_ACCESS权限),然后就可以用 hProcess 控制进程. if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) ) { _tprintf(L"OpenProcess(%d) failed!!! [%d]", dwPID, GetLastError()); return FALSE; } // #2. 在目标进程(notepad.exe) 内存中分配 szDllName 大小的内存,返回 pRemoteBuf 作为该缓冲区的地址. pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE); // #3. 将 myhack.dll 路径写入刚刚分配的缓冲区. WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL); // #4. 获取 LoadLibraryW() API 地址,kernel32.dll在每个进程中的加载地址相同(这个特性就是我们要利用的). hMod = GetModuleHandle(L"kernel32.dll"); pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW"); // #5. 在 notepad.exe 中运行线程 hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL); //CreateRemoteThread()驱使进程调用LoadLibrary(),进而加载指定的DLL文件 WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); CloseHandle(hProcess); return TRUE;} int _tmain(int argc, TCHAR *argv[]){ if( argc != 3) { _tprintf(L"USAGE : %s ", argv[0]); return 1; } // change privilege if( !SetPrivilege(SE_DEBUG_NAME, TRUE) ) return 1; // inject dll if( InjectDll((DWORD)_tstol(argv[1]), argv[2]) ) _tprintf(L"InjectDll(\"%s\") success!!!", argv[2]); else _tprintf(L"InjectDll(\"%s\") failed!!!", argv[2]); return 0;}
下面来详细分析一下injectDll函数。
调用 OpenProcess这个API,借助程序运行时以参数形势传递过来的dwPID值,获取exe进程的句柄(PROCESS_ALL_ACCESS)。得到PROCESS_ALL_ACCESS之后,就可以用获取的句柄控制对应进程。
//获取目标的进程句柄hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)
需要把即将加载的dll文件的路径通知目标进程。因为任何内存空间都无法进行写入操作,所以先使用VirtualAllocEx() API在目标进程的内存空间中分配一块缓冲区,且指定的缓冲区大小为dll文件路径字符串的长度。
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE); 提示:VirtualAllocEx()函数的返回值为分配所得缓冲区的地址。该地址不是程序自身进程(Inject.exe)的内存地址,而是hProcess句柄所指目标进程(notepad.exe)的内存地址。
使用WriteProcessMemory将DLL路径字符串(xxx\xxx\xxx.dll)写入到分配所得缓冲区地址。WriteProcessMemory所写的内存空间也是hProcess句柄所指的目标进程的内存空间。
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL); //调用API //Windows操作系统提供了调试API,借助其可以访问其它进程的内存空间。 //例如:VirtualAllocEx()、 WriteProcessMemory等
调用LoadLibrary前需要先获取其地址。LoadLibraryW()是LoadLibrary()的Unicode字符串版本。
我们的目标明明是获取加载到 notepad.exe 进程的kernel32.dll的 LoadLibraryW的起始地址,但代码却用来获取加载到 InjectDll.exe进程的kernel32.dll的 LoadLibraryW的起始地址。如果加载到 notepad.exe 进程中的kernel32.dl的地址与加载到 InjectDll.exe 进程中的kernel32.dll的地址相同,那么上面的代码就不会有什么问题。但是如果kernell32.d在每个进程中加载的地址都不同,那么上面的代码就错了,执行时会发生引用错误。
根据 Os 类型、语言、版本不同,kerne32.dll加载的地址也不网。并且 Vista /7中应用了新的 ASLR 功能,每次启动时。系统 DLL 加载的地址都会改0。但是在系统运行期间它都会被映射( Mapping )到号进程的相同地址。
Windows 作系统中, DLL 首次进入内存称为“加载”( Loading ),以后其他进程需要使用相网 DLL 时不必再次加载,只要将加载过的 DLL 代码与资源映射一下即可,这种映射技术有利于提高肉存的使用效率。
像上面这样, OS 核心 DUL 会被加载到自身固有的地址, DLL 注人利用的就是 Windows Os 这一特性(该特性也可能会被恶意使用,成为 Windows 安全漏洞)。导人InjectDll.exe进程中LoadlibraryW地址与导人notepad.exe进程中的LoadLibraryWO地址是相同的。
//在windows中,kernel32.dll在每个进程中的加载地址是相同的。hMod = GetModuleHandle(L"kernel32.dll");pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");
在目标进程中运行远程线程,pThreadProc是exe进程内存中LoadlibraryW的地址,pRemoteBuf是exe进程内存中dll字符串的地址。
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
CreateRemoteThread用来在目标进程中执行其创建的线程,其函数原型如下:
除第一个参数 hProcess 外,其他参数与 CreateThread ()函数完全一样。 hProcess 参数是要执行线程的目标进程(或称“远程进程”、“宿主进程”)的句柄。 IpStartAddress 与 IpParameter 参数分别给出线程函数地址与线程参数地址。需要注意的是,这2个地址都应该在目标进程虚拟内存空间中(这样目标进程才能认识它们)。
HANDLE CreateRemoteThread( // 进程句柄 hProcess, // 线程安全描述字,指向SECURITY_ATTRIBUTES结构的指针 LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, // 线程栈大小,以字节表示 LPTHREAD_START_ROUTINE lpStartAddress, // 指向在远程进程中执行的函数地址 LPVOID lpParameter, // 传入参数 DWORD dwCreationFlags, // 创建线程的其它标志 LPDWORD lpThreadId // 线程身份标志,如果为NULL,则不返回); HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, __drv_aliasesMem LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId);
查看ThreadProc与LoadLibrary。两函数都有一个4字节的参数,并返回一个4字节的值。也就是说,二者形态结构完全一样灵感即源于此。调用 CreateRemoteThread 时,只要将 LoadLibrary函数的地址传递给第四个参数 IpStartAddress ,把要注人的 DLL 的路径字符串地址传递给第五个参数 IpParameter 即可(必须是目标进程的虚拟内存空间中的地址)。由于前面已经做好了一切准备,现在调用该函数使目标进程加载指定的 DLL 文件就行了。
CreateRemoteThread (函数最主要的功能就是驱使目标进程调用LoadLibrary函数,进而加载指定的 DLL 文件。
//调用 CreateRemoteThread 创建远程线程所需要的过程函数的标准形式为DWORD WINAPI ThreadProc( _In_ LPVOID lpParameter); //Win32编程加载DLL的API为:HMODULE WINAPI LoadLibrary( _In_ LPCTSTR lpFileName);
四、DLL卸载
DLL卸载(DLL Ejection):将强制插入进程的DLL弹出的技术。
原理:驱使目标进程调用FreeLibrary() API。
提示:FreeLibrary卸载dll的方法只适用于CreateRemoteThread注入
先注入dll到目标进程
注入成功后,卸载dll
分析一下EjectDll.exe
#include "windows.h"#include "tlhelp32.h"#include "tchar.h" #define DEF_PROC_NAME (L"notepad.exe")#define DEF_DLL_NAME (L"myhack.dll") DWORD FindProcessID(LPCTSTR szProcessName){ DWORD dwPID = 0xFFFFFFFF; HANDLE hSnapShot = INVALID_HANDLE_VALUE; PROCESSENTRY32 pe; // Get the snapshot of the system pe.dwSize = sizeof( PROCESSENTRY32 ); hSnapShot = CreateToolhelp32Snapshot( TH32CS_SNAPALL, NULL ); // find process Process32First(hSnapShot, &pe); do { if(!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile)) { dwPID = pe.th32ProcessID; break; } } while(Process32Next(hSnapShot, &pe)); CloseHandle(hSnapShot); return dwPID;} BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) { TOKEN_PRIVILEGES tp; HANDLE hToken; LUID luid; if( !OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) ) { _tprintf(L"OpenProcessToken error: %u", GetLastError()); return FALSE; } if( !LookupPrivilegeValue(NULL, // lookup privilege on local system lpszPrivilege, // privilege to lookup &luid) ) // receives LUID of privilege { _tprintf(L"LookupPrivilegeValue error: %u", GetLastError() ); 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) ) { _tprintf(L"AdjustTokenPrivileges error: %u", GetLastError() ); return FALSE; } if( GetLastError() == ERROR_NOT_ALL_ASSIGNED ) { _tprintf(L"The token does not have the specified privilege. "); return FALSE; } return TRUE;} BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName){ BOOL bMore = FALSE, bFound = FALSE; HANDLE hSnapshot, hProcess, hThread; HMODULE hModule = NULL; MODULEENTRY32 me = { sizeof(me) }; LPTHREAD_START_ROUTINE pThreadProc; // dwPID = notepad 进程 ID // 使用 TH32CS_SNAPMODULE 参数,获取加载到 notepad 进程的 DLL名称 hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID); bMore = Module32First(hSnapshot, &me); for( ; bMore ; bMore = Module32Next(hSnapshot, &me) ) { if( !_tcsicmp((LPCTSTR)me.szModule, szDllName) || !_tcsicmp((LPCTSTR)me.szExePath, szDllName) ) { bFound = TRUE; break; } } if( !bFound ) { CloseHandle(hSnapshot); return FALSE; } if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) ) //使用进程ID来获取目标进程的进程句柄 { _tprintf(L"OpenProcess(%d) failed!!! [%d]", dwPID, GetLastError()); return FALSE; } //获取加载到EjectDll.exe进程的kernel32.FreeLibrary地址(这个地址在所有进程中是一样的) hModule = GetModuleHandle(L"kernel32.dll"); pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary"); //在目标进程中运行线程,pThreadProc是FreeLibrary地址,me.modBaseAddr是要卸载的DLL的加载地址 hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, me.modBaseAddr, 0, NULL); WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); CloseHandle(hProcess); CloseHandle(hSnapshot); return TRUE;} int _tmain(int argc, TCHAR* argv[]){ DWORD dwPID = 0xFFFFFFFF; // find process dwPID = FindProcessID(DEF_PROC_NAME); if( dwPID == 0xFFFFFFFF ) { _tprintf(L"There is no <%s> process!", DEF_PROC_NAME); return 1; } _tprintf(L"PID of \"%s\" is %d", DEF_PROC_NAME, dwPID); // change privilege if( !SetPrivilege(SE_DEBUG_NAME, TRUE) ) return 1; // eject dll if( EjectDll(dwPID, DEF_DLL_NAME) ) _tprintf(L"EjectDll(%d, \"%s\") success!!!", dwPID, DEF_DLL_NAME); else _tprintf(L"EjectDll(%d, \"%s\") failed!!!", dwPID, DEF_DLL_NAME); return 0;}
获取进程中加载的DLL信息。
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);//使用 Create Toolhelp32Snapshot0 API 可以获取加载到进程的模块( DLL )信息。//将获取的 hSnapshot 句柄传递给Module32First(Module32NextO函数后,//即可设置与MODULEENTRY32结构体相关的模块信息.
获取目标进程的句柄。
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)//使用pid获取目标进程的进程句柄
获取FreeLibrary API地址
hModule = GetModuleHandle(L"kernel32.dll"); pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary"); //若要驱使 notepad 进程自己调用 FreeLibrary API ,需要先得到 FreeLibrary的地址。 //然而代码获取的不是加载到 notepad.exe 进程中的Kernel32!FreeLibrary 地址,// 而是加载到 EjectDl . exei 程中的Kernel32! FreeLibrary 地址。
在目标进程中运行线程
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, me.modBaseAddr, 0, NULL);//pThreadProc 参数是 FreeLibrary API 的地址,//me.modBaseAddr 参数是要卸载的 DLL 的加载地址。//将线程函数指定为 FreeLibrary 函数,并把 DLL 加载地址传递给线程参数,//就在目称世中成功调用了 FreeLibraryO ) API 。
五、AppInit_DLLss
计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows
填入dll文件路径
修改LoadAppInit_DLLs
重启系统使修改生效,使用火绒剑,process explorer查看是否注入成功。可以看见已经被注注入成功了。并且是注入了所有加载了user32.dll的进程。但是由于此dll的目标是notepad.exe进程,所以只要当运行这个exe之后才会有所动作。
myhack2.dll的源码比较简单。主要目的为加载进程为notepad.exe的程序,然后隐藏并连接指定网站。
// myhack2.cpp #include "windows.h"#include "tchar.h" #define DEF_CMD L"c:\\Program Files\\Internet Explorer\\iexplore.exe" #define DEF_ADDR L"http://www.naver.com"#define DEF_DST_PROC L"notepad.exe" BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved){ TCHAR szCmd[MAX_PATH] = {0,}; TCHAR szPath[MAX_PATH] = {0,}; TCHAR *p = NULL; STARTUPINFO si = {0,}; PROCESS_INFORMATION pi = {0,}; si.cb = sizeof(STARTUPINFO); si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE; switch( fdwReason ) { case DLL_PROCESS_ATTACH : if( !GetModuleFileName( NULL, szPath, MAX_PATH ) ) break; if( !(p = _tcsrchr(szPath, '\\')) ) break; if( _tcsicmp(p+1, DEF_DST_PROC) ) break; wsprintf(szCmd, L"%s %s", DEF_CMD, DEF_ADDR); if( !CreateProcess(NULL, (LPTSTR)(LPCTSTR)szCmd, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi) ) break; if( pi.hProcess != NULL ) CloseHandle(pi.hProcess); break; } return TRUE;}
六、SetWindowsHooKEX()
钩子过程(hook procedure)是系统调用的回调函数。
安装钩子时,钩子过程需要在DLL内部,该DLL的示例句柄(instance handle)即hMod。
线程ID如果为0,则钩子为“全局钩子”。
用SetWindowsHookEx()设置好钩子后,在某个进程中生成指定消息时,操作系统会将相关DLL文件强制注入相应进程。
HHOOK SetWindowsHookEx( int idHook, // 钩子的类型,即它处理的消息类型 HOOKPROC lpfn, //钩子子程的地址指针。如果dwThreadId参数为0 // 或是一个由别的进程创建的线程的标识, // lpfn必须指向DLL中的钩子子程。 // 除此以外,lpfn可以指向当前进程的一段钩子子程代码。 //钩子函数的入口地址,当钩子钩到任何消息后便调用这个函数。 HINSTANCE hMod, //应用程序实例的句柄。标识包含lpfn所指的子程的DLL。 // 如果dwThreadId 标识当前进程创建的一个线程, // 而且子程代码位于当前进程,hMod必须为NULL。 // 可以很简单的设定其为本应用程序的实例句柄。 DWORD dwThreadId //与安装的钩子子程相关联的线程的标识符。 // 如果为0,钩子子程与所有的线程关联,即为全局钩子。 );
6.1、效果示意
首先运行HookMain.exe
再运行notepad.exe,之后再使用查看,发现dll文件已经被注入。
输入q,拆除钩子。拆除后,dll文件消失,可以正常输入。
6.2、分析源码
HookMain.exe主要过程为:首先加载KeyHook.dll文件,然后调用HookStart()函数开始钩取,用户输入"q"时,调用HookStop()函数终止钩取。
//HookMain.exe #include "stdio.h"#include "conio.h"#include "windows.h" #define DEF_DLL_NAME "KeyHook.dll"#define DEF_HOOKSTART "HookStart"#define DEF_HOOKSTOP "HookStop" typedef void (*PFN_HOOKSTART)();typedef void (*PFN_HOOKSTOP)(); void main(){ HMODULE hDll = NULL; PFN_HOOKSTART HookStart = NULL; PFN_HOOKSTOP HookStop = NULL; char ch = 0; // 加载KeyHook.dll hDll = LoadLibraryA(DEF_DLL_NAME); if( hDll == NULL ) { printf("LoadLibrary(%s) failed!!! [%d]", DEF_DLL_NAME, GetLastError()); return; } // 获取导出函数地址 HookStart = (PFN_HOOKSTART)GetProcAddress(hDll, DEF_HOOKSTART); HookStop = (PFN_HOOKSTOP)GetProcAddress(hDll, DEF_HOOKSTOP); // 开始 HookStart(); // “q”退出 printf("press 'q' to quit!"); while( _getch() != 'q' ) ; // 结束 HookStop(); // 卸载 KeyHook.dll FreeLibrary(hDll);}
KeyHook.dll在调用HookStart()时,SetWindowsHookEx()函数就会将KeyboardProc()添加到键盘钩链。
安装好键盘“钩子”后,无论哪个进程,只要发生键盘输人事件, OS 就会强制将 KeyHook . dl 人相应进程。加载了 KeyHook.dll 的进程中,发生键盘事件时会首先调用执行KeyHookKeyboardProc 。
KeyboardProc 函数中发生键盘输入事件时,就会比较当前进程的名称与“ notepad.exe ”字符串,若相同,则返回1,终止 KeyboardProc()函数,这意味着截获且删除消息。这样,键盘消息就不会传递到 notepad.exe 程序的消息队列。 安装好键盘“钩子”后,无论哪个进程,只要发生键盘输人事件, OS 就会强制将 KeyHook . dl 人相应进程。加载了 KcyHook . dll 的进程中,发生键盘事件时会首先调用执行 KeyHfookKeyboardProc 。
KeyboardProd 函数中发生键盘输人事件时,就会比较当前进程的名称与“ notepad . exe ”字符串,若相同,则返网1,终止 KcyboardProc (函数,这意味着截获且删除消息。这样,键盘消息就环会传透到 notapadexe 程序的消息队列。notepad.exe 未能接收到任何键盘消息,故无法输出。
除此之外(即当前进程名称非“ notepad . exe ”时),执行 return CallNextHookEx ( g_hHook , nCode , wParam, lParam),消息会被传递到另一个应用程序或钩链的另一个“钩子”函数。
// KeyHook.dll #include "stdio.h"#include "windows.h" #define DEF_PROCESS_NAME "notepad.exe" HINSTANCE g_hInstance = NULL;HHOOK g_hHook = NULL;HWND g_hWnd = NULL; BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved){ switch( dwReason ) { case DLL_PROCESS_ATTACH: g_hInstance = hinstDLL; break; case DLL_PROCESS_DETACH: break; } return TRUE;} LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam){ char szPath[MAX_PATH] = {0,}; char *p = NULL; if( nCode >= 0 ) { // bit 31 : 0 => press, 1 => release if( !(lParam & 0x80000000) ) //释放键盘按键时 { GetModuleFileNameA(NULL, szPath, MAX_PATH); p = strrchr(szPath, '\\'); // 比较当前进程名称,如果是 notepad.exe 则消息不会传给应用程序 if( !_stricmp(p + 1, DEF_PROCESS_NAME) ) return 1; } } //反之,调用 CallNextHookEx() 消息传给应用程序 return CallNextHookEx(g_hHook, nCode, wParam, lParam);} #ifdef __cplusplusextern "C" {#endif __declspec(dllexport) void HookStart(){ g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0); } __declspec(dllexport) void HookStop(){ if( g_hHook ) { UnhookWindowsHookEx(g_hHook); g_hHook = NULL; } }#ifdef __cplusplus}#endif
6.3、调试方法
先调试HookMain().exe,OD打开该程序。
查找核心代码
1、逐行跟踪调试2、检索相关API3、搜索相关字符串
由于之前打开过此程序,所以直接搜索"press 'q' to quit!"。
点击后跳转到该位置,在401000地址处下断点。然后运行到此处。
先在401006地址处调用LoadLibrary,然后由40104B地址处的CALL指令调用KeyHook.HookStart()函数。
F7跟踪进入。
图中的代码是被加载到HookMain.exe 进程中的 KeyHook.dll的HookStart()函数。
在100010EF地址处可以看到 CALL SetWindowsHookExW() 指令,其上方10010E8与100010ED地址处的2条 PUSH 指令用于把 SetWindowsHookExW() API 的第1、2两个参数压入栈。
Set WindowsHookExW() API 的第一个参数( idHook)值为WH_KEYBOARD(2),第二个参数( Ipfn )值为10001020,该值即是钩子过程的地址。后面调试 KeyHook . dlI 时再仔细看该地址。HookMain.exe的main()函数(401000)的其余代码接收到用户输人的“ q ”命令后终止钩取。
调试KeyHook.dll
使用OD打开notepad.exe,F9运行
在OD中设置如下的选项
运行HookMain.exe
随后在notepad中随意输入一个字母,此时dll被加载到10000000处
根据系统环境不同,有时不会先显示 KeyHook.dll,而是先加載其他 DLL 库。
此时按(F9)运行键,直到KeyHook.dll加载完成。
有些系统无法正常运行该功能,此时使用OllyDbg2.0即可保证运行顺畅。
点击dll跳转到KeyHook.dll的EP地址处。并且由于之前在调试HookMain.exe时候已经知道钩子的地址是10001020,所以直接在此处下断点。
下好断点好,再重新运行一下程序。然后在记事本当中尝试输入数据,记事本无接收数据的意向,并且OD已经跳转到断点处。
1、OD运行notepad.exe2、开启Break on new module(中断于新模块)选项3、运行HookMain.exe4、进行键盘输入,触发键盘消息事件5、dll被注入6、OD中设置钩子进程(KeyboardProc)断点