匯入地址表鉤取技術解析

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

前置知識

匯入表

在一個可執行檔案需要用到其餘DLL檔案中的函式時,就需要用到匯入表,用於記錄需要引用的函式。例如我們編寫的可執行檔案需要用到CreateProcess函式,就需要用到kernel32.dll檔案並且將其中的CreateProcess函式的資訊匯入到我們的可執行檔案中,然後再呼叫。

為了管理這些匯入函式,就構建了一個匯入表進行統一的管理,簡單來說,當我們編寫的可執行檔案中使用到匯入函式就會去匯入表中去搜尋找到指定的匯入函式,獲取該匯入函式的地址並呼叫。

image-20240424192254287

因此載入器再呼叫匯入函式之前需要先找到匯入表的所在處。在可執行檔案對映到記憶體空間是,都是以Dos Header開始的,在該頭部存在elfanew的欄位,用於記錄PE檔案頭的偏移,在PE檔案頭存在可選頭的結構體,該結構體中儲存資料目錄項,其中就包括了匯入表。因此在記憶體中我們需要透過Dos Header -> Nt Header -> Option Header -> Import Table的順序獲取匯入表。

image-20240424193422922

這裡使用《加密與解密》的圖來看一下匯入表的結構體,如下圖。

image-20240424195938393

可以看到匯入表涉及的變數非常多,這裡重點關注OriginalFirstThunkFistThunk以及Name

  • Name:指向匯入庫的名稱。

  • OriginalFistThunk:指向輸入名稱表,裡面儲存了匯入函式的資訊。

  • FirstThunk:指向輸入地址表,可以看到在初始化的時候OriginalFistThunkFirstThunk指向的是同一塊區域,即匯入函式的資訊。

輸入名稱表的結構體如下圖,這裡重點關注OrdinalAddressOfData

  • Ordinal:記錄函式的序號,即匯入函式以序號儲存

  • AdressOfData:以函式命的形式記錄匯入函式

image-20240424201610702

那麼INTIAT的區別在於,載入器會在從匯入表中獲取了匯入函式名稱後,會搜尋該函式的名稱並獲取該函式的地址並填入到IAT中,因此在經歷了載入器後,IAT中儲存了實際地址。如下圖。

image-20240424202850423

匯入地址表鉤取技術

輸入地址表鉤取技術就是透過修改輸入地址表的地址值,因此當呼叫該匯入函式時會跳轉到被篡改的地址上。

在鉤取之前的狀態如下圖

image-20240424204535373

在鉤取之後的狀態如下圖

image-20240424204853835

因此總結一下輸入地址表鉤取技術的流程

  • 確定需要鉤取的匯入函式

  • 獲取輸入地址表的地址

  • 在輸入地址表中搜尋需要鉤取的匯入函式地址並且將匯入函式地址修改為自定義的函式

  • 在處理完之後需要在自定義函式中重新呼叫被鉤取的函式

確定需要鉤取的匯入函式

首先確定可執行檔案中存在什麼匯入函式,可以發現目標的可執行檔案中匯入了kernel32.dll的系統庫,並且匯入的CreateProcessW

image-20240424205932029

那麼採用輸入地址表鉤取方法鉤取CreateProcessW函式。

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

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

獲取匯入地址表的地址

根據DOS Header -> Nt Header -> Option Header ->Import Table的順序進行搜尋,即可獲取匯入地址表的地址。

程式碼如下

...
        //獲取當前程序的基地址
    hMod = GetModuleHandle(NULL);
    pBase = (PBYTE)hMod;
    //程序的基地址是從DOS頭開始的
    pImageDosHeader = (PIMAGE_DOS_HEADER)hMod;
    //透過e_lfanew變數獲取NT頭的偏移,然後加上基地址及NT頭的位置
    pImageNtHeaders = (PIMAGE_NT_HEADERS)(pBase + pImageDosHeader->e_lfanew);
    //資料目錄項下標為1的項是匯入表
    pImageImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(pImageNtHeaders->OptionalHeader.DataDirectory[1].VirtualAddress + pBase)
