取消保护库中 ELF 部分的开始和结束,以覆盖链接程序

Unprotect start and end of ELF section in library, to override from linked program

我想获取库中某个部分的开始和结束指针,以一种可以从该程序链接到的程序中覆盖它的方式。

这让我可以在程序中指定一些关于库应该如何加载的参数。这是一个具体的例子:

foo.c,图书馆:

#include <stdio.h>

typedef void (*fptr)();
void lib_function();

void dummy()
{
    printf("NO -- I should be overriden by prog_function\n");
}

fptr section_fptrlist __attribute__((weak, section("fptrlist"))) = (fptr)dummy;
extern fptr __start_fptrlist;
extern fptr __stop_fptrlist;

void __attribute__((constructor)) setup()
{
    // setup library: call pre-init functions;
    for (fptr *f = &__start_fptrlist; f != &__stop_fptrlist; f++)
        (*f)();
}

void lib_function()
{
}

bar.c,程序:

#include <stdio.h>

void lib_function();
typedef void (*fptr)();


void pre_init()
{
    printf("OK -- run me from library constructor\n");
}

fptr section_fptrlist __attribute__((section("fptrlist"))) = (fptr)pre_init;

int main()
{
    lib_function();
    return 0;
}

我从 foo.c 构建 libfoo.so,然后从 bar.clibfoo.so 构建一个测试程序,例如:

gcc -g -O0    -fPIC -shared foo.c -o libfoo.so
gcc -g -O0    bar.c -L. -lfoo -o test

这曾经工作得很好,即使用 ld 版本 2.26.1 我得到了预期的结果:

$ ./test 
OK -- run me from library constructor

现在使用 ld 版本 2.29.1 我得到:

$ ./test 
NO -- I should be overriden by prog_function

我在一台机器上编译了所有东西,只是通过将目标文件复制到另一台机器来更改链接器步骤,运行 ld -shared foo.o -o libfoo.so 然后将库复制回来,就我而言可以告诉链接器这是工作和不工作之间的唯一区别。

我进一步使用 gcc 7.2.0 和 glibc 2.22-62,但如上所述,这似乎不是决定性的。链接器脚本的差异似乎很小,使用一个而不是另一个似乎对结果没有任何影响(2.26 和 2.29 的脚本确实正常工作,2.29 和 2.26 的脚本没有)。无论如何,这是差异:

--- ld_script_v2.26     2018-02-02 21:52:56.038573732 +0100
+++ ld_script_v2.29     2018-02-02 21:52:41.154504340 +0100
@@ -1,4 +1,4 @@
@@ -53,5 +53,5 @@ SECTIONS
   .plt            : { *(.plt) *(.iplt) }
 .plt.got        : { *(.plt.got) }
-.plt.bnd        : { *(.plt.bnd) }
+.plt.sec        : { *(.plt.sec) }
   .text           :
   {
@@ -226,4 +226,5 @@ SECTIONS
   /* DWARF Extension.  */
   .debug_macro    0 : { *(.debug_macro) }
+  .debug_addr     0 : { *(.debug_addr) }
   .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
   /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }

查看动态符号 table(使用 readelf -Ws)我注意到在 2.29 版本中,符号现在受到保护:

with ld 2.29> readelf -Ws libfoo.so  | grep fptr
 8: 0000000000201028     0 NOTYPE  GLOBAL PROTECTED   24 __start_fptrlist
14: 0000000000201028     8 OBJECT  WEAK   DEFAULT     24 section_fptrlist
16: 0000000000201030     0 NOTYPE  GLOBAL PROTECTED   24 __stop_fptrlist
54: 0000000000201028     8 OBJECT  WEAK   DEFAULT     24 section_fptrlist
58: 0000000000201028     0 NOTYPE  GLOBAL PROTECTED   24 __start_fptrlist
62: 0000000000201030     0 NOTYPE  GLOBAL PROTECTED   24 __stop_fptrlist
whith ld 2.26> readelf -Ws libfoo.so | grep fptrlist                                                                                                                                                                                                    
 9: 0000000000201028     0 NOTYPE  GLOBAL DEFAULT   23 __start_fptrlist
15: 0000000000201028     8 OBJECT  WEAK   DEFAULT   23 section_fptrlist
17: 0000000000201030     0 NOTYPE  GLOBAL DEFAULT   23 __stop_fptrlist
53: 0000000000201028     8 OBJECT  WEAK   DEFAULT   23 section_fptrlist
57: 0000000000201028     0 NOTYPE  GLOBAL DEFAULT   23 __start_fptrlist
61: 0000000000201030     0 NOTYPE  GLOBAL DEFAULT   23 __stop_fptrlist

我知道,from this answer, that the feature I was relying on is more shady that well defined. I was able to track down the fact that this change was intentional。现在对我来说实现从我的库设置调用程序函数的目标的最佳方法是什么? 我还能使这种方法起作用吗?例如,有没有办法取消对这些符号的保护?

虽然这个例子很小,但这个问题实际上发生在一个相当大的C++项目中,所以改动越少越好。

我觉得问题是

for (fptr *f = &__start_fptrlist; f != &__stop_fptrlist; f++)
    (*f)();

此处的 for 循环预计会经过您应用中定义的 __start_fptrlist__stop_fptrlist。虽然 .protected 使这些符号从 .so 本身解析出来。

一个简单的解决方法是:

// foo.c
/* ... */
fptr *my_start = &__start_fptrlist;
fptr *my_stop = &__stop_fptrlist;

void __attribute__((constructor)) setup()
{
    // setup library: call pre-init functions;
    for (fptr *f = my_start; f != my_stop; f++)
        (*f)();
}

此处 my_* 函数的确切值并不重要,因为这两个名称应该绑定到 app.

的值
// bar.c
/* ... */
fptr section_fptrlist __attribute__((section("fptrlist"))) = (fptr)pre_init;

extern fptr __start_fptrlist;
extern fptr __stop_fptrlist;

fptr *my_start = &__start_fptrlist;
fptr *my_stop = &__stop_fptrlist;

这将强制您的 for 循环通过您应用中的地址而不是您的 .so。因为 my_* 符号是全局的,它们将首先从 app.

中解析

警告:代码未经测试,因为我没有您描述的环境。请告诉我这种方法是否适用于您的机器。