
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)斷點
雷石安全實驗室
看雪學苑
CNCERT國家工程研究中心
一顆小胡椒
0x00實驗室
黑白之道
合天網安實驗室
看雪學苑
看雪學苑
看雪學苑
GoUpSec
GoUpSec