小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

HOOK 幾種實(shí)現(xiàn)方式區(qū)別

 lichwoo 2024-12-27 發(fā)布于北京

Hook簡介

微軟的MSDN中,對(duì)Hook的解釋為:
A hook is a point in the system message-handling mechanism where an application can install a subroutine to monitor the message traffic in the system and process certain types of messages before they reach the target window procedure.

微軟只是簡單的將Hook解釋為一種過濾(或叫掛鉤)消息的技術(shù)。

我們這里講解的Hook,簡單解釋為:掛鉤,掛鉤一切事物。包含微軟的解釋。

掛鉤的事物通常指的是函數(shù)

Hook 目的

過濾一些關(guān)鍵函數(shù)調(diào)用,在函數(shù)執(zhí)行前,先執(zhí)行自己的掛鉤函數(shù)。達(dá)到監(jiān)控函數(shù)調(diào)用,改變函數(shù)功能的目的。

Hook 分類

  • 系統(tǒng)消息Hook(微軟官方提供的方法 Ring3)
    • SetWindowsHookEx
    • UnhookWindowsHookEx
    • CallNextHookEx

 

  • API Hook(非官方方法 Ring0/Ring3)
    • 使用匯編代碼編替換函數(shù)開始的匯編代碼,使其跳轉(zhuǎn)到我們的Hook函數(shù)中,非官方叫法:Inline hook
    • 使用Hook函數(shù)地址替換調(diào)用函數(shù)地址(IAT hook/SSDT hook … …)

系統(tǒng)消息 Hook

類型:

WH_GETMESSAGE    當(dāng)系統(tǒng)調(diào)用GetMessage和PeekMessage時(shí),調(diào)用Hook函數(shù)

WH_KEYBOARD_LL     當(dāng)系統(tǒng)發(fā)送鍵盤消息到線程輸入隊(duì)列前,先調(diào)用Hook函數(shù)

WH_MOUSE_LL         當(dāng)系統(tǒng)發(fā)送鼠標(biāo)消息到線程輸入隊(duì)列前,先調(diào)用Hook函數(shù)

… …

 

HHOOK WINAPI SetWindowsHookEx(

int idHook,            // 類型

HOOKPROC lpfn,        // 回調(diào)函數(shù)

HINSTANCE hMod,    // 回調(diào)函數(shù)所在的DLL的Handle(可能在調(diào)用進(jìn)程也可能不在)

DWORD dwThreadId    // 針對(duì)哪兒個(gè)線程做Hook

);

 

此Hook可以以線程為單位進(jìn)行篩選。每個(gè)GUI線程有一個(gè)消息隊(duì)列,用來接受消息,既然是消息Hook,那就應(yīng)該是針對(duì)某個(gè)線程的接收到的消息做Hook。

例如:實(shí)現(xiàn)的防鍵盤勾取,可以使用這種技術(shù)。安裝了一個(gè)WH_KEYBOARD_LL鉤子,在鍵盤按下,發(fā)往任意的線程消息隊(duì)列前,會(huì)先調(diào)用我們的鍵盤鉤子,鉤子里面判斷是不是我們的輸入框的窗口消息,如果不是,忽略。如果是,做加密處理并保存,改變發(fā)送的字符(如改為"*"),發(fā)送至窗口過程中。如果第三方調(diào)用GetWindowText,由于文本框中的都是"*",所以無法獲取出真實(shí)字符。

但這種鉤子誰也可以安裝,Windows將所有程序安裝的鉤子組成了一個(gè)鉤子鏈。如果第三方也安裝了一個(gè)鉤子,還是在我們安裝完之后安裝的,那么它將會(huì)截獲到真實(shí)字符。所以,所以可以進(jìn)一步加強(qiáng)處理,增加一個(gè)定時(shí)器,不停的安裝卸載多個(gè)鉤子。這樣,即便第三方也在不停的安裝鉤子,想截取到完整的PIN,可能性變的很小。但如果這個(gè)惡意程序駐留在用戶系統(tǒng)中很多天,通過每次的統(tǒng)計(jì),可能能得到完整的PIN。

