淺談熱補丁的鉤取方式

蚁景网安实验室發表於2024-06-25

前言

熱補丁的鉤取方式是為了解決內聯鉤取在多執行緒情況下會出錯的情況,使用熱補丁的鉤取可以避免重複讀寫指令造成問題。

內聯鉤取潛在問題

正常情況下,在每次跳轉到自定義函式時需要將原始的指令(mov edi,edi)寫回CreateProcessW函式內,為了後續正確呼叫CreateProcesW函式,在呼叫完畢之後,又需要進行掛鉤的處理,即將mov指令修改為jmp指令。

image-20240508111729098

但是在多執行緒的情況下就可能會出現下列問題,在進行mov指令篡改時可能會發生執行緒的切換,因為篡改指令的操作不是原子操作。那麼線上程2時可能呼叫了CreateProcessW函式時可能跳轉指令還沒寫完成,例如下圖的jmp 0x12xx,而不是原本的jmp 0x1234就導致了執行出錯。

image-20240508112437672

為了解決此問題採用了熱補丁鉤取。

熱補丁鉤取

熱補丁是指在不中斷系統執行進行應用。即不中斷程式執行也能夠修改系統庫或程式中的執行邏輯。

這裡以CreateProcessW為例子

windbg中使用以下指令在CreateProcessW函式中打下斷點

.reload /f
bp CreateProcessW

可以看到CreateProcessW函式入口點是mov edi,edi指令,而在該指令上方有一段沒用用到的空間,在windbg中使用int 3指令填充了。

image-20240508134834761

mov edi,edi指令本身沒有實際意義,這就是微軟在系統庫預留的空間,用於打上熱補丁。因為這個指令無論被修改成什麼都不會影響程式的執行。

接著可以發現這跳指令的長度為2位元組,因此可以使用任意的2位元組長的指令替換mov edi,edi

那麼這裡就需要尋找可以完成跳轉的指令,並且僅佔用2位元組完成對mov指令的替換。

【----幫助網安學習,以下所有學習資料免費領!加vx:dctintin,備註 “部落格園” 獲取!】

 ① 網安學習成長路徑思維導圖
 ② 60+網安經典常用工具包
 ③ 100+SRC漏洞分析報告
 ④ 150+網安攻防實戰技術電子書
 ⑤ 最權威CISSP 認證考試指南+題庫
 ⑥ 超1800頁CTF實戰技巧手冊
 ⑦ 最新網安大廠面試題合集(含答案)
 ⑧ APP客戶端安全檢測指南(安卓+IOS)

在彙編中存在著短跳轉指令可以完成跳轉並且僅佔用2位元組,用以下例子來觀察一下短跳轉的指令。

int main()
{
    // 使用標籤作為跳轉目標
    __asm {
        jmp short label;
    };
​
    // 標籤處定義跳轉目標
label:
    // 這裡是跳轉目標後的程式碼
    return 0;
}

可以看到在跳轉到標籤label上時,採用的跳轉指令機器碼是EB開頭的,而不是E9,並且指令長度也只有2位元組。

image-20240508135609706

那麼00是跳轉的偏移值,根據該例子分析一下跳轉偏移的計算

跳轉偏移 = 目標地址 - 當前地址 - 當前指令的長度
00      = 00731005 - 00731003 - 2

可以看到計算偏移的公式與jmp指令一致,只是跳轉的指令的長度為5位元組,而短跳轉的指令長度為2位元組,因此jmp指令也被稱之為長跳轉。

那麼怎麼配合短跳轉進行一個鉤取操作,如下圖。我們可以藉助短跳轉使得指令執行到上述填充的區域,然後再使用jmp指令完成鉤取的操作。這裡需要注意的是空閒區域的空間大小需要大於5個位元組,不然無法容納jmp指令。

image-20240508140251069

最終修改後鉤取的效果如下圖,在自定義函式中不在需要鉤取與脫鉤的操作,因為我們修改的指令不會影響正常的CreateProcessW函式執行。那麼在既然不存在寫操作,那麼在多執行緒中也不會因為條件競爭導致還沒寫完就切換執行緒的情況。

image-20240508140519160

那麼程式碼實現部分如下,這裡需要注意長跳轉的指令0xE9,短跳轉的指令為0xEB,這裡先把偏移計算好了0xF9,因此寫好了,但是這個偏移值不是唯一值,只要找到的地址存在大於5位元組的空閒區域都是可以的。緊接著就是修改函式內部的指令,將初始的指令修改為短跳轉,然後再空閒區中填充長跳轉即可。

...
    //長跳轉指令
    BYTE pBuf[5] = { 0xE9, 0 };
    //短跳轉指令 + 偏移值
    BYTE pShortJmp[2] = { 0xEB, 0xF9};
    //獲取模組地址
    HMODULE hModule = GetModuleHandleA(szDllName);
    //獲取函式地址
    FARPROC pfnOld = GetProcAddress(hModule, szFuncName);
    //選中長跳轉指令填充的地址,這裡選擇恰好能容納jmp指令的位置
    DWORD target = (DWORD)pfnOld - 5;
    //計算跳轉的偏移
    DWORD dwAddress =  (DWORD)pfnNew - target - 5;
    //修改區域的許可權
    VirtualProtect((LPVOID)target, 7, PAGE_EXECUTE_READWRITE, &dwOldProtect);
    //將偏移填充到指令中
    memcpy(&pBuf[1], &dwAddress, 4);
    //將長跳轉指令填充
    memcpy((LPVOID)target, pBuf, 5);
    //儲存原始的兩個位元組
    memcpy(pOldBytes, pfnOld, 2);
    //將短跳轉指令填充
    memcpy(pfnOld, pShortJmp, 2);
    VirtualProtect((LPVOID)target, 7, dwOldProtect, &dwOldProtect);
...

在自定義函式中,只需要直接呼叫CreatePorcessW + 2的指令就可以完成原始CreateProcessW函式,不再需要掛鉤脫鉤的處理。

...
    //呼叫CreateProcessW + 2
    BOOL ret = ((LPFN_CreateProcessW)((DWORD)pfnOld + 2))(
        applicationName,
        lpCommandLine,
        lpProcessAttributes,
        lpThreadAttributes,
        bInheritHandles,
        dwCreationFlags,
        lpEnvironment,
        lpCurrentDirectory,
        lpStartupInfo,
        lpProcessInformation
        );
...

完整程式碼:

https://github.com/h0pe-ay/HookTechnology/tree/main/Hook-HotPatch

總結

優點:避免多執行緒出錯

缺點:不一定有熱補丁的條件,就是不一定存在有垃圾指令

如64位程式的CreateProcessW函式的第一條指令是mov r11,rsp,但是後續的指令都需要用到r11暫存器的值,因此該指令不是無用指令。就不能上述熱補丁的方法。

image-20240508142953237

更多網安技能的線上實操練習,請點選這裡>>

相關文章