2017-06-02 71 views
0

比方說有一個DLL A.DLL與已知的切入點DoStuff,我以某種方式與我自己的DLL迷上了fakeA.dll使得系統在呼喚我,而不是DoStuff。如何編寫這樣一個函數,以便它可以在不知道函數的參數的情況下調用掛鉤DLL(A.DLL)的相同入口點?即我在fakeA.DLL功能看起來像掛鉤的功能,我不知道的參數

LONG DoStuff(
// don't know what to put here 
) 
{ 
    FARPROC pfnHooked; 
    HINSTANCE hHooked; 
    LONG lRet; 

    // get hooked library and desired function 

    hHooked = LoadLibrary("A.DLL"); 
    pfnHooked = GetProcAddress(hHooked, "DoStuff"); 

    // how do I call the desired function without knowing the parameters? 

    lRet = pfnHooked(???); 

    return lRet; 
} 

我現在的想法是,參數在棧上的,所以我猜我會必須有足夠大的堆棧變量(例如一個大屁股struct)至捕獲任何參數,然後將其傳遞給pfnHooked?即

// actual arg stack limit is >1MB but we'll assume 1024 bytes is sufficient 
typedef struct { char unknownData[1024]; } ARBITARY_ARG; 

ARBITARY_ARG DoStuff(ARBITARY_ARG args){ 
    ARBITARY_ARG aRet; 
    ... 
    aRet = pfnHooked(args); 

    return aRet; 
} 

這項工作?如果是這樣,有沒有更好的辦法?


更新:經過一些基本的(和非決定性的)檢測合格作爲參數任意塊不工作(這並不奇怪,因爲該計劃將剛讀什麼,它需要從棧中)。但是,收集返回值比較難,因爲它太大會導致訪問衝突。將任意返回大小設置爲8字節(或者對於x86可能爲4)可能是大多數情況下的解決方案(包括無效返回),但這仍然是猜測。如果我有某種方式知道DLL的返回類型(不一定在運行時),那將是宏偉的。

+2

1 /你不能在C中做這件事。2/C對「DLL」一無所知。你指的是機器碼。 3/C對「LoadLibrary」,「GetProcAddress」等一無所知。您指的是特定於win32的機器代碼。 4 /你不能在C. – Sebivor

+1

@Seb中這樣做當然C對DLL一無所知。這個問題被標記爲winapi。 C雖然知道函數地址,但這是winapi在這裏獲取的。問題是,是否有一種技術可以爲DLL做到這一點。 – Patrick

+1

沒有語言技巧可以讓你明智地處理你不知道的論點*他們的意思*。即使你使你的方法工作(這很可能,簡單地處理沒有參數,並返回一些看起來不像錯誤代碼的明智的東西),你還沒有取得比使用好的調試器所能實現的更好的東西。您的最終目標是*理解參數* - 您可以通過調試器更好地實現該目標。 – tofro

回答

5

這應該是一個評論,但元回答是肯定的,你可以在不知道調用約定和參數的情況下在x64/x86平臺上掛鉤函數。它可以純粹用C完成嗎?不,它也需要對各種調用約定和彙編程序有很好的理解。鉤子框架將會有一些用Assembly編寫的位。

大多數掛鉤框架通過創建一個將執行流程從被調用函數的前導碼重定向到存根代碼(通常獨立於它正在掛鉤的函數)的固有方式來實現。在用戶模式下,你可以保證堆棧始終在場,所以你可以在同一個堆棧上推送自己的局部變量,只要你可以彈出它們並將堆棧恢復到原始狀態。

您並不需要將現有參數複製到您自己的堆棧變量中。您可以檢查堆棧,在嘗試任何操作之前,請仔細閱讀關於調用約定的一些信息,以及如何在不同的體系結構上構建堆棧中各種類型調用的堆棧。

1

比方說有一個DLL A.DLL與已知的切入點DoStuff

如果入口點DoStuff稱爲它應該在一些C頭文件在某處被記錄在案,至少是。所以一種可能的方法可能是解析該頭部以得到其簽名(即,C聲明DoStuff)。也許你可以用所有系統頭文件中聲明的所有函數的簽名來填充一些數據庫......或者如果你有它的話也可以使用調試信息。

如果你調用一些功能(C語言),不給所有需要的參數,該calling convention & ABI將仍然可以使用,並且這些(失蹤)參數獲取垃圾值(如果調用約定定義參數被傳入一個寄存器中,該寄存器中的垃圾;如果該約定定義了要在call stack上傳遞的參數,則該特定調用堆棧槽內的垃圾)。所以你可能會崩潰,肯定有一些undefined behavior(這是可怕的,因爲你的程序可能似乎工作,但仍然是非常錯誤的)。

