这是我的问题:
有一个静态库(xxx.lib)和一些在xxx.lib中调用函数foo()
的C文件。我希望每次调用foo()
时都会收到通知消息。但我不允许更改其他人编写的任何源代码。
我花了几天时间在互联网上搜索并发现了几个类似的Q& As,但这些建议都没有真正解决我的问题。我列出了其中一些:
使用gcc -wrap
:Override a function call in C
感谢上帝,我正在使用Microsoft C编译器和链接器,我找不到与-wrap
等效的选项。
Microsoft Detours: Detours在运行时拦截C调用并将调用重定向到trampoline函数。但Detours只对IA32版本免费,而且它不是开源的。
我正在考虑在函数foo()
的开头注入一个jmp指令,将它重定向到我自己的函数。但是,当foo()
为空时,这是不可行的,例如
void foo() ---> will be compiled into 0xC3 (ret)
{ but it'll need at least 8 bytes to inject a jmp
}
我在MSDN上找到了一个名为Hotpatch的技术。它表示链接器将在每个函数的开头添加几个字节的填充。那很好,因为我可以用jmp指令替换填充字节来实现运行时的拦截!但是当我使用/ FUNCTIONPADMIN选项和链接器时,它会给我一个警告:
LINK : warning LNK4044: unrecognized option '/FUNCTIONPADMIN'; ignored
任何人都可以告诉我如何正确制作“hotpatchable”图像?对我的问题来说,这是一个可行的解决方案吗?
我还有希望实现它吗?
答案 0 :(得分:1)
如果您有源代码,可以通过添加-finstrument-functions
来为包含您感兴趣的函数的文件构建添加#include <stdio.h>
#include <time.h>
static FILE *fp_trace;
void
__attribute__ ((constructor))
trace_begin (void)
{
fp_trace = fopen("trace.out", "w");
}
void
__attribute__ ((destructor))
trace_end (void)
{
if(fp_trace != NULL) {
fclose(fp_trace);
}
}
void
__cyg_profile_func_enter (void *func, void *caller)
{
if(fp_trace != NULL) {
fprintf(fp_trace, "e %p %p %lu\n", func, caller, time(NULL) );
}
}
void
__cyg_profile_func_exit (void *func, void *caller)
{
if(fp_trace != NULL) {
fprintf(fp_trace, "x %p %p %lu\n", func, caller, time(NULL));
}
}
,而无需更改源代码来检测代码。然后您必须编写__cyg_profile_func_enter /退出函数以打印您的跟踪。来自here的示例:
mtabulate
如果您有源将库重新编译为共享库,那么另一种方法。从那里可以使用任意数量的调试系统对您自己的.so / .dll进行运行时插入。 (在Windows上浏览unix,某些东西或其他东西[在Windows上有人 - 请编辑])。
如果您没有来源,那么我认为您的选项3仍然有用。写病毒的人已经做了多年。您可能需要进行一些手动检查(因为x86指令的长度不一样),但诀窍是提取完整的指令并将其替换为跳转到安全的地方。做你必须做的事情,让寄存器回到与你删除的指令运行时相同的状态,然后跳到你插入的跳转指令之后。
答案 1 :(得分:0)
如果不更改任何代码,我认为没有这样做。
我能想到的最简单的方法是为你的void foo()
函数编写包装器,并用你的包装器查找/替换它。
void myFoo(){
return foo();
}
而不是致电foo()
致电myFoo()
。
希望这会对你有所帮助。
答案 2 :(得分:0)
VC编译器提供2个选项/Gh
&amp; /GH
用于挂钩功能。
/Gh
标志在每个方法或函数的开头调用_penter
函数,/GH
标志调用_pexit
函数每个方法或功能的结束。
因此,如果我在_penter
中编写一些代码来找出调用函数的地址,那么我将能够通过比较函数地址来有选择地拦截任何函数。
我做了一个样本:
#include <stdio.h>
void foo()
{
}
void bar()
{
}
void main() {
bar();
foo();
printf ("I'm main()!");
}
void __declspec(naked) _cdecl _penter( void )
{
__asm {
push ebp; // standard prolog
mov ebp, esp;
sub esp, __LOCAL_SIZE
pushad; // save registers
}
unsigned int addr;
// _ReturnAddress always returns the address directly after the call, but that is not the start of the function!
// subtract 5 bytes as instruction for call _penter
// is 5 bytes long on 32-bit machines, e.g. E8 <00 00 00 00>
addr = (unsigned int)_ReturnAddress() - 5;
if (addr == foo) printf ("foo() is called.\n");
if (addr == bar) printf ("bar() is called.\n");
_asm {
popad; // restore regs
mov esp, ebp; // standard epilog
pop ebp;
ret;
}
}
使用cl.exe source.c /Gh
构建并运行它:
bar() is called.
foo() is called.
I'm main()!
这很完美!
有关如何使用_penter
和_pexit
的更多示例,请访问A Simple Profiler和tracing with penter pexit以及A Simple C++ Profiler on x64。
我已经使用这种方法解决了我的问题,我希望它也可以帮到你。
:)