在 strace 输出中找不到 ld-linux-x86-64.so.2

逆向工程 二元分析 动态分析 动态链接
2021-06-27 12:14:12

我在 /bin/cat 上做了一个 ldd,我看到动态加载程序库 /lib64/ld-linux-x86-64.so.2 是其中的一部分。

ldd /bin/cat
    linux-vdso.so.1 (0x00007ffe743f4000)
    libc.so.6 => /lib64/libc.so.6 (0x00007fde4f0a1000)
    /lib64/ld-linux-x86-64.so.2 (0x000056057639c000)

但是,当我对这个二进制文件 ( strace -o cat.trace /usr/bin/ls /etc/motd )执行 strace并且找不到它正在加载时,我的问题就开始了我假设它应该是内核加载的第一个。

execve("/usr/bin/ls", ["/usr/bin/ls", "/etc/motd"], 0x7ffe9e6a8d38 /* 56 vars */) = 0
brk(NULL)                               = 0x5650331a2000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f39527b9000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=95294, ...}) = 0
mmap(NULL, 95294, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f39527a1000
close(3)                                = 0
open("/lib64/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0000c\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=153176, ...}) = 0
mmap(NULL, 2253688, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f3952370000
mprotect(0x7f3952393000, 2097152, PROT_NONE) = 0
mmap(0x7f3952593000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x23000) = 0x7f3952593000
mmap(0x7f3952595000, 4984, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f3952595000
close(3)                                = 0
open("/lib64/libcap.so.2", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\25\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=18608, ...}) = 0
mmap(NULL, 2113840, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f395216b000
mprotect(0x7f395216f000, 2093056, PROT_NONE) = 0
mmap(0x7f395236e000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3000) = 0x7f395236e000
close(3)                                = 0
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\10\2\0\0\0\0\0"..., 832) = 832

从 ldd 输出可以看出,这里的一个补充问题是 - ldd 如何在加载库之前预测动态链接/加载的库的加载地址?在我研究的理论上,内存是共享资源,然后多个库应该能够加载到相同的内存位置(尽管不是同时)。

1个回答

在 stackoverflow 上可以找到一个与此非常相似的问题:如何 execve 调用动态链接器/加载器 (ld-linux.so.2)

没有strace与动态链接器相关联的输出的原因是在内核创建进程期间将控制传递给动态链接器。换句话说,动态链接器不是通过系统调用从用户空间调用的。没有系统调用意味着没有strace输出。

来自通用 SYSV ABI,第 5 章:“程序加载和动态链接”::

参与动态链接的可执行文件应具有一个PT_INTERP程序头元素。在函数执行期间exec,系统从PT_INTERP段中检索路径名并从解释器文件的段中创建初始过程映像。也就是说,系统不使用原始可执行文件的段映像,而是为解释器组合内存映像。然后解释器负责从系统接收控制并为应用程序提供环境。

正如处理器补充第 3 章中提到的“进程初始化”,解释器以两种方式之一接收控制。首先,它可能会接收一个文件描述符来读取位于开头的可执行文件。它可以使用这个文件描述符来读取和/或将可执行文件的段映射到内存中。其次,根据可执行文件格式,系统可能会将可执行文件加载到内存中,而不是给解释器一个打开的文件描述符。除了文件描述符之外,解释器的初始进程状态与可执行文件接收到的内容相匹配。口译员本身可能不需要第二个口译员。解释器可以是共享对象或可执行文件。

上面使用的术语“系统”指的是内核。

$ readelf -l /bin/cat

Elf file type is EXEC (Executable file)
Entry point 0x402602
There are 9 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                 0x00000000000001f8 0x00000000000001f8  R E    8
  INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238
                 0x000000000000001c 0x000000000000001c  R      1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]

INTERP程序头很重要,因为内核在将二进制文件映射到内存以创建正在运行的进程时使用程序头。可以推断,程序头中动态链接器的路径名的存在对内核的进程创建有影响。

在构建使用动态链接的可执行文件时,链接编辑器会PT _ INTERP向可执行文件添加 type 类型的程序头元素,告诉系统调用动态链接器作为程序解释器。

exec 和动态链接器合作为程序创建进程映像,这需要以下操作:

  • 将可执行文件的内存段添加到进程映像中;
  • 向进程映像添加共享对象内存段;
  • 对可执行文件及其共享对象执行重定位;
  • 关闭用于读取可执行文件的文件描述符(如果已提供给动态链接器);
  • 将控制权转移给程序,使程序看起来好像直接从函数中获得控制权 exec

回复评论

我在 NetBSD 机器上尝试了同样的东西,在那里我看到 ld.so 的映射是第一步。所以这基本上意味着这取决于实现,无论我们是否会在 strace 中看到动态链接器。对?

错误,至少根据NetBSD 进程启动文档

请注意,当启动动态 ELF 可执行文件时,ELF 加载器(也称为解释器/usr/libexec/ld.elf_so:)由内核加载可执行文件。ELF 加载器由内核启动,并负责随后启动可执行文件本身。

为了获得更好的答案,您必须专门针对此提出一个单独的问题,并包含strace输出。

ldd即使没有加载它,如何提及库的基址?

ldd调用动态链接器,它加载动态链接程序的动态依赖项。ldd手册页:

在通常情况下,ldd调用标准动态链接器(请参阅ld.so(8)),并将LD_TRACE_LOADED_OBJECTS环境变量设置为 1。这会导致动态链接器检查程序的动态依赖项,并查找(根据 中描述的规则ld.so(8))并加载满足这些条件的对象依赖关系。对于每个依赖项,ldd显示匹配对象的位置和加载它的(十六进制)地址。linux-vdsold-linux共享依赖项是特殊的;请参阅vdso(7)ld.so(8)。)

在某些情况下ldd甚至会执行二进制文件:

请注意,在某些情况下(例如,程序指定了 ld-linux.so 以外的 ELF 解释器),某些版本ldd可能会尝试通过尝试直接执行程序来获取依赖信息(这可能会导致执行程序的 ELF 解释器中定义的任何代码,也可能是程序本身的执行)。因此,永远不要在不受信任的可执行文件上使用 ldd,因为这可能会导致执行任意代码。

ldd 确实会导致程序的动态依赖项被加载到虚拟内存中。