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

时间:2018-02-02 21:39:26

标签: linux linker elf sections binutils

我想获取库中某个部分的开始和结束指针,以便可以从程序所链接的程序中覆盖它。

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

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;
}

我从libfoo.so构建foo.c,然后从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&#39; s脚本确实作为例外,2.29与2.26&#39; s脚本没有)。无论如何,这就是差异:

--- 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_*) }

查看动态符号表(带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,我依赖的功能更加阴暗,定义明确。我能够追踪到this change was intentional的事实。现在,对于我来说,实现从库设置中调用程序功能的目标,最好的方法是什么? 我还能使这种方法有效吗?有没有办法取消保护这些符号?

即使这个例子很小,这个问题实际上发生在一个相当大的C ++项目中,所以变化越小越好。

1 个答案:

答案 0 :(得分:2)

我认为问题是

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_*函数的确切值并不重要,因为这两个名称应该绑定到a​​pp的值。

// 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_*符号是全局的,所以它们将首先从应用中解析。

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