构建一个.so,它也是一个可执行文件

问题描述 投票:0回答:4

所以大家可能都知道 glibc 的

/lib/libc.so.6
可以像普通的可执行文件一样在 shell 中执行,在这种情况下它会打印版本信息并退出。这是通过在 .so 中定义入口点来完成的。对于某些情况,将其用于其他项目也可能很有趣。不幸的是,您可以通过 ld 的 -e 选项设置的低级入口点有点太低级:动态加载器不可用,因此您无法调用任何正确的库函数。因此 glibc 在这个入口点通过裸系统调用实现 write() 系统调用。

我现在的问题是,任何人都可以想出一种好方法,如何从该入口点引导完整的动态链接器,以便可以访问其他 .so 的函数?

c linux shared-libraries glibc dlopen
4个回答
57
投票

更新2:参见Andrew G Morgan的稍微复杂的解决方案,它确实适用于任何GLIBC(该解决方案也用于

libc.so.6
本身(从永远),这就是为什么你可以将它运行为
./libc.so.6
(它打印以这种方式调用时的版本信息))。

更新 1: 这不再适用于较新的 GLIBC 版本:

./a.out: error while loading shared libraries: ./pie.so: cannot dynamically load position-independent executable

2009年的原始答案:

使用

-pie
选项构建共享库似乎可以为您提供您想要的一切:

/* pie.c */
#include <stdio.h>
int foo()
{
  printf("in %s %s:%d\n", __func__, __FILE__, __LINE__);
  return 42; 
}
int main() 
{ 
  printf("in %s %s:%d\n", __func__, __FILE__, __LINE__);
  return foo(); 
}


/* main.c */
#include <stdio.h>

extern int foo(void);
int main() 
{ 
  printf("in %s %s:%d\n", __func__, __FILE__, __LINE__);
  return foo(); 
}


$ gcc -fPIC -pie -o pie.so pie.c -Wl,-E
$ gcc main.c ./pie.so


$ ./pie.so
in main pie.c:9
in foo pie.c:4
$ ./a.out
in main main.c:6
in foo pie.c:4
$

附注glibc 通过系统调用实现

write(3)
,因为它没有其他地方可以调用(它已经是最低级别)。这与能否执行无关
libc.so.6


16
投票

我一直在寻求向

pam_cap.so
添加对此的支持,并发现了这个问题。正如 @EmployedRussian 在他们自己的帖子的后续文章中指出的那样,所接受的答案在某个时候停止工作。花了一段时间才弄清楚如何再次使其工作,所以这是一个有效的示例。

这个工作示例涉及 5 个文件,通过一些相应的测试来展示如何工作。

首先,考虑这个简单的程序(称之为

empty.c
):

int main(int argc, char **argv) { return 0; }

编译它,我们可以看到它如何解析我系统上的动态符号,如下所示:

$ gcc -o empty empty.c
$ objcopy --dump-section .interp=/dev/stdout empty ; echo
/lib64/ld-linux-x86-64.so.2
$ DL_LOADER=/lib64/ld-linux-x86-64.so.2

最后一行设置了一个 shell 变量以供稍后使用。

这是构建我的示例共享库的两个文件:

/* multi.h */
void multi_main(void);
void multi(const char *caller);

/* multi.c */
#include <stdio.h>
#include <stdlib.h>
#include "multi.h"

void multi(const char *caller) {
    printf("called from %s\n", caller);
}

__attribute__((force_align_arg_pointer))
void multi_main(void) {
    multi(__FILE__);
    exit(42);
}

const char dl_loader[] __attribute__((section(".interp"))) =
    DL_LOADER ;

(2021 年 11 月 13 日更新:强制对齐是为了 帮助

__i386__
代码与 SSE 兼容 - 没有它,我们将很难调试
glibc
SIGSEGV
崩溃。)

我们可以按如下方式编译并运行它:

$ gcc -fPIC -shared -o multi.so -DDL_LOADER="\"${DL_LOADER}\"" multi.c -Wl,-e,multi_main
$ ./multi.so
called from multi.c
$ echo $?
42

因此,这是一个

.so
,可以作为独立的二进制文件执行。接下来,我们验证它是否可以作为共享对象加载。