API Hook - Inline Hook

工作方式如下:

  1. 調(diào)用GetProcAddress對(duì)內(nèi)存中要攔截的函數(shù)進(jìn)行定位(如Kernel32.dll的ExitProcess),得到它的內(nèi)存地址。
  2. 把這個(gè)函數(shù)起始的幾個(gè)字節(jié)保存到我們的內(nèi)存中。
  3. 用CPU的JUMP匯編指令來覆蓋這個(gè)函數(shù)的起始幾個(gè)字節(jié),這條JUMP指令用來跳轉(zhuǎn)到我們的替換函數(shù)的內(nèi)存地址。我們替換的函數(shù)需要和原函數(shù)具有完全相同的聲明:參數(shù)相同,返回值相同,調(diào)用約定相同。
  4. 當(dāng)線程調(diào)用被攔截的函數(shù)時(shí),跳轉(zhuǎn)指令實(shí)際上會(huì)跳轉(zhuǎn)到我們的替代函數(shù),這是,我們可以執(zhí)行相應(yīng)的功能代碼。
  5. 為了撤銷對(duì)函數(shù)的攔截,我們必須把步驟2中保存的字節(jié)恢復(fù)回被攔截的函數(shù)中。
  6. 我們調(diào)用被攔截函數(shù)(現(xiàn)在已經(jīng)不再攔截),讓函數(shù)執(zhí)行它的正常處理。
  7. 當(dāng)原來函數(shù)返回時(shí),我們?cè)俅螆?zhí)行第2,第3步,這樣我們的替代函數(shù)將來還會(huì)被調(diào)用到。

注意事項(xiàng):

  • JUMP指令對(duì)CPU(AMD,Intel)有依賴,在X86,X64下,更會(huì)有不同的表現(xiàn)。
  • 由于替換匯編代碼的過程,不是原子操作的,很有可能在其它線程運(yùn)行到此函數(shù)的入口的時(shí)候進(jìn)行了替換,導(dǎo)致指令異常,程序崩潰。

解決多線程的辦法(分析來自Mhook庫):

  • 替換的過程首先上互斥鎖(如EnterCriticalSection),這把鎖只對(duì)替換的過程互斥,并無法避免其它線程正在調(diào)用要替換的函數(shù)。
  • 第二步做的就是掛起除本線程外的其它線程,并同時(shí)確保其它線程的執(zhí)行點(diǎn)(IP)不在我們將以替換的區(qū)域中。(參加Mhook的: SuspendOtherThreads函數(shù))

下面是摘自Mhook的一段代碼,它會(huì)掛起除了Hook線程以外的其它線程,并且確保其他線程的執(zhí)行點(diǎn)(IP)不在要Hook的區(qū)域內(nèi)。

