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

如何在C语言中拦截静态库调用?,c,compilation,linker,C,Compilation,Linker,我的问题是: 在xxx.lib中有一个静态库(xxx.lib)和一些调用函数foo()的C文件。我希望每次调用foo()时都能收到一条通知消息。但我不允许更改任何由他人编写的源代码 我花了几天时间在互联网上搜索,发现了几个类似的问答,但这些建议都不能真正解决我的问题。我列举了其中一些: 使用gcc-wrap: 谢天谢地,我使用的是Microsoft C编译器和链接器,我找不到与-wrap等效的选项 Microsoft绕道: Detours在运行时截取C调用,并将调用重新定向到trampoline

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

我花了几天时间在互联网上搜索,发现了几个类似的问答,但这些建议都不能真正解决我的问题。我列举了其中一些:

  • 使用
    gcc-wrap
    : 谢天谢地,我使用的是Microsoft C编译器和链接器,我找不到与
    -wrap
    等效的选项

  • Microsoft绕道: 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上找到了一种技术。它说链接器将在每个函数的开头添加几个字节的填充。这很好,因为我可以用jmp指令替换填充字节,从而在运行时实现拦截!但当我对链接器使用/FUNCTIONPADMIN选项时,它会给我一个警告:

    LINK : warning LNK4044: unrecognized option '/FUNCTIONPADMIN'; ignored
    
    任何人都可以告诉我如何正确地制作“可热修补”图像?这是解决我问题的可行办法吗


  • 我还希望实现它吗?

    我认为在不更改任何代码的情况下,没有任何方法可以做到这一点。 我能想到的最简单的方法是为
    void foo()
    函数编写包装器,并用包装器查找/替换它

    void myFoo(){
         return foo();
    }
    
    而不是调用
    foo()
    调用
    myFoo()


    希望这将对您有所帮助。

    如果您有源代码,您可以在不更改源代码的情况下,通过添加
    -finstrument functions
    来构建包含您感兴趣的函数的文件。然后,您必须编写uu cyg_profile_func_enter/exit函数来打印跟踪。例如:

    #包括
    #包括
    静态文件*fp_跟踪;
    无效的
    __属性(构造函数)
    跟踪_开始(无效)
    {
    fp_trace=fopen(“trace.out”,“w”);
    }
    无效的
    __属性(析构函数)
    跟踪结束(无效)
    {
    如果(fp_跟踪!=NULL){
    fclose(fp_跟踪);
    }
    }
    无效的
    __cyg_profile_func_enter(void*func,void*caller)
    {
    如果(fp_跟踪!=NULL){
    fprintf(fp_跟踪,“e%p%p%lu\n”,func,调用者,时间(NULL));
    }
    }
    无效的
    __cyg_profile_func_exit(void*func,void*caller)
    {
    如果(fp_跟踪!=NULL){
    fprintf(fp_跟踪,“x%p%p%lu\n”,func,调用者,时间(NULL));
    }
    }
    
    如果您有源代码将库重新编译为共享库,那么还有另一种方法。从那里,您可以使用任意数量的调试系统进行自己的.so/.dll运行时插入。(unix上的ltrace,windows上的某物或其他[windows上的某人--请编辑])


    如果您没有来源,那么我认为您的选项3应该仍然有效。写病毒的人已经写了很多年了。您可能需要进行一些手动检查(因为x86指令的长度并不完全相同),但诀窍是拉出完整的指令,并将其替换为跳转到安全的地方。执行必须执行的操作,使寄存器恢复到与删除的指令运行时相同的状态,然后跳转到插入的跳转指令之后

    VC编译器提供了两个选项&挂钩函数

    /Gh
    标志导致在每个方法或函数的开头调用
    \u penter
    函数,
    /Gh
    标志导致在每个方法或函数的结尾调用
    \u pexit
    函数

    因此,如果我在
    \u 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;
        }
    }
    
    #包括
    void foo()
    {
    }
    空条()
    {
    }
    void main(){
    bar();
    foo();
    printf(“我是main()!”;
    }
    无效uuu declspec(裸体)u cdecl u penter(无效)
    {
    __asm{
    push ebp;//标准prolog
    mov-ebp,esp;
    子esp,本地大小
    pushad;//保存寄存器
    }
    无符号整数地址;
    //\u ReturnAddress总是在调用后直接返回地址,但这不是函数的开始!
    //减去5个字节作为调用penter的指令
    //在32位机器(例如E8)上为5字节长
    地址=(无符号整数)\返回地址()-5;
    如果(addr==foo)printf(“调用了foo()。\n”);
    如果(addr==bar)printf(“调用了bar()。\n”);
    _asm{
    popad;//还原注册表
    mov esp,ebp;//标准尾声
    pop-ebp;
    ret;
    }
    }
    
    使用
    cl.exe source.c/Gh
    构建并运行它:

    调用了
    bar()。
    调用foo()。
    我是梅因

    太完美了

    有关如何使用
    \u penter
    \u pexit
    的更多示例,请参见此处和

    我已经用这个方法解决了我的问题,我希望它也能帮助你


    :)

    我认为如果没有大量的摆弄,这是不可能的。对象文件并不是专门为实现这一点而设计的。老实说,我会选择选项3。但首先,您必须找到要重定向的函数,因为不会有任何导出,就像您有DLL时一样。所以,是的,这是可以做到的,但是需要做很多工作。一个可能有用的链接:是的,我看到了这篇文章。但这在实际项目中并不实用。我确实希望有一种方法可以在运行时拦截函数调用。想知道迂回是怎么做到的……我对windows没有太多经验。你能检查一下这个吗?感谢@ManthanTilva,本文给出了与上面列出的相同的解决方案。但他们不适合我
    #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;
        }
    }