/* opener.c */
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {
    void *handle = dlopen("./multi.so", RTLD_NOW);
    if (handle == NULL) {
        perror("no multi.so load");
        exit(1);
    }
    void (*multi)(const char *) = dlsym(handle, "multi");
    multi(__FILE__);
}

也就是说,我们动态加载共享对象并从中运行一个函数:

$ gcc -o opener opener.c -ldl
$ ./opener
called from opener.c

最后,我们链接到这个共享对象:

/* main.c */
#include "multi.h"

int main(int argc, char **argv) {
    multi(__FILE__);
}

我们编译运行的地方如下:

$ gcc main.c -o main multi.so
$ LD_LIBRARY_PATH=./ ./main
called from main.c

(注意,因为

multi.so
不在标准系统库位置,我们需要使用
LD_LIBRARY_PATH
环境变量覆盖运行时查找共享对象文件的位置。)


1
投票

我想您会将

ld -e
指向一个入口点,然后该入口点将使用
dlopen()
函数系列来查找并引导动态链接器的其余部分。 当然,您必须确保
dlopen()
本身是静态链接的,或者您可能必须实现足够的自己的链接器存根才能获取它(使用诸如
mmap()
之类的系统调用接口,就像 libc 本身所做的那样。

这些对我来说听起来都不“好”。 事实上,仅仅阅读 glibc 源代码(以及

ld-linux
源代码,作为一个例子)就足以评估工作规模的想法对我来说听起来很老套。 这也可能是一个可移植性的噩梦。 Linux 实现
ld-linux
的方式与 OpenSolaris、FreeBSD 等下完成链接的方式可能存在重大差异。 (我不知道)。


0
投票

对于那些像我一样对 glibc 取消支持感到困扰的人,我通过查看原始补丁找到了一个(肮脏的)解决方案。它检查 FLAGS_1 中的 DF_1_PIE,因此我正在修补二进制文件以从 FLAGS_1 中删除 DF_1_PIE,这足以让 ld.so 高兴。我在我的工具之一的以下补丁中完成了此操作:http://git.formilux.org/?p=people/willy/nousr.git;a=commitdiff;h=f4ffb101

请注意,这涉及使用 sed 进行二进制修补,因此它只能凭经验工作,因为我注意到所有其他标志都为零,但如果设置了其他标志,可能无法工作,也许在其他架构上,可能在 32 位上,并且肯定会不适用于大端。但它让我再次支持该实用程序:

$ LD_PRELOAD=./nousr.so ls
Makefile  README  nousr.c  nousr.so*  nousr.so-p1  nousr.so-p2  nousr.so-p2-patched  nousr.so-p3  patch-it.txt
$ ./nousr.so ls
Makefile  README  nousr.c  nousr.so  nousr.so-p1  nousr.so-p2  nousr.so-p2-patched  nousr.so-p3  patch-it.txt

它当然可以改进以匹配其他块并复制它们,但我现在不在乎。对于那些想要手动尝试的人,构建一个名为 foo.so 的共享对象并应用命令:

gcc -shared -fPIC -c foo.c
gcc -pie -o foo.so foo.o
# LD_PRELOAD=./foo.so ls
# ERROR: ld.so: object './foo.so' from LD_PRELOAD cannot be preloaded (cannot open shared object file): ignored.

set -- $(objdump -j .dynamic -h foo.so | fgrep .dynamic)
size=$3; ofs=$6
dd if=foo.so of=foo.so-p1 bs=1 count=$((0x$ofs))
dd if=foo.so of=foo.so-p2 bs=1 skip=$((0x$ofs)) count=$((0x$size))
dd if=foo.so of=foo.so-p3 bs=1 skip=$((0x$ofs+0x$size))
sed -e 's,\xfb\xff\xff\x6f\x00\x00\x00\x00\x00\x00\x00\x08,\xfb\xff\xff\x6f\x00\x00\x00\x00\x00\x00\x00\x00,g' < foo.so-p2 > foo.so-p2-patched 
cat foo.so{-p1,-p2-patched,-p3} > foo2.so
chmod 755 foo2.so
# LD_PRELOAD=./foo.so ls
# foo.c foo.o foo.so foo2.so ...

希望这可以帮助某人,因为我发现这非常烦人,并且所提出的解决方案都不适合我:-/

© www.soinside.com 2019 - 2024. All rights reserved.