//=========================================================================
// Internal function:
//
// Suspend all threads in this process while trying to make sure that their
// instruction pointer is not in the given range.
//=========================================================================
static BOOL SuspendOtherThreads(PBYTE pbCode, DWORD cbBytes) {
    BOOL bRet = FALSE;
    // make sure we're the most important thread in the process
    INT nOriginalPriority = GetThreadPriority(GetCurrentThread());
    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
    // get a view of the threads in the system
    HANDLE hSnap = fnCreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, GetCurrentProcessId());
    if (GOOD_HANDLE(hSnap)) {
        THREADENTRY32 te;
        te.dwSize = sizeof(te);
        // count threads in this process (except for ourselves)
        DWORD nThreadsInProcess = 0;
        if (fnThread32First(hSnap, &te)) {
            do {
                if (te.th32OwnerProcessID == GetCurrentProcessId()) {
                    if (te.th32ThreadID != GetCurrentThreadId()) {
                        nThreadsInProcess++;
                    }
                }
                te.dwSize = sizeof(te);
            } while(fnThread32Next(hSnap, &te));
        }
        ODPRINTF((L"mhooks: SuspendOtherThreads: counted %d other threads", nThreadsInProcess));
        if (nThreadsInProcess) {
            // alloc buffer for the handles we really suspended
            g_hThreadHandles = (HANDLE*)malloc(nThreadsInProcess*sizeof(HANDLE));
            if (g_hThreadHandles) {
                ZeroMemory(g_hThreadHandles, nThreadsInProcess*sizeof(HANDLE));
                DWORD nCurrentThread = 0;
                BOOL bFailed = FALSE;
                te.dwSize = sizeof(te);
                // go through every thread
                if (fnThread32First(hSnap, &te)) {
                    do {
                        if (te.th32OwnerProcessID == GetCurrentProcessId()) {
                            if (te.th32ThreadID != GetCurrentThreadId()) {
                                // attempt to suspend it
                                g_hThreadHandles[nCurrentThread] = SuspendOneThread(te.th32ThreadID, pbCode, cbBytes);
                                if (GOOD_HANDLE(g_hThreadHandles[nCurrentThread])) {
                                    ODPRINTF((L"mhooks: SuspendOtherThreads: successfully suspended %d", te.th32ThreadID));
                                    nCurrentThread++;
                                } else {
                                    ODPRINTF((L"mhooks: SuspendOtherThreads: error while suspending thread %d: %d", te.th32ThreadID, gle()));
                                    // TODO: this might not be the wisest choice
                                    // but we can choose to ignore failures on
                                    // thread suspension. It's pretty unlikely that
                                    // we'll fail - and even if we do, the chances
                                    // of a thread's IP being in the wrong place
                                    // is pretty small.
                                    // bFailed = TRUE;
                                }
                            }
                        }
                        te.dwSize = sizeof(te);
                    } while(fnThread32Next(hSnap, &te) && !bFailed);
                }
                g_nThreadHandles = nCurrentThread;
                bRet = !bFailed;
            }
        }
        CloseHandle(hSnap);
        //TODO: we might want to have another pass to make sure all threads
        // in the current process (including those that might have been
        // created since we took the original snapshot) have been
        // suspended.
    } else {
        ODPRINTF((L"mhooks: SuspendOtherThreads: can't CreateToolhelp32Snapshot: %d", gle()));
    }
    SetThreadPriority(GetCurrentThread(), nOriginalPriority);
    if (!bRet) {
        ODPRINTF((L"mhooks: SuspendOtherThreads: Had a problem (or not running multithreaded), resuming all threads."));
        ResumeOtherThreads();
    }
    return bRet;
}
 
//=========================================================================
// Internal function:
//
// Suspend a given thread and try to make sure that its instruction
// pointer is not in the given range.
//=========================================================================
static HANDLE SuspendOneThread(DWORD dwThreadId, PBYTE pbCode, DWORD cbBytes) {
    // open the thread
    HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, dwThreadId);
    if (GOOD_HANDLE(hThread)) {
        // attempt suspension
        DWORD dwSuspendCount = SuspendThread(hThread);
        if (dwSuspendCount != -1) {
            // see where the IP is
            CONTEXT ctx;
            ctx.ContextFlags = CONTEXT_CONTROL;
            int nTries = 0;
            while (GetThreadContext(hThread, &ctx)) {
#ifdef _M_IX86
                PBYTE pIp = (PBYTE)(DWORD_PTR)ctx.Eip;
#elif defined _M_X64
                PBYTE pIp = (PBYTE)(DWORD_PTR)ctx.Rip;
#endif
                if (pIp >= pbCode && pIp < (pbCode + cbBytes)) {
                    if (nTries < 3) {
                        // oops - we should try to get the instruction pointer out of here.
                        ODPRINTF((L"mhooks: SuspendOneThread: suspended thread %d - IP is at %p - IS COLLIDING WITH CODE", dwThreadId, pIp));
                        ResumeThread(hThread);
                        Sleep(100);
                        SuspendThread(hThread);
                        nTries++;
                    } else {
                        // we gave it all we could. (this will probably never
                        // happen - unless the thread has already been suspended
                        // to begin with)
                        ODPRINTF((L"mhooks: SuspendOneThread: suspended thread %d - IP is at %p - IS COLLIDING WITH CODE - CAN'T FIX", dwThreadId, pIp));
                        ResumeThread(hThread);
                        CloseHandle(hThread);
                        hThread = NULL;
                        break;
                    }
                } else {
                    // success, the IP is not conflicting
                    ODPRINTF((L"mhooks: SuspendOneThread: Successfully suspended thread %d - IP is at %p", dwThreadId, pIp));
                    break;
                }
            }
        } else {
            // couldn't suspend
            CloseHandle(hThread);
            hThread = NULL;
        }
    }
    return hThread;
}

