确定应用程序的库调用行为

逆向工程 拆卸 x86 linux C++ 图书馆
2021-06-12 21:28:21

我有一个闭源 Linux 库 libfoo,它可以调用另一个闭源库 libbar 以获取其他(主要是不需要的)功能。但是,出于道德原因,我不能使用 libbar,所以我试图制作一个虚拟库 libfakebar,它“实现”尽可能少的 libbar,同时仍然可以用作替代品(即,尽可能接近尽可能多地收集存根)。

我最初尝试简单地通过 libbar 从 libbar 中提取定义的符号,nm -D --defined-only libbar.so并且只为这些符号创建存根,参考可用的公共文档以获得适当的函数签名。但是,当 libfoo 尝试进行不基于符号的调用时,这会失败。具体来说,它调用BarInstance()libbar 以获取指向 C++ 类实例的指针,向该指针添加一个看似任意的值,然后使用在新地址处找到的值作为指向它调用的下一个函数的指针。这是一个演示此示例的示例:

callq  0x7fff97158300 <BarInstance@plt>
mov    (%rax),%r8
lea    0x96c165(%rip),%rcx        # 0x7fff97b50e65
mov    %ebp,%edx
mov    %r12d,%esi
mov    %rax,%rdi
callq  *0x28(%r8)

我无法理解这应该如何有意义或成为“合法”代码。值得注意的是,如果我BarInstance()返回一组指向存根函数而不是类实例的指针,libfoo 会毫无怨言地运行,并且一切“正常”。不幸的是,它不足以满足我的最低功能要求,因为我需要的至少一个功能不适用于上述存根,而且 libfoo 的奇怪调用行为并不能帮助我了解更多可能需要加在哪里。

我不确定这会产生什么影响,但似乎 libbar 是使用 GCC 的-fvisibility=hidden. 我一直无法弄清楚上述调用行为的确切含义,因此我需要在 libfakebar 中做什么才能正确复制 libfoo 代码按预期工作所需的任何内容 - 这里发生了什么?

1个回答

这似乎是相当典型的虚函数调用,其中指向虚函数表的指针存储在偏移量的对象中,0000并且调用指向0028中偏移量处的函数

对象的虚函数表指针(在 offset 处0000)在类构造函数中初始化。由于构造函数将在库中,因此不需要在调用代码中直接引用虚函数表。类似地,对虚函数的正常调用是通过虚函数表进行的,并且在调用代码中也不需要对其进行任何直接引用。

为了应用程序和库之间的一致性,重要的是两者

  • 用于编译应用程序的库头文件与库二进制文件匹配,并且
  • 用于编译应用程序的编译器使用与用于编译库的相同的应用程序二进制接口

有关这方面的更多信息,您可能需要阅读有关二进制兼容性的信息

ABI 指定的一件事是虚函数表中函数的顺序。例如,安腾 C++ ABI(由 GCC 使用)说

“虚表中虚函数指针的顺序就是类中相应成员函数的声明顺序。”

因此,如果声明顺序在构建库和构建应用程序之间发生变化,将不再有二进制兼容性。