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

3
这里是我的问题:有一个静态库(xxx.lib)和一些C文件,调用了xxx.lib中的函数foo()。我希望在每次调用foo()时收到通知消息,但我不被允许更改其他人编写的任何源代码。
我在互联网上搜索了几天,并找到了一些类似的问答,但这些建议都无法真正解决我的问题。我列出其中一些:
  1. use gcc -wrap: Override a function call in C Thank god, I'm using Microsoft C compiler and linker, and I can't find an equivalent option as -wrap.

  2. Microsoft Detours: Detours intercepts C calls in runtime and re-direct the call to a trampoline function. But Detours is only free for IA32 version, and it's not open source.

  3. I'm thinking about injecting a jmp instruction at the start of function foo() to redirect it to my own function. However it's not feasible when foo()is empty, like

      void foo() --->  will be compiled into 0xC3 (ret)
      {                but it'll need at least 8 bytes to inject a jmp
      }
    
  4. I found a technology named Hotpatch on MSDN. It says the linker will add serveral bytes of padding at the beginning of each function. That's great, because I can replace the padding bytes with jmp instruction to realize the interception in runtime! But when I use the /FUNCTIONPADMIN option with the linker, it gives me a warning:

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

    Anybody could tell me how could I make a "hotpatchable" image correctly? Is it a workable solution for my question ?

我还有希望实现它吗?

我认为这是不可能的,除非进行大量的调整。目标文件并不是专门设计用于此目的的。 - fuz
说实话,我会选择第三个选项。但首先,您需要找到要重定向的函数,因为没有像DLL那样的导出。所以是的,它可以完成,但涉及很多工作。这里有一个可能有帮助的链接:小教程 - inzanez
是的,我看到了这篇文章。但在实际项目中并不实用。我希望有一种方法可以在运行时拦截函数调用。想知道Detours是如何做到的... - Sheldon
1
我在Windows方面没有太多经验。你能检查一下这个吗?http://wwwold.cs.umd.edu/Library/TRs/CS-TR-4585/CS-TR-4585.pdf - Manthan Tilva
谢谢@ManthanTilva,这篇文章提供了与我之前列出的相同的解决方案。但它们并不适合我。 - Sheldon
这个怎么样?https://dev59.com/GHRB5IYBdhLWcg3wc3A0 看起来好像是你找到的一样 :/ - Shark
3个回答

1
如果您有源代码,可以通过在包含您感兴趣的函数的文件的构建中添加-finstrument-functions而不更改源代码来使用GCC仪器化代码。然后,您需要编写__cyg_profile_func_enter/exit函数以打印跟踪信息。这里有一个例子:here
#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));
 }
}

如果您有重新编译库作为共享库的源代码,那么另一种方法是可以进行运行时插入自己的.so/.dll文件,使用任何数量的调试系统(在Unix上使用ltrace,在Windows上使用某些东西[请Windows上的某人编辑])。
如果您没有源代码,则我认为您的第三个选项仍然有效。编写病毒的人已经这样做了多年。您可能需要进行一些手动检查(因为x86指令不都是相同长度),但技巧是提取出完整的指令并将其替换为跳转到安全位置的指令。做必要的操作,将寄存器恢复到与删除的指令运行时相同的状态,然后跳转到插入的跳转指令之后。

非常感谢您的帮助~ 我刚刚使用选项\Gh\GH发布了一个解决方案。这两个选项所做的事情与GCC选项-finstrument-functions类似。 :) - Sheldon

1
VC编译器提供了2个选项/Gh/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()被调用。 foo()被调用。 我是main()!

完美!

更多关于如何使用_penter_pexit的示例可以在这里找到:一个简单的分析器使用penter pexit进行跟踪x64上的一个简单C++分析器

我使用这种方法解决了我的问题,希望它也能帮助你。

:)


0

我认为没有任何方法可以在不更改任何代码的情况下完成这个任务。 我能想到的最简单的方法是编写一个包装器来包装你的void foo()函数,并使用Find/Replace功能替换它。

void myFoo(){
     return foo();
}

不要直接调用foo(),而是调用myFoo()

希望这可以帮到你。


感谢您。我知道我所要求的有些超出范围,但我仍然期望奇迹出现。 :) - Sheldon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接