Inline Hook技術(shù)復(fù)雜,對(duì)CPU有依賴,需要編寫匯編代碼,在搶占式,多線程系統(tǒng)中,很容易出現(xiàn)問題。但目前有比較穩(wěn)定的Hook庫可以使用,也是不錯(cuò)的選擇之一。

API Hook – SEH Hook

這里簡單介紹一下,SEH是windows的結(jié)構(gòu)化異常處理,通過安裝一個(gè)結(jié)構(gòu)化異常處理函數(shù),當(dāng)在保護(hù)的執(zhí)行區(qū)域內(nèi)發(fā)生異常時(shí),會(huì)跳轉(zhuǎn)到此異常函數(shù)。SEH Hook屬于比較另類的Hook方式,它在函數(shù)開頭插入異常代碼,如(INT 3),當(dāng)執(zhí)行到此函數(shù)時(shí),由于觸發(fā)異常,則會(huì)跳到我們的處理函數(shù)中。

API Hook - IAT Hook

IAT - Import Address Table (輸入地址表)

簡單的說,此表格由一連串的函數(shù)地址組成,這些函數(shù)地址從其它模塊中導(dǎo)入的函數(shù)地址。當(dāng)Windows加載器加載一個(gè)PE可執(zhí)行文件時(shí),會(huì)將真正需要的函數(shù)的虛擬地址填入此表格。

 

輸入函數(shù)是如何被調(diào)用的

通常,我們編寫一個(gè)程序(調(diào)用者),調(diào)用一個(gè)輸入函數(shù)時(shí),此函數(shù)并不在當(dāng)前程序中。我們只需要包含相應(yīng)的頭文件,然后鏈接正確的lib庫(靜態(tài)lib 和 動(dòng)態(tài)DLL對(duì)應(yīng)的lib),這些函數(shù)的實(shí)際代碼存在于外部的一個(gè)DLL中。調(diào)用者只保留函數(shù)的相關(guān)信息(函數(shù)名,DLL文件名等)。由于一個(gè)PE文件沒有被加載到內(nèi)存中,編譯完成的PE文件是無法確定輸入函數(shù)的具體地址的。

 

下面通過一個(gè)例子來說明輸入函數(shù)的地址是如何確定的。

現(xiàn)在,我調(diào)用一個(gè)Windows API函數(shù):Sleep,原型如下:

extern "C" __declspec(dllimport) void __stdcall Sleep(

     unsigned long dwMilliseconds

);

 

代碼中調(diào)用如下:

Sleep(200);

 

VC編譯器會(huì)將此函數(shù)匯編如下:

push     0C8h

call dword ptr [00407000]

 

由于在編譯階段,編譯器是無法確定Sleep在真正運(yùn)行時(shí)候的地址的。所以,編譯器在PE文件中分配一塊導(dǎo)入地址表,call 的只是導(dǎo)入地址的地址,而不是真正的地址。此示例中, 00407000是地址,它指向?qū)氲刂繁淼哪硞€(gè)區(qū)域,指向的內(nèi)容在PE被Windows加載器加載的時(shí)候,根據(jù)DLL實(shí)際加載的地址,找到真正的Sleep地址,將其替換為Sleep的真實(shí)地址。此方法類似于C++的多態(tài)。(父類的指針在運(yùn)行時(shí)可以指向不同類型的子類)。這樣,call dword ptr [00407000],從00407000取出來的地址就是Sleep的地址。如下圖所示。

