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)斷點