但是,也看看libffi。一旦您知道(在運行時)要傳遞給某個任意函數的內容,您可以構造一個調用,傳遞正確的數量和類型的參數。

我現在的想法是,參數在棧

我認爲這是錯誤的(至少在許多x86-64系統)上。一些參數通過寄存器傳遞。閱讀關於x86 calling conventions

這項工作?

沒有,因爲有些參數傳遞直通寄存器,並且由於調用約定取決於調用函數的簽名(浮點值可能會在不同的寄存器堆棧傳遞,或總是將無法正常工作;可變參數函數有特定的調用約定;等等......)

順便說一句,最近一些C optimizing compilers能夠做tail call優化,這可能會使事情變得複雜。

1

有沒有這樣做的標準方式,因爲很多事情,如傳遞參數時調用約定,指針大小等。你將不得不爲你的平臺閱讀ABI並編寫一個實現,我再次擔心在C中不可能實現。你將需要一些內聯彙編。

一個簡單的方法來做到這將是(像X86_64的平臺) -

MyDoStuff: 
    jmpq *__real_DoStuff 

這個鉤子什麼也不做只是調用真正的功能。如果你想在掛鉤時做任何有用的事情,你必須在通話之前保存恢復一些寄存器(再次保存取決於ABI)

2

是的,這是可能的通用掛鉤100%正確 - 一個共同的多個具有不同參數的函數計數和調用約定。對於x86/x64(amd64)平臺。

但這種需要使用小ASM存根 - 當然這將是在x86/x64不同 - 但它會很小 - 幾行代碼只是 - 2小存根程序 - 一個用於過濾預 - 通話和一個用於通話後。但大多數代碼執行(95%+)將獨立於平臺和C++(當然這可能DO和ç但比較C++ - ç源代碼將更大,醜陋,很難實現)

在我的解決方案中,需要爲每個掛鉤API(每個hooked api分配一個塊)分配一小塊可執行代碼塊。在這個塊 - 存儲功能名稱,原始地址(或預先呼叫後的傳輸控制 - 這取決於掛鉤方法)和一個相對的呼叫指令到公共asm預呼叫存根。這個的魔法不僅可以將控制轉移到普通存根,而且堆棧中的返回地址將指向阻止自己(好吧,有一些偏移量,但是如果我們使用 C++和繼承 - 它將會是指向一些基類,從中我們派生了我們的可執行塊類)。因爲在普通預分支存根中,我們將獲得信息 - 哪個API調用我們在這裏掛鉤,然後將此信息傳遞給C++常用處理程序。

一個音符,因爲在相對調用可以是僅在範圍[rip-0x80000000, rip+0x7fffffff]需要聲明在單獨BSS部(分配)該代碼塊我們PE內部並標記這個部分作爲RWE。我們不能簡單地使用VirtualAlloc來分配存儲空間,因爲返回的地址可能離我們常見的預分號存根太遠。

在共同 ASM precall存根碼