IAT Hook就是去替換取出來的這個(gè)Sleep地址,將00407000地址指向的內(nèi)存替換為我們的MySleep。當(dāng)再次執(zhí)行到call dword ptr [00407000],此00407000指向的內(nèi)存保存的已經(jīng)是MySleep地址了。

IAT Hook的優(yōu)點(diǎn)在于不依賴CPU,容易實(shí)現(xiàn)。很難出現(xiàn)程序崩潰的問題。

 

如何找到IAT并定位正確的地址?

需要掌握PE文件的結(jié)構(gòu)。(可以參考看雪論壇出版的加密與解密(第三版))

下面的代碼片段,引自《Windows核心編程(第5版)》 示例代碼 22-LastMsgBoxInfo :

 

void CAPIHook::ReplaceIATEntryInOneMod(PCSTR pszCalleeModName,
PROC pfnCurrent, PROC pfnNew, HMODULE hmodCaller) {
 
// Get the address of the module's import section
ULONG ulSize;
 
// An exception was triggered by Explorer (when browsing the content of
// a folder) into imagehlp.dll. It looks like one module was unloaded...
// Maybe some threading problem: the list of modules from Toolhelp might
// not be accurate if FreeLibrary is called during the enumeration.
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = NULL;
__try {
pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR) ImageDirectoryEntryToData(
hmodCaller, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &ulSize);
}
__except (InvalidReadExceptionFilter(GetExceptionInformation())) {
// Nothing to do in here, thread continues to run normally
// with NULL for pImportDesc
}
 
if (pImportDesc == NULL)
return; // This module has no import section or is no longer loaded
 
 
// Find the import descriptor containing references to callee's functions
for (; pImportDesc->Name; pImportDesc++) {
PSTR pszModName = (PSTR) ((PBYTE) hmodCaller + pImportDesc->Name);
if (lstrcmpiA(pszModName, pszCalleeModName) == 0) {
 
// Get caller's import address table (IAT) for the callee's functions
PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)
((PBYTE) hmodCaller + pImportDesc->FirstThunk);
 
// Replace current function address with new function address
for (; pThunk->u1.Function; pThunk++) {
 
// Get the address of the function address
PROC* ppfn = (PROC*) &pThunk->u1.Function;
 
// Is this the function we're looking for?
BOOL bFound = (*ppfn == pfnCurrent);
if (bFound) {
if (!WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew,
sizeof(pfnNew), NULL) && (ERROR_NOACCESS == GetLastError())) {
DWORD dwOldProtect;
if (VirtualProtect(ppfn, sizeof(pfnNew), PAGE_WRITECOPY,
&dwOldProtect)) {
 
WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew,
sizeof(pfnNew), NULL);
VirtualProtect(ppfn, sizeof(pfnNew), dwOldProtect,
&dwOldProtect);
}
}
return; // We did it, get out
}
}
} // Each import section is parsed until the right entry is found and patched
}
}

API Hook - SSDT Hook (Ring0)

SSDT (System Service Dispatch Table) 系統(tǒng)服務(wù)調(diào)度表

此表保存了系統(tǒng)中所有的系統(tǒng)服務(wù)函數(shù)地址,通過改變此表中的地址,達(dá)到Hook某個(gè)系統(tǒng)函數(shù)的目的

為何替換SSDT表中的地址,首先需要了解,Windows內(nèi)核是如何為應(yīng)用程序服務(wù)的?

為了保證每個(gè)進(jìn)程的安全, Windows為不同的進(jìn)程分配了獨(dú)立的進(jìn)程空間,即虛擬內(nèi)存空間。一個(gè)進(jìn)程的虛擬地址是無法指到另一個(gè)進(jìn)程中的數(shù)據(jù)的。對(duì)應(yīng)32位x86系統(tǒng),每個(gè)進(jìn)程的空間是4G,0x00000000-0xFFFFFFFF。為了高效的調(diào)用系統(tǒng)服務(wù),Windows把操作系統(tǒng)的代碼和數(shù)據(jù)映射到所有進(jìn)程的空間中,因此4G劃分為低2G的用戶層和高2G的內(nèi)核層空間。如圖所示:

