C 挂钩一个我不知道的函数';我不知道要修改的参数
假设有一个DLLC 挂钩一个我不知道的函数';我不知道要修改的参数,c,winapi,dll,hook,C,Winapi,Dll,Hook,假设有一个DLLa.DLL,它具有一个已知的入口点DoStuff,我以某种方式将它与我自己的DLLfakeA.DLL连接起来,这样系统就会调用我的DoStuff。如何编写这样一个函数,使其能够在不知道函数参数的情况下调用挂钩DLL(a.DLL)的同一入口点?例如,fakeA.DLL中的函数 LONG DoStuff( // don't know what to put here ) { FARPROC pfnHooked; HINSTANCE hHooked; LONG
a.DLL
,它具有一个已知的入口点DoStuff
,我以某种方式将它与我自己的DLLfakeA.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;
}
我目前的想法是参数在堆栈上,所以我猜我必须有一个足够大的堆栈变量(例如一个大的assstruct
)来捕获任何参数,然后将其传递给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(不一定在运行时)中知道返回类型,那就太棒了。这应该是一条注释,但元答案是肯定的,在x64/x86平台上,您可以在不知道调用约定和参数的情况下挂接函数。这可以用C语言来完成吗?不,它还需要大量了解各种调用约定和汇编编程。钩子框架将在汇编中写入它的一些位 大多数挂钩框架本质上是通过创建一个蹦床来实现的,蹦床将执行流从被调用函数的前导重定向到存根代码,存根代码通常独立于它所挂钩的函数。在用户模式下,保证堆栈始终存在,因此您也可以将自己的局部变量推送到同一堆栈上,只要您可以弹出它们并将堆栈恢复到原始状态 您实际上不需要将现有参数复制到自己的堆栈变量中。您只需检查堆栈,在尝试任何操作之前,一定要阅读一些关于调用约定的内容,以及如何在不同的体系结构上为汇编中的各种类型的调用构造堆栈 假设存在一个具有已知入口点的DLL
a.DLL
如果入口点DoStuff
已知,那么应该在某个地方记录它,至少在某些C头文件中。因此,一种可能的方法可能是解析该头以获取其签名(即DoStuff
的C声明)。也许您可以用所有系统头文件中声明的所有函数的签名填充一些数据库,等等。。。或者使用调试信息(如果有的话)
如果调用某个函数(在C中)但没有给出所有必需的参数,则仍将使用&并且这些(缺少的)参数将获得垃圾值(如果调用约定定义了要在寄存器中传递的参数,则为该寄存器中的垃圾;如果调用约定定义了要在上传递的参数,则为该特定调用堆栈插槽中的垃圾)。因此,您可能会崩溃,并且肯定会遇到一些问题(这很可怕,因为你的程序可能看起来有效,但仍然是错误的)
但是,也要研究一下。一旦知道(在运行时)要传递给某个任意函数的内容,就可以通过传递正确数量和类型的参数来构造对该函数的调用
我目前的想法是,这些参数都在堆栈上
我认为这是错误的(至少在许多x86-64系统上是如此)。一些参数是通过寄存器传递的。请阅读
这样行吗
不,它不起作用,因为一些参数是通过寄存器传递的,而且调用约定取决于被调用函数的签名(浮点值可能在不同的寄存器中传递,或者总是在堆栈上传递;可变函数有特定的调用约定;等等)
顺便说一句,最近的一些C能够进行优化,这可能会使事情变得复杂。没有标准的方法来做这件事,因为在传递参数时,很多事情都很重要,比如调用约定、指针大小等。你必须阅读平台的ABI并编写一个实现,我担心这在C.Yo中也不可能实现你需要一些内联汇编 一种简单的方法是(对于X86_64这样的平台)-
这个钩子什么也不做,只是调用真正的函数。如果你想在钩子时做一些有用的事情,你必须在调用之前保存或还原一些寄存器(同样,保存什么取决于ABI)是的,这是可能的,通用挂钩100%正确-对于具有不同参数计数和调用约定的多个函数,一个通用挂钩。对于x86/x64(amd64)平台 但是为了满足这一需求,使用少量的asm存根-当然对于x86/x64来说会有所不同-但是它将非常小-只有几行代码-2个小存根过程-一个用于过滤器预调用,一个用于后调用。但是大多数代码实现(95%+)将是平台独立的,并且在c++(当然,这可能是在c上执行的,但与c++-c相比,源代码将更大、更难看,更难实现) 在我的解决方案中,需要为每个挂钩api分配小的可执行代码块(每个挂钩api分配一个代码块)。在这个块中,存储函数名、原始地址(或预调用后将控制转移到何处-这取决于挂钩方法)还有一个相对的调用指令到通用asm预调用存根。这个调用的神奇之处不仅在于它
MyDoStuff:
jmpq *__real_DoStuff
#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++;
}
}
}
}
}
extern ?OnCall@FUNC_INFO@@QEAAPEAXPEAPEAX@Z : PROC ; FUNC_INFO::OnCall
extern ?OnCall@RET_INFO@@QEAA_J_J@Z : PROC ; RET_INFO::OnCall
?retstub@CODE_STUB@@SAXXZ proc
pop rcx
mov rdx,rax
call ?OnCall@RET_INFO@@QEAA_J_J@Z
?retstub@CODE_STUB@@SAXXZ endp
?callstub@CODE_STUB@@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 ?OnCall@FUNC_INFO@@QEAAPEAXPEAPEAX@Z
add rsp,18h
mov rcx,[rsp+8]
mov rdx,[rsp+10h]
mov r8,[rsp+18h]
mov r9,[rsp+20h]
jmp rax
?callstub@CODE_STUB@@SAXXZ endp
extern ?OnCall@FUNC_INFO@@QAIPAXPAPAX@Z : PROC ; FUNC_INFO::OnCall
extern ?OnCall@RET_INFO@@QAIHH@Z : PROC ; RET_INFO::OnCall
?retstub@CODE_STUB@@SAXXZ proc
pop ecx
mov edx,eax
call ?OnCall@RET_INFO@@QAIHH@Z
?retstub@CODE_STUB@@SAXXZ endp
?callstub@CODE_STUB@@SAXXZ proc
xchg [esp],ecx
push edx
lea edx,[esp + 8]
call ?OnCall@FUNC_INFO@@QAIPAXPAPAX@Z
pop edx
pop ecx
jmp eax
?callstub@CODE_STUB@@SAXXZ endp
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);