必須保存RCX,RDX,R8,R9寄存器(這是絕對必須的)和ECX,EDX寄存器86。這是需要的情況下,如果功能使用__fastcall調用約定。然而,例如Windows API的幾乎不使用__fastcall - 只有幾個__fastcall從數千贏API的存在功能(確保這一點,發現這個功能 - 去LIB文件夾和搜索[email protected]字符串(這是__fastcall共同的前綴),然後調用C++共同處理程序,它必須原始功能(其中,傳輸控制)末梢的返回地址。存根恢復RCX,RDX,R8,R9(或ECX,EDX)寄存器和(但不是致電!)到這個地址

如果我們只想過濾前調用這就是我們所需要的。然而在大多數情況下需要過濾器(hook)和post-call - 查看/修改函數返回值和out參數。這也是可能的,但需要更多的編碼。

對於hook後調用顯然我們必須替換hooked api的返回地址。但是我們必須改變返回地址?並保存原始返回地址?爲此我們不能使用全局變量。甚至不能使用本地線程(__declspec(thread)thread_local),因爲調用可能是必需的。不能使用易失性寄存器(因爲它在api調用過程中發生了變化)並且不能使用非易失性寄存器 - 因爲在這種情況下,我們將保存它,以便稍後恢復 - 但是遇到了一些問題 - 在哪裏?

只有一個(和漂亮)溶液此處 - 分配其中含有一個相對呼叫指令到普通呼叫後ASM存根可執行存儲器(RWE)的小塊。以及一些數據保存的原始返回地址,函數參數(用於檢出處理函數中的參數)和函數名稱

這裏再次說明,某些發行者爲x64--這個塊必須離普通帖子不太遠(+/- 2GB) - 所以最好也分配這個存根單獨的.bss部分(與預調用存根)。

有多少人需要這個ret-stubs?每個API調用一個(如果我們想要控制郵政調用)。所以在任何時候都不會超過api通話。通常會說256個預先分配的塊 - 綽綽有餘。即使我們在預先呼叫中分配了這個塊,我們也不會在通話後控制它,但不會崩潰。並且我們不能爲所有被hook的api控制後期調用,但僅限於一些。

非常快速和互鎖alloc/free這個塊 - 需要建立堆棧語義。通過互鎖彈出來分配並通過互鎖推入釋放。和預先初始化(call指令),該模塊在開始(而推動所有IT堆棧,爲每一次不重新初始化它在呼叫前)在ASM

共同的呼聲後存根很簡單 - 在這裏,我們不需要保存任何寄存器。我們只需調用C++後處理程序的塊地址(我們彈出它從棧 - 呼叫指令塊的結果),並與原來的返回值(RAXEAX)。嚴格地說--API函數可以返回一對rax + rdxeax + edx但是99.9%以上的windows api在單個寄存器中返回值,我假設我們將只鉤住這個api。但是,如果想要,可以稍微調整手柄碼本太(只是在大多數情況下,這並不需要)

C++後調用句柄恢復原來的返回地址(通過使用_AddressOfReturnAddress()),可以記錄通話和/或修改了參數最後回到原來的API調用者。我們的處理程序返回什麼 - 這將是api調用的最終返回值。通常我們的桅杆會返​​回原始值。

C++代碼

#if 0 
#define __ASM_FUNCTION __pragma(message(__FUNCDNAME__" proc\r\n" __FUNCDNAME__ " endp")) 
#define _ASM_FUNCTION {__ASM_FUNCTION;} 
#define ASM_FUNCTION {__ASM_FUNCTION;return 0;} 
#define CPP_FUNCTION __pragma(message("extern " __FUNCDNAME__ " : PROC ; " __FUNCTION__)) 
#else 
#define _ASM_FUNCTION 
#define ASM_FUNCTION 
#define CPP_FUNCTION 
#endif 

class CODE_STUB 
{ 
#ifdef _WIN64 
    PVOID pad; 
#endif 
    union 
    { 
     DWORD code; 
     struct 
     { 
      BYTE cc[3]; 
      BYTE call; 
     }; 
    }; 
    int offset; 

public: 

    void Init(PVOID stub) 
    { 
     // int3; int3; int3; call stub 
     code = 0xe8cccccc; 
     offset = RtlPointerToOffset(&offset + 1, stub); 

     C_ASSERT(sizeof(CODE_STUB) == RTL_SIZEOF_THROUGH_FIELD(CODE_STUB, offset)); 
    } 

    PVOID Function() 
    { 
     return &call; 
    } 

    // implemented in .asm 
    static void __cdecl retstub() _ASM_FUNCTION; 
    static void __cdecl callstub() _ASM_FUNCTION; 
}; 

struct FUNC_INFO 
{ 
    PVOID OriginalFunc; 
    PCSTR Name; 

    void* __fastcall OnCall(void** stack); 
}; 

struct CALL_FUNC : CODE_STUB, FUNC_INFO 
{ 
}; 

C_ASSERT(FIELD_OFFSET(CALL_FUNC,OriginalFunc) == sizeof(CODE_STUB)); 

struct RET_INFO 
{ 
    union 
    { 
     struct 
     { 
      PCSTR Name; 
      PVOID params[7]; 
     }; 

     SLIST_ENTRY Entry; 
    }; 

    INT_PTR __fastcall OnCall(INT_PTR r); 
}; 

struct RET_FUNC : CODE_STUB, RET_INFO 
{ 
}; 

C_ASSERT(FIELD_OFFSET(RET_FUNC, Entry) == sizeof(CODE_STUB)); 

#pragma bss_seg(".HOOKS") 

RET_FUNC g_rf[1024];//max call count 
CALL_FUNC g_cf[16];//max hooks count 

#pragma bss_seg() 

#pragma comment(linker, "/SECTION:.HOOKS,RWE") 

class RET_FUNC_Manager 
{ 
    SLIST_HEADER _head; 

public: 

    RET_FUNC_Manager() 
    { 
     PSLIST_HEADER head = &_head; 

     InitializeSListHead(head); 

     RET_FUNC* p = g_rf; 
     DWORD n = RTL_NUMBER_OF(g_rf); 

     do 
     { 
      p->Init(CODE_STUB::retstub); 
      InterlockedPushEntrySList(head, &p++->Entry); 
     } while (--n); 
    } 

    RET_FUNC* alloc() 
    { 
     return static_cast<RET_FUNC*>(CONTAINING_RECORD(InterlockedPopEntrySList(&_head), RET_INFO, Entry)); 
    } 

    void free(RET_INFO* p) 
    { 
     InterlockedPushEntrySList(&_head, &p->Entry); 
    } 
} g_rfm; 

void* __fastcall FUNC_INFO::OnCall(void** stack) 
{ 
    CPP_FUNCTION; 

    // in case __fastcall function in x86 - param#1 at stack[-1] and param#2 at stack[-2] 

    // this need for filter post call only 
    if (RET_FUNC* p = g_rfm.alloc()) 
    { 
     p->Name = Name; 
     memcpy(p->params, stack, sizeof(p->params)); 
     *stack = p->Function(); 
    } 

    return OriginalFunc; 
} 

INT_PTR __fastcall RET_INFO::OnCall(INT_PTR r) 
{ 
    CPP_FUNCTION; 

    *(void**)_AddressOfReturnAddress() = *params; 

    PCSTR name = Name; 
    char buf[8]; 
    if (IS_INTRESOURCE(name)) 
    { 
     sprintf(buf, "#%04x", (ULONG)(ULONG_PTR)name), name = buf; 
    } 

    DbgPrint("%p %s(%p, %p, %p ..)=%p\r\n", *params, name, params[1], params[2], params[3], r); 
    g_rfm.free(this); 
    return r; 
} 

struct DLL_TO_HOOK 
{ 
    PCWSTR szDllName; 
    PCSTR szFuncNames[]; 
}; 

void DoHook(DLL_TO_HOOK** pp) 
{ 
    PCSTR* ppsz, psz; 
    DLL_TO_HOOK *p; 
    ULONG n = RTL_NUMBER_OF(g_cf); 

    CALL_FUNC* pcf = g_cf; 

    while (p = *pp++) 
    { 
     if (HMODULE hmod = LoadLibraryW(p->szDllName)) 
     { 
      ppsz = p->szFuncNames; 

      while (psz = *ppsz++) 
      { 
       if (pcf->OriginalFunc = GetProcAddress(hmod, psz)) 
       { 
        pcf->Name = psz; 
        pcf->Init(CODE_STUB::callstub); 

        // do hook: pcf->OriginalFunc -> pcf->Function() - code for this skiped 
        DbgPrint("hook: (%p) <- (%p)%s\n", pcf->Function(), pcf->OriginalFunc, psz); 

        if (!--n) 
        { 
         return; 
        } 

        pcf++; 
       } 
      } 
     } 
    } 
} 

ASM 64代碼:

extern [email protected][email protected]@[email protected] : PROC ; FUNC_INFO::OnCall 
extern [email protected][email protected]@[email protected] : PROC ; RET_INFO::OnCall 

[email protected][email protected]@SAXXZ proc 
    pop rcx 
    mov rdx,rax 
    call [email protected][email protected]@[email protected] 
[email protected][email protected]@SAXXZ endp 

[email protected][email protected]@SAXXZ proc 
    mov [rsp+10h],rcx 
    mov [rsp+18h],rdx 
    mov [rsp+20h],r8 
    mov [rsp+28h],r9 
    pop rcx 
    mov rdx,rsp 
    sub rsp,18h 
    call [email protected][email protected]@[email protected] 
    add rsp,18h 
    mov rcx,[rsp+8] 
    mov rdx,[rsp+10h] 
    mov r8,[rsp+18h] 
    mov r9,[rsp+20h] 
    jmp rax 
[email protected][email protected]@SAXXZ endp 

ASM x86代碼

extern [email protected][email protected]@[email protected] : PROC ; FUNC_INFO::OnCall 
extern [email protected][email protected]@[email protected] : PROC ; RET_INFO::OnCall 

[email protected][email protected]@SAXXZ proc 
    pop ecx 
    mov edx,eax 
    call [email protected][email protected]@[email protected] 
[email protected][email protected]@SAXXZ endp 

[email protected][email protected]@SAXXZ proc 
    xchg [esp],ecx 
    push edx 
    lea edx,[esp + 8] 
    call [email protected][email protected]@[email protected] 
    pop edx 
    pop ecx 
    jmp eax 
[email protected][email protected]@SAXXZ endp 

你可以問我從哪裏硝酸鉀這個裝飾的名字像[email protected][email protected]@[email protected]?尋找開始的C++代碼 - 對於多個宏 - 並且首次與#if 1一起編譯並在輸出窗口中查找。希望你明白(你可能需要使用這個名字,但不是我的名字 - 裝飾可以不同)

以及如何致電void DoHook(DLL_TO_HOOK** pp)?像這樣:

DLL_TO_HOOK dth_kernel32 = { L"kernel32", { "VirtualAlloc", "VirtualFree", "HeapAlloc", 0 } }; 
DLL_TO_HOOK dth_ntdll = { L"ntdll", { "NtOpenEvent", 0 } }; 

DLL_TO_HOOK* ghd[] = { &dth_ntdll, &dth_kernel32, 0 }; 
DoHook(ghd);