Windows定義了兩種訪問模式(access mode)

  • 用戶模式(user mode)    (ring3)
  • 內(nèi)核模式(kernel mode)(ring0)

    應(yīng)用程序運(yùn)行在用戶模式下,操作系統(tǒng)運(yùn)行在內(nèi)核模式下。內(nèi)核模式對(duì)應(yīng)CPU的高權(quán)限級(jí)別,內(nèi)核模式下可以訪問系統(tǒng)的所有資源,擁有執(zhí)行所有指令的權(quán)限,用戶模式對(duì)應(yīng)CPU較低的權(quán)限,只可以訪問系統(tǒng)允許的其訪問的內(nèi)存空間和資源,并且沒有權(quán)限運(yùn)行一些特殊指令。

    用戶模式下的代碼不可以直接訪問內(nèi)核模式下的代碼和數(shù)據(jù),不能直接通過call指令調(diào)用內(nèi)核模式下的任何函數(shù)。如果嘗試,系統(tǒng)會(huì)產(chǎn)生保護(hù)性錯(cuò)誤,程序直接終止。

    間接訪問:用戶程序通過調(diào)用系統(tǒng)服務(wù)來間接訪問系統(tǒng)空間的數(shù)據(jù)或間接的來調(diào)用系統(tǒng)空間中的代碼。

下面介紹用戶程序調(diào)用API:ReadFile,如何到達(dá)內(nèi)核層:

對(duì)應(yīng)的匯編代碼如下:

eax中保存的就是SSDT的索引。

在KiSystemServcie中,通過eax,索引SSDT中保存的地址,并調(diào)用之。

所以,如果我們知道SSDT中的每個(gè)地址對(duì)應(yīng)的服務(wù)函數(shù),并且知道此服務(wù)函數(shù)對(duì)應(yīng)到用戶層的API。那么我們可以編寫一個(gè)驅(qū)動(dòng),加載到內(nèi)核空間中,將驅(qū)動(dòng)中的我們自己的函數(shù)地址替換到SSDT中,應(yīng)用層調(diào)用API后,最后會(huì)拐到我們自己的函數(shù)中,達(dá)到Hook的目的。

系統(tǒng)有兩張SSDT表,一個(gè)是導(dǎo)出的KeServiceDescriptorTable(ntoskrnl.exe),一個(gè)是未導(dǎo)出的KeServiceDescriptorTableShadow(ntoskrnl.exe,win32k.sys)

由于內(nèi)核已經(jīng)導(dǎo)出KeServiceDescriptorTable全局變量,所以編寫程序的時(shí)候,不需要定位KeServiceDescriptorTable。KeServiceDescriptorTableShadow未導(dǎo)出,所以需要使用一些非公開的方法來定位此地址,通常都是采用硬性編碼的,沒有系統(tǒng)適應(yīng)性。

這兩張表見圖:

在我在做的反截屏驅(qū)動(dòng)中,就是查找到 KeServiceDescriptorTableShadow地址,替換NtGdiBitBlt(對(duì)應(yīng)的Win32 API為 BitBlt)等內(nèi)核函數(shù)地址,進(jìn)行功能過濾。

SSDT的缺點(diǎn)在于在64位的系統(tǒng)下,基本無法工作,除非你能跨過微軟的安全防護(hù)。目前來看,還沒有人破解win8.1. 

Hook其他進(jìn)程

下面的例子是否有問題:

  • 示例1:A進(jìn)程中有個(gè)MySleep函數(shù),A進(jìn)程通過IAT Hook的方式,將A進(jìn)程中的IAT中對(duì)應(yīng)的Sleep地址進(jìn)行了替換,替換為MySleep函數(shù)地址, Hook是否能夠成功?
  • 示例2:A進(jìn)程中有個(gè)MySleep函數(shù),A進(jìn)程通過IAT Hook的方式,將B進(jìn)程中的IAT中對(duì)應(yīng)的Sleep地址進(jìn)行了替換,替換為MySleep函數(shù)地址,Hook是否能夠成功?

