如何用C语言拦截静态库调用?

时间:2016-07-15 08:52:37

标签: c compilation linker

这是我的问题: 有一个静态库(xxx.lib)和一些在xxx.lib中调用函数foo()的C文件。我希望每次调用foo()时都会收到通知消息。但我不允许更改其他人编写的任何源代码。

我花了几天时间在互联网上搜索并发现了几个类似的Q& As,但这些建议都没有真正解决我的问题。我列出了其中一些:

  1. 使用gcc -wrapOverride a function call in C 感谢上帝,我正在使用Microsoft C编译器和链接器,我找不到与-wrap等效的选项。

  2. Microsoft Detours: Detours在运行时拦截C调用并将调用重定向到trampoline函数。但Detours只对IA32版本免费,而且它不是开源的。

  3. 我正在考虑在函数foo()的开头注入一个jmp指令,将它重定向到我自己的函数。但是,当foo()为空时,这是不可行的,例如

      void foo() --->  will be compiled into 0xC3 (ret)
      {                but it'll need at least 8 bytes to inject a jmp
      }
    
  4. 我在MSDN上找到了一个名为Hotpatch的技术。它表示链接器将在每个函数的开头添加几个字节的填充。那很好,因为我可以用jmp指令替换填充字节来实现运行时的拦截!但是当我使用/ FUNCTIONPADMIN选项和链接器时,它会给我一个警告:

    LINK : warning LNK4044: unrecognized option '/FUNCTIONPADMIN'; ignored
    

    任何人都可以告诉我如何正确制作“hotpatchable”图像?对我的问题来说,这是一个可行的解决方案吗?

  5. 我还有希望实现它吗?

3 个答案:

答案 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 Profilertracing with penter pexit以及A Simple C++ Profiler on x64

我已经使用这种方法解决了我的问题,我希望它也可以帮到你。

:)