我有一个编译的ELF文件libfoo.so,它导出一些类方法,例如:

struct Bar {
   void f();
   void g();
};

我知道这些类的确切声明,但是定义被编译为.so。我使用与.so相同的编译器(gcc> = 7),因此名称改写和ABI匹配。这意味着,如果将上述声明添加到代码中,则可以直接调用在libfoo.so中实现的方法。

但是,我不想用顶级libfoo东西污染我的 namespace (并且我不控制libfoo源)。所以我想在struct Bar中声明namespace foo {}。现在,一旦执行此操作,名称更改将不再匹配,并且加载程序将无法找到有问题的功能。

我不想为所有这些对象编写代码,因为a)有成千上万个b)导致虚函数和析构函数出现问题。

我正在考虑使用objcopy并在libfoo.so中重命名所有导出的符号,但是希望这里有更好的解决方案。

libfoo和我的代码都使用C++ 14(可以移至C++ 17)和gcc 7(可以移至更高版本)。在Linux上针对64位ELF进行了编译。

最佳答案

最终结果比预期的要复杂得多,但我确实做到了。如果libfoo.so具有函数Bar::f()(被混合为_ZN3Bar1fEv),并且我想定义foo::Bar::f()(_ZN3foo3Bar1fEv),则首先需要在libfoo.so中找到函数的相对偏移量。使用nm,您将获得类似以下内容的信息:

0000000000abcd00 T _ZN3Bar1fEv

我有一个脚本可以解析此输出,并生成如下汇编代码:
; Export start of the translation space
.global START
START:
    nop

.global _ZN3foo3Bar1fEv ; Desired target name
_ZN3foo3Bar1fEv:
    movq $0x0000abcd12345678, %rax ; arbitrary constant
    addq $0xabcd00, %rax ; offset from libfoo
    pushq %rax
    ret

; same for every other needed symbol

; Export end of the translation space
.global END
END:
    nop

这定义了一个具有所需名称的“裸”函数,该函数将一个任意常量添加到偏移量并跳转到该偏移量。额外的跳跃是我可以忍受的最佳表现。

处理ASLR需要任意常量-0xabcd00是基本偏移量,但是将libfoo.so加载到某个基本地址(例如0x04000000)时,函数的实际地址(即是否需要函数指针)是0x04abde00。生成asm代码时,我使用了一个临时常量,该常量将在加载后进行修复:
const uintptr_t offset = 0xabcd00; // Expected offset in libfoo.so
uintptr_t real = (uintptr_t) dlsym(RTLD_DEFAULT, "_ZN3Bar1fEv"); // real address with ASLR
uintptr_t aslr_base = real - offset; // This is the actual base to be applied.

// Search everything between START and END and update the arbitrary constant
extern "C" void START(); extern "C" void END();
uint8_t *p = (uint8_t*)&START;
uint8_t *end = (uint8_t*)&END;
mprotect(ALIGN_TO_PAGE(p), end-p+PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC); // mark the memory as writable
// update all instances of the arbitrary constant
while (p != end)
{
    if (*(uintptr_t*)p == 0x0000abcd12345678)
        *(uintptr_t*)p = aslr_base;
    p++;
}

然后,您可以恢复内存保护。通过仅使用raxpush进行计算,并使用mov进行间接寻址,可以将asm优化为不使用rsp,但这适用于我的情况。

什么不起作用
  • objcopy允许重新定义或注入(inject)静态符号,但不支持编辑动态符号表。我发现的任何工具都没有。
  • 链接器脚本允许定义别名(PROVIDE(alias = real)),但前提是在链接时定义了目标。您不能为动态符号创建别名。
  • 从全局变量中获取ASLR偏移量不起作用-链接器一直要求使用-fPIC编译所有代码。这样就无需修改代码。
  • 关于c++ - 将类从共享库导入特定的 namespace ,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/57738060/

    10-13 04:35