我們知道,Windows下各個(gè)進(jìn)程的空間是相互隔離的。A進(jìn)程雖然能夠替換B進(jìn)程中的IAT地址(通過VirtualProtect,WriteProcessMemory可以實(shí)現(xiàn)),但是替換的MySleep的地址存在于A進(jìn)程中,并不在B進(jìn)程中,B進(jìn)程直接調(diào)用,基本的可能就是崩潰了,如果這個(gè)地址正好是B進(jìn)程的一個(gè)有效函數(shù)地址,還調(diào)用成功了,你會(huì)更加摸不著頭腦。

 

自己Hook自己通常來說,沒有太大意義,那么如何Hook其它進(jìn)程?

  • 如果使用的是SSDT hook,由于是在內(nèi)核層,對(duì)所有的進(jìn)程都是有效的。
  • 如果是在應(yīng)用層,如何Hook其它進(jìn)程?我們可以將我們的函數(shù)放置到一個(gè)DLL中,將此DLL注入到其他進(jìn)程中,這樣其他進(jìn)程調(diào)用DLL中的函數(shù),將不會(huì)訪問失敗。

 

Windows提供了一種DLL注入的技術(shù),注入的方式有:

  • 使用注冊(cè)表來注入
  • 使用window 系統(tǒng)消息掛鉤來注入(參見 Windows核心編程 22-DIPS示例)
  • 使用遠(yuǎn)程線程來注入(參見 Windows核心編程 22-InjLib示例)…

這幾種注入技術(shù)都在《 Windows核心編程 》詳細(xì)講解到。

使用注冊(cè)表來注入

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\ 中:

有一個(gè)鍵:AppInit_DLLs,可以包含一組DLL(逗號(hào)或空格分隔),將我們的DLL路徑寫入此鍵值中。

創(chuàng)建一個(gè)名為LoadAppInit_DLLs,將其值設(shè)置為1.

當(dāng)user32.dll被映射到一個(gè)新的進(jìn)程時(shí),會(huì)收到DLL_PROCESS_ATTACH通知。當(dāng)user32.dll對(duì)它進(jìn)行處理時(shí),會(huì)取得上述注冊(cè)表鍵值,并調(diào)用LoadLibrary載入這個(gè)鍵值存儲(chǔ)的所有DLL。

 

缺點(diǎn):

基于CUI的應(yīng)用程序沒有用到user32.dll,因此無法加載DLL。

我可能只想注入某些應(yīng)用中,映射的越多,崩潰的可能性越大。

使用window 系統(tǒng)消息掛鉤來注入

為了能讓系統(tǒng)消息掛鉤正常運(yùn)行,Microsoft被迫設(shè)計(jì)出一種機(jī)制,讓我們可以將DLL注入到另一進(jìn)程空間中(想象一下:調(diào)用SetWindowsHookEx安裝一個(gè)WH_GETMESSAGE鉤子,如果讓其他進(jìn)程運(yùn)行SetWindowsHookEx設(shè)置的函數(shù)?)

SetWindowsHookEx(WH_GETMESSAGE,GetMsgProc,hInstDll,0);

GetMsgProc在調(diào)用此函數(shù)的進(jìn)程空間中,假設(shè)為A進(jìn)程。第三個(gè)參數(shù)為hInstDll,為當(dāng)前進(jìn)程空間中的一個(gè)DLL的句柄,此DLL包含了GetMsgProc函數(shù),最后一個(gè)參數(shù)表示要給哪兒個(gè)線程安裝,0表示給系統(tǒng)中所有的GUI線程安裝掛鉤。

