內容

(1)API函數分析:選取特定的API函數,通過逆向工程技術分析其二進制代碼結構和執行流程,了解其參數傳遞方式和返回值處理方法。

(2)HOOK技術實現:介紹HOOK技術的基本原理和實現方法。

(3)OD的動態分析、IDA的靜態分析工具的使用。

(4)基于攻防世界逆向題easy hook的靜態分析。

簡單介紹一下hook

HOOK是一種截取信息、更改程序執行流向、添加新功能的強大技術。通過在執行真正的目標函數之前執行實現插入的代碼,獲取程序執行過程的決定權。

二次開發或補丁信息截獲安全防護 HOOK使用最多的戰場,各類安全軟件都會在應用層和內核中安裝hook、系統回調、過濾驅動等。從而對系統中所有的進/線程、窗口、文件、網絡操作等進行檢查、過濾和攔截,在x86系統中,流行在KiFastCallEntry中進行HOOK,以達到間接hook所有系統調用的目的,同時在應用層中通常也會有一些安全模塊,通過安裝一些應用層HOOK來配合操作。

API HOOK

API HOOK是一種HOOK技術,它的原理是通過修改目標程序中的API調用來實現對程序的攔截和修改。在Windows操作系統中,每個API都有一個唯一的標識符,稱為函數地址或入口地址。通過修改目標程序中API調用的入口地址,可以將程序的流程控制轉移到HOOK函數中,從而實現對程序的攔截和修改。

具體來說,API HOOK的實現分為以下幾個步驟:

(1)獲取目標程序中API的入口地址;

(2)保存原始的API入口地址,以便在HOOK函數中調用;

(3)修改目標程序中API的入口地址,將其指向HOOK函數;

(4)在HOOK函數中實現自定義的功能或修改;

如果需要,可以在HOOK函數中調用原始的API函數。

通過API HOOK技術,可以實現對目標程序中的API進行攔截和修改,比如監控系統調用、實現自定義的功能、加強安全性等。但是,使用API HOOK技術也有一定的風險和副作用,如果使用不當可能會導致系統不穩定或者安全漏洞。因此,在使用API HOOK技術時需要注意安全性和穩定性問題。

HOOK 流程

HOOK 本函數MessageBoxA的源代碼(能運行)

#include 
#include
typedef int (WINAPI* lpMessageBoxA)(HWND, LPCSTR, LPCSTR, UINT);
BYTE olddata32[5] = { 0 };
void hook();
void Unhook();
//回調函數 當調用hook時,hook執行完成后調用該函數后再進入main函數
int WINAPI MyMessageboxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
    Unhook();
    lpMessageBoxA messagebox = (lpMessageBoxA)GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");
    int ret = messagebox(0, "inlinehook", "tip3", 0);
    hook();//函數釋放前再次HOOK,為了攔截下次調用
    return ret;
}
void hook()
{
    //獲取messagebox的基地址
    DWORD messagebox = (DWORD)GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");
    BYTE data[5] = { 0xe9, };
    DWORD offset = (DWORD)MyMessageboxA - messagebox - 5; //計算jmp跳轉的偏移量
    //保存前五個字節的數據
    memcpy(olddata32, (const void *)messagebox, 5);
    //把偏移量與JMP指令拼接
    memcpy(&data[1], &offset, 4);
    DWORD oldProtect = 0;
    //更改頁面屬性 將內存改為可讀可寫可執行
    VirtualProtect((LPVOID)messagebox, 5, PAGE_EXECUTE_READWRITE, &oldProtect);
    memcpy((void*)messagebox, data, 5);
    //還原屬性
    VirtualProtect((LPVOID)messagebox, 5, oldProtect, &oldProtect);
}
void Unhook()
{
    DWORD messagebox = (DWORD)GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");
    DWORD oldProtect = 0;
    VirtualProtect((LPVOID)messagebox, 5, PAGE_EXECUTE_READWRITE, &oldProtect);
    memcpy((void*)messagebox, olddata32, sizeof(olddata32));
    VirtualProtect((LPVOID)messagebox, 5, oldProtect, &oldProtect);
}
void main()
{
    MessageBoxA(0, "hello", "tip1", 0);
    hook();
    MessageBoxA(0, "hello", "tip2", 0);
}

結果分析

Hook前

Hook后,我們的彈窗本該是hello的但是hook后,程序流程被我們修改了。

過程分析

(1)首先我們先給MessageBoxA API下軟件斷點

這里我提供三種方法:

方法一: Ctrl + F 快捷鍵輸入MessageBoxA,跳到一個地址,下軟件斷點。

方法二:在Command后面紅色標注的命令框里輸入:BP MessageBoxA。

方法三:利用吾愛破解里面的OD插件給MessageBoxA下斷點。

(2)下好斷點后程序運行后跳到了760D34B0地址

我們可以看到760D34B0就是我們BP MessageBoxA下斷點后的地址,我們可以記住這個地址,因為我們HOOK主要修改的就是該地址,下面我們從匯編代碼分析該函數流程。

760D34B0 > 8BFF mov edi,edi

它只是一個占位符,方便在調試和優化代碼時進行修改。

