是的,這是可能的通用掛鉤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++後處理程序的塊地址(我們彈出它從棧 - 呼叫指令從塊的結果),並與原來的返回值(RAX或EAX)。嚴格地說--API函數可以返回一對rax + rdx或eax + 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);
1 /你不能在C中做這件事。2/C對「DLL」一無所知。你指的是機器碼。 3/C對「LoadLibrary」,「GetProcAddress」等一無所知。您指的是特定於win32的機器代碼。 4 /你不能在C. – Sebivor
@Seb中這樣做當然C對DLL一無所知。這個問題被標記爲winapi。 C雖然知道函數地址,但這是winapi在這裏獲取的。問題是,是否有一種技術可以爲DLL做到這一點。 – Patrick
沒有語言技巧可以讓你明智地處理你不知道的論點*他們的意思*。即使你使你的方法工作(這很可能,簡單地處理沒有參數,並返回一些看起來不像錯誤代碼的明智的東西),你還沒有取得比使用好的調試器所能實現的更好的東西。您的最終目標是*理解參數* - 您可以通過調試器更好地實現該目標。 – tofro