接下來會(huì)發(fā)生什么:

  1. B進(jìn)程的一個(gè)線程準(zhǔn)備向一個(gè)窗口發(fā)送一條消息。
  2. 系統(tǒng)檢測到已經(jīng)安裝了一個(gè)WH_GETMESSAGE鉤子。
  3. 系統(tǒng)檢測GetMsgProc所在的DLL是否已經(jīng)被映射到進(jìn)程B地址空間中。
  4. 如果尚未映射,系統(tǒng)會(huì)強(qiáng)制調(diào)用LoadLibrary強(qiáng)制將DLL映射到B進(jìn)程地址空間中,DLL引用計(jì)數(shù)加一。
  5. DLL映射到B中的地址可能和A相同,也可能不同。如果不同,需要調(diào)整GetMsgProc的地址。
  6. GetMsgProc = hInstDll B + (GetMsgProcA – hInstDll A)
  7. 系統(tǒng)在進(jìn)程B中地址該DLL的引用計(jì)數(shù)。
  8. 在B空間調(diào)用GetMsgProc,在此函數(shù)里,可以做API Hook處理,還可以創(chuàng)建一些消息機(jī)制,通過A進(jìn)程進(jìn)行功能控制。

使用遠(yuǎn)程線程來注入

在另一個(gè)進(jìn)程中創(chuàng)建遠(yuǎn)程線程:

HANDLE WINAPI CreateRemoteThread(
_In_ HANDLE hProcess,
_In_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ SIZE_T dwStackSize,
_In_ LPTHREAD_START_ROUTINE lpStartAddress,
_In_ LPVOID lpParameter,
_In_ DWORD dwCreationFlags,
_Out_ LPDWORD lpThreadId
);


只要使用這個(gè)遠(yuǎn)程線程調(diào)用LoadLibrary,來加載我們的DLL,就能達(dá)到DLL注入的目的。例如:

CreateRemoteThread(hProcessRemote,NULL, 0, LoadLibraryW,L"C:\\MyLib.dll", 0, NULL);

當(dāng)遠(yuǎn)程線程在遠(yuǎn)程進(jìn)程執(zhí)行時(shí),會(huì)立即調(diào)用LoadLibraryW,并傳入DLL路徑。

 

但并沒有這么簡單

CreateRemoteThread(hProcessRemote,NULL, 0, LoadLibraryW,L"C:\\MyLib.dll", 0, NULL);

但存在兩個(gè)問題:

  1. 第一個(gè)問題是:

上面我們已經(jīng)講過LoadLibraryW是個(gè)導(dǎo)入函數(shù),使用地址引用IAT的方式來調(diào)用,顯然這個(gè)地址在不同的PE文件里是不同的。在其他進(jìn)程中,這個(gè)地址是不一樣的。

對(duì)CreateRemoteThread調(diào)用,假定本進(jìn)程和遠(yuǎn)程進(jìn)程中,Kernel32.dll被映射到地址空間是同一內(nèi)存。(雖然是假定,到目前為止,都是同一地址,重啟會(huì)變,但啟動(dòng)后不變)。

可以改變?yōu)椋?

PTHREAD_START_ROUNTINE pfnThreadRtn = (PTHREAD_START_ROUNTINE)GetProcAddress(GetModuleHandle(_T("Kernel32")), "LoadLibraryW");

CreateRemoteThread(hProcessRemote,NULL, 0, pfnThreadRtn,L"C:\\MyLib.dll", 0, NULL);

  1. 第二個(gè)問題:

L"C:\\MyLib.dll"字符串在調(diào)用進(jìn)程中,目標(biāo)進(jìn)程并沒有這個(gè)字符串,如果在目標(biāo)程序執(zhí)行,此地址則是執(zhí)行一個(gè)未知地址,可能會(huì)崩潰。

解決這個(gè)問題的辦法,需要把本地字符串放到遠(yuǎn)程進(jìn)程去。調(diào)用VirtualAllocEx可以讓一個(gè)進(jìn)程在另一個(gè)進(jìn)程中分配一塊空間。WriteProcessMemory可以將字符串從本進(jìn)程復(fù)制到遠(yuǎn)程進(jìn)程中。

Hook 開發(fā)庫

EasyHook(inline)

同時(shí)支持內(nèi)核和用戶層Hook,在使用中發(fā)現(xiàn)內(nèi)核層有不少Bug,據(jù)說用戶層很穩(wěn)定??雌浯a對(duì)多線程沒有處理,可能會(huì)有問題。

Mhook(inline)

只支持用戶層Hook,代碼輕量,清晰。 

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多