760D34B2 55 push ebp

760D34B3 8BEC mov ebp,esp

通過這兩條指令,函數就可以在堆棧中為局部變量分配存儲空間,并在函數執行過程中保存和恢復現場。這樣做的好處是可以避免局部變量和其他函數之間的沖突,同時也可以提高函數的可讀性和可維護性。

760D34B5 6A FF push -0x1

760D34B7 6A 00 push 0x0

通常被用于函數的參數傳遞,將參數壓入堆棧中,以便被調用函數在堆棧中獲取。

760D34B9 FF75 14 push dword ptr ss:[ebp+0x14]

760D34BC FF75 10 push dword ptr ss:[ebp+0x10] ;

760D34BF FF75 0C push dword ptr ss:[ebp+0xC] ;

760D34C2 FF75 08 push dword ptr ss:[ebp+0x8]

這四個push是將MessageBoxA的四個參數壓入堆棧中去。

760D34C5 E8 D6010000 call user32.MessageBoxTimeoutA

該函數是顯示MessageBoxA的彈窗函數。

(3)執行函數后我們繼續運行,執行該函數后OD顯示如圖所示:

00C4295C 6A 00 push 0x0

00C4295E 68 187CC400 push hookmess.00C47C18 ; ASCII "tip2"

00C42963 68 107CC400 push hookmess.00C47C10 ; ASCII "hello"

00C42968 6A 00 push 0x0

這四個push是hook前的函數流程,將該四個push壓入堆棧后,在運行到光標的call中跳入到前面的MessageBoxA函數流程。

(4)hook后我們F7進入該函數

我們可以看到760D34B0-760D34B7地址的匯編指令被我們修改了

Hook前的執行指令:

760D34B0 > 8BFF mov edi,edi

760D34B2 55 push ebp

760D34B3 8BEC mov ebp,esp

Hook后執行過程的指令被我們修改成了jmp 00C4110E

00C4110E地址就是我們修改MessageBoxA函數的地址

(5)HOOK函數的逆向分析如下:

這個跳轉是VC編譯器的特性可以不用管。

00C41730是HOOK函數的起始地址。

這下面的四個push就是將我們修改MessageBoxA也就是MyMessageBoxA的參數壓入堆棧,然后通過下面的call調用。

最后執行以前的函數流程,將修改后的MessageBoxA函數的彈窗顯示出來。

攻防世界逆向題Easy Hook分析過程

查殼


如下圖所示,我們可以知道該程序是32位的PE文件。

IDA靜態分析

步驟一,拉進IDA中按下f5得到main函數的反匯編代碼。

步驟二:分析偽代碼,如下圖是我整理的偽代碼注釋,我們分析完畢后可以知道flag的長度為19,sub_401220()函數就是hook函數,hook的是WriteFile函數。加密過程也在該函數里面。

步驟三:我們運行程序輸入1234567890123456789,可以在程序運行目錄下找到一個文件,我們用文本的形式打開該文件可以得到下面的結果,并不是我們的輸入。

步驟四:進入sub_401220()函數分析,這個函數邏輯清晰地找到了庫函數 WriteFile 的地址,向 byte_40C9BC 中載入了 JUMP 指令的字節碼 E9,計算了 sub_401080 距離 WriteFile 的第五個字節的距離。我們分析先sub_4010D0()函數。

步驟五:進入sub_4010D0()函數分析,我們分析該函數就可以確定是hook了,我們回到 sub_401220()函數,可以知道sub_401080就是整個程序的關鍵了。

步驟六:進入sub_401080()函數分析,該函數就是我們HOOK的返回值,通過sub_401080()函數重新定義了一個WriteFile函數,修改了參數里面的值。我們先分析sub_401140()函數有什么用。

步驟七:進入sub_401140()函數分析,在 sub_401000() 函數加密完 lpBuffer 后,sub_401140又把 WriteFile 函數還原,繼續寫操作。

步驟八:進入sub_401000()函數分析,該函數就是我們加密的函數,flag由此解密,簡單的異或,第一個判斷i=18時,flag[i] = a1[i] ^ 0x13; i 不等于18時,第二個判斷,if (i % 2),滿足條件flag[i] = (a1[i] ^ i) + i,不滿足條件flag[i+2] = (res[i] ^ i)。至此IDA的分析就完成了,接下來就是寫解密腳本了。

最后寫解密腳本,得到flag。

#include 
unsigned char res[] = {
    0x61, 0x6A, 0x79, 0x67, 0x6B, 0x46, 0x6D, 0x2E,
    0x7F, 0x5F, 0x7E, 0x2D, 0x53, 0x56, 0x7B, 0x38,
    0x6D, 0x4C, 0x6E, 0x00};
char flag[19];
int main()
{for (int i = 18; i >= 0; i--){
        if (i == 18)
            flag[i] = res[i] ^ 0x13;
        else{
            if (i % 2){
                flag[i] = (res[i] ^ i) + i;
            }else{
                flag[i+2] = (res[i] ^ i);
            } } }
    for (int i = 1; i < 19; i++)
        printf("%c", flag[i]);}