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

use*_*104 48 c linux glibc shared-libraries dlopen

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

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

Emp*_*ian 49

使用-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
$
Run Code Online (Sandbox Code Playgroud)

PS glibc write(3)通过系统调用实现,因为它没有其他地方可以调用(它已经是最低级别).这与能够执行无关libc.so.6.

  • 我发现`-shared`选项可能会使它无法工作,并给您分段错误。您必须确保不存在`-shared`选项,或将其放在`-pie`之前,以便忽略它。 (2认同)

And*_*gan 13

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

这个工作示例涉及 5 个文件,用于展示一些相应测试的工作原理。

首先,考虑这个简单的程序(称之为empty.c):

int main(int argc, char **argv) { return 0; }
Run Code Online (Sandbox Code Playgroud)

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

$ 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
Run Code Online (Sandbox Code Playgroud)

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

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

/* multi.h */
void multi_main(void);
void multi(const char *caller);
Run Code Online (Sandbox Code Playgroud)

/* 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 ;
Run Code Online (Sandbox Code Playgroud)

(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
Run Code Online (Sandbox Code Playgroud)

因此,这是.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__);
}
Run Code Online (Sandbox Code Playgroud)

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

$ gcc -o opener opener.c -ldl
$ ./opener
called from opener.c
Run Code Online (Sandbox Code Playgroud)

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

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

int main(int argc, char **argv) {
    multi(__FILE__);
}
Run Code Online (Sandbox Code Playgroud)

我们编译并运行它如下:

$ gcc main.c -o main multi.so
$ LD_LIBRARY_PATH=./ ./main
called from main.c
Run Code Online (Sandbox Code Playgroud)

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