...

獲取匯入函式地址並修改

在獲取匯入地址表的地址後,首先透過遍歷匯入表的結構體,提取其中的Name欄位,判斷是否為我們需要鉤取的匯入庫名。在匹配完成後則選擇繼續遍歷IAT中的函式地址,找到需要鉤取的函式地址,找到後則修改為自定義函式的地址。

image-20240424212559365

程式碼如下

    ...
    //遍歷匯入表項
    for (; pImageImportDescriptor->Name; pImageImportDescriptor++)
    {
        //獲取匯入庫的名稱
        szLibName = (LPCSTR)(pImageImportDescriptor->Name + pBase);
        //比較匯入庫的名稱,判斷是否為kernel32.dll
        if (!_stricmp(szLibName, szDllName))
        {
            //獲取IAT
            PIMAGE_THUNK_DATA pImageThunkData = (PIMAGE_THUNK_DATA)(pImageImportDescriptor->FirstThunk + pBase);
            //獲取匯入函式地址
            for (; pImageThunkData->u1.Function; pImageThunkData++)
            {
                //判斷函式地址是否是需要鉤取的函式地址,這裡需要注意的是64位與32位地址的區別
                if (pImageThunkData->u1.Function == (ULONGLONG)pfnOrg)
                {
                    //修改IAT的許可權為可寫
                    VirtualProtect(&pImageThunkData->u1.Function, 4, PAGE_EXECUTE_READWRITE, &dwOldProtect);
                    //將原始的地址修改為自定義函式地址
                    pImageThunkData->u1.Function = (ULONGLONG)pfnNew;
                    //將許可權恢復
                    VirtualProtect(&pImageThunkData->u1.Function, 4, dwOldProtect, &dwOldProtect);
                    return TRUE;
                }
            }
        }
        ...

在自定義函式中重新呼叫被鉤取的函式

這裡需要注意的是,我們需要構建一個自定函式,該函式的返回型別與引數需要與鉤取的函式一模一樣,這樣我們就可以獲取所有引數的資訊,然後篡改後重新傳遞給原始的匯入函式,即可完成鉤取。

image-20240424213549505

程式碼如下,這裡篡改原始CreateaProcessW函式的第一個引數,使計算器

...
    LPCWSTR applicationName = L"C:\\Windows\\System32\\calc.exe";
    
    return ((LPFN_CreateProcessW)g_pOrgFunc)(applicationName,
        lpCommandLine,
        lpProcessAttributes,
        lpThreadAttributes,
        bInheritHandles,
        dwCreationFlags,
        lpEnvironment,
        lpCurrentDirectory,
        lpStartupInfo,
        lpProcessInformation);
...

完整程式碼:https://github.com/h0pe-ay/HookTechnology/blob/main/Hook-IAT/iat.cpp

除錯

剛開始使用的是xdbg除錯,但是用的不太習慣,後面改用WinDbg還可以原始碼除錯,這裡記錄一下需要用到的操作與指令。

符號表與原始碼載入

在設定中可以選擇原始碼預設的目錄以及符號表預設的目錄,符號檔案則是利用Visutal Studio編譯生成的pdb檔案。

其中srv*c:\Symbols*https://msdl.microsoft.com/download/symbols是下載官方的符號表檔案,這裡可以選擇刪掉只除錯我們設定的檔案。不然每次都需要下載一遍影響時間。

原始碼檔案也可以在側邊欄選擇Open source file選項開啟。

image-20240425103000930

DLL載入除錯

由於鉤取時需要先使用DLL注入技術將自定義的DLL檔案注入進去,因此想要除錯鉤取過程則需要在DLL附著的時候打下斷點。

利用sxe ld:xxx.dll即可在載入xxx.dll的時候打下斷點。

利用sxe ud:xxx.dll即在解除安裝xxx.dll的時候打下斷點。

image-20240425103802758

關閉最佳化除錯

防止自定義函式中的變數被最佳化導致不方便單步除錯,在Visual Studio中可以選擇關閉最佳化進行編譯。

image-20240425104053895

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

相關文章