自定義跳轉函式的通用unhook方法

SecIN發表於2022-05-06

0x00 前言

本文介紹一種比較有意思的unhook手法,來源於小夥伴發的一個GitHub的POC:https://github.com/trickster0/LdrLoadDll-Unhooking,本文講參照此POC來一步步解讀這個方法。

目前大家常用的經典手法大都是直接系統呼叫(Syscall)或是找到ntdll的地址並重新對映磁碟中的.text段,去獲得一個乾淨的dll去尋找函式地址的方式。

下面介紹這種方式相當於我們自己去組裝一個“跳轉函式”,巧妙地規避了一些Hook,具有一定的參考以及學習價值。

0x01 流程分析

  1. 首先,構造Nt函式引數的結構體
    UNICODE_STRING ldrldll;
    OBJECT_ATTRIBUTES objectAttributes = { 0 };
    wchar_t ldrstring[] = L"Wininet.dll";
    RtlInitUnicodeString(&ldrldll, ldrstring);
    InitializeObjectAttributes(&objectAttributes, &ldrldll, OBJ_CASE_INSENSITIVE, NULL, NULL);
  1. 接著定義和初始化要修補的指令的頭部、地址、尾部
    unsigned char jumpPrelude[] = { 0x49, 0xBB }; 
    unsigned char jumpAddress[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF };
    unsigned char jumpEpilogue[] = { 0x41, 0xFF, 0xE3, 0xC3 };
  1. 新建一塊記憶體頁,屬性為可讀可寫(opsec),這塊記憶體頁是為了儲存最終要使用的LdrLoadDll的地址。
LPVOID trampoline = VirtualAlloc(NULL,19, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
  1. 獲取ntdll內匯出函式LdrLoadDll,原始的地址
LPVOID origLdrLoadDll = GetProcAddress(GetModuleHandleA("ntdll.dll"),"LdrLoadDll");

許多EDR去Hook API的方式就是去修改Windows DLL中的函式,透過在函式開頭插入JMP指令來跳轉到自己的檢測函式;如果API被Hook了的話,一般前5個位元組會變為JMP xxxxh,跳轉到檢測函式的地址,下方圖片說明了這個流程。

image-20220117134946650

上述獲取到的原始函式地址,放在記憶體視窗檢視

image-20220117132815195

為了更方便檢視,使用Windbg反彙編LdrLoadDll檢視其結構,記錄前五個位元組,也就是\x48\x89\x5c\x24\x10

image-20220117005918587

5.將原始的前5個位元組,放入我們開始申請的地址中。

CCopyMemory(trampoline,(PVOID)"\x48\x89\x5c\x24\x10", 5);

這一步比較巧妙,即使是EDR修改了前5個位元組為跳轉指令,我們也不用在意,因為我們不會去使用原始的前5個位元組,而是自己放進去。

  1. 獲取原始地址前5個位元組後的地址,放入第2步申請的jumpAddress
    LPVOID jmpAddr = (void*)((char*)origLdrLoadDll + 0x5);
    *(void**)(jumpAddress) = jmpAddr; //jmpaddr的地址

image-20220117133552397

檢視jumpAddress的記憶體,發現也就是地址7ff8d8ae6a15

image-20220117133636639

  1. 接著是3個複製操作
    CCopyMemory((PBYTE)trampoline+5, jumpPrelude, 2);
    CCopyMemory((PBYTE)trampoline + 5 + 2, jumpAddress, sizeof(jumpAddress)); 
    CCopyMemory((PBYTE)trampoline + 5 + 2 + 8, jumpEpilogue, 4);

首先將jumpPrelude複製到前5個位元組後,然後複製原始函式5個位元組後的地址,最後複製jumpEpilogue指令尾部

在第5步的時候trampoline事先寫入了前5個位元組,記憶體看起來是這樣的

image-20220117134129791

經過3次內容的複製,最終指令為:

image-20220117134431236

  1. 修改這個最終要使用的“自定義跳轉函式”的記憶體空間為可執行
VirtualProtect(trampoline,30,PAGE_EXECUTE_READ,&oldProtect);
  1. 最後,將地址賦給事先定義的函式結構,並且呼叫
    LdrLoadrDll = (pNewLdrLoadDll)trampoline;
    HANDLE wininetmodule = NULL;
    LdrLoadrDll(NULL, 0 , &ldrldll, &wininetmodule);

開啟VS的彙編視窗,到了函式呼叫時,步入進去即可更清晰的看到最終trampoline中所做的操作,右邊藍框

image-20220117005309923

0x02 總結

這樣就構建了一個完整的自定義的跳轉函式,這個函式實際上的功能還是跳轉回原始的LdrLoadDll的前5個位元組後的地址去執行,相當於避免了修改前5個位元組JMP到檢測函式的這種Hook方法。

我們自定義的跳轉函式不受其影響,並且呼叫也是從NTDLL發出的,以一張圖來說明流程:

image-20220117143938358

最後,歡迎各位師傅關注微信公眾號 駭客在思考 ,一起學習


相關文章