加载时链接期间的符号地址与Linux中的运行时链接

时间:2017-06-08 06:48:33

标签: c++ c linux gcc dynamic-linking

我试图理解基于加载时间链接(使用RUN npm install)与Linux中动态库的运行时链接(使用ADD .)的机制之间的差异,以及这些机制如何影响图书馆的状态及其符号的地址。

实验

我有三个简单的文件:

libhello.c:

gcc -l

libhello.h:

dlopen(), dlsym()

main.c中:

int var;
int func() {
    return 7;
}

我使用命令extern int var; int func();

编译libhello.c

我使用命令#include <inttypes.h> #include <stdio.h> #include <stdint.h> #include <dlfcn.h> #include "libhello.h" int main() { void* h = dlopen("libhello.so", RTLD_NOW); printf("Address Load-time linking Run-time linking\n"); printf("------- ----------------- ----------------\n"); printf("&var 0x%016" PRIxPTR " 0x%016" PRIxPTR "\n", (uintptr_t)&var , (uintptr_t)dlsym(h, "var" )); printf("&func 0x%016" PRIxPTR " 0x%016" PRIxPTR "\n", (uintptr_t)&func, (uintptr_t)dlsym(h, "func")); }

编译main.c

观察

运行main.c可执行文件打印如下:

gcc -shared -o libhello.so -fPIC libhello.c

加载时链接地址保持不变,但运行时链接地址会在每次运行时发生变化。

问题

  1. 为什么运行时地址每次运行都会改变?它们是否因Address space layout randomization而发生变化?
  2. 如果是这种情况,为什么不解决加载时链接的变化?是否负载时间链接容易受到针对随机化的相同攻击,目的是防范?
  3. 在上面的程序中,相同的库被加载两次 - 一次在加载时,然后在运行时使用gcc main.c -L. -lhello -ldl。第二个加载不会复制第一个加载的状态。即如果在Address Load-time linking Run-time linking ------- ----------------- ---------------- &var 0x0000000000601060 0x00007fdb4acb1034 &func 0x0000000000400700 0x00007fdb4aab0695 之前更改dlopen()的值,则此值不会反映在通过var加载的dlopen()版本中。在第二次加载期间有没有办法保留这种状态?

4 个答案:

答案 0 :(得分:13)

  1. 是的,它是ASLR。

  2. 因为PIE(位置无关可执行文件)非常昂贵(性能方面)。因此,许多系统都会对库进行权衡,因为它们必须独立于位置,但不要将可执行文件随机化,因为它会花费太多的性能。是的,这种方式更容易受到攻击,但大多数安全性都是一种权衡。

  3. 是的,不要通过句柄搜索符号,而是使用RTLD_DEFAULT。像这样加载相同动态库的两个实例通常是个坏主意。有些系统可以跳过在dlopen中加载库,如果他们知道已经加载了相同的库,那么动态链接器会考虑同一个库&#34;可以根据您的库路径进行更改。你在这里非常严重/弱定义的行为领域,多年来一直在进化以处理错误和问题,而不是通过深思熟虑的设计。

  4. 请注意RTLD_DEFAULT将返回主可执行文件中的符号地址或第一个(加载时间)加载的动态库,并且将忽略动态加载的库。

    另外,值得记住的另一件事是,如果你在libhello中引用var它将始终从库的加载时版本解析符号,即使在dlopen:ed版本中也是如此。我修改了func以返回var并将此代码添加到您的示例代码中:

    int (*fn)(void) = dlsym(h, "func");
    int *vp;
    
    var = 17;
    printf("%d %d %d %p\n", var, func(), fn(), vp);
    
    vp = dlsym(h, "var");
    *vp = 4711;
    printf("%d %d %d %p\n", var, func(), fn(), vp);
    
    vp = dlsym(RTLD_DEFAULT, "var");
    *vp = 42;
    printf("%d %d %d %p\n", var, func(), fn(), vp);
    

    并获得此输出:

    $ gcc main.c -L. -lhello -ldl && LD_LIBRARY_PATH=. ./a.out
    17 17 17 0x7f2e11bec02c
    17 17 17 0x7f2e11bec02c
    42 42 42 0x601054
    Address  Load-time linking    Run-time linking
    -------  -----------------    ----------------
    &var     0x0000000000601054   0x0000000000601054
    &func    0x0000000000400700   0x0000000000400700
    

答案 1 :(得分:4)

您看到的内容取决于许多变量。在Debian 64bit上我第一次尝试

Address  Load-time linking    Run-time linking
-------  -----------------    ----------------
&var     0x0000000000600d58   0x0000000000600d58
&func    0x00000000004006d0   0x00000000004006d0

这意味着,dlopen使用了已经链接的库,您的系统似乎没有这样做。要利用ASLR,您需要使用位置独立代码编译main.cgcc -fPIC main.c ./libhello.so -ldl

Address  Load-time linking    Run-time linking
-------  -----------------    ----------------
&var     0x00007f4e6cec6944   0x00007f4e6cec6944
&func    0x00007f4e6ccc6670   0x00007f4e6ccc6670

答案 2 :(得分:1)

我希望这个提示可以帮到你。

  1. 主程序是ELF文件,需要重定位。并且重定位发生在加载时。因此,在调用dlsym之前,主程序中的var和func地址已重新定位。

  2. dlsym func在没有重定位的情况下返回OS广告运行时中的符号地址,该地址位于SO映射区域。

  3. 您可以使用映射信息查找不同的内容:

    wutiejun@linux-00343520:~/Temp/sotest> LD_LIBRARY_PATH=./ ./test
    Address  Load-time linking    Run-time linking
    -------  -----------------    ----------------
    &var     0x000000000804a028   0x00000000f77a9014
    &func    0x0000000008048568   0x00000000f77a744c
    
    
    wutiejun@linux-00343520:~> cat /proc/7137/maps
    08048000-08049000 r-xp 00000000 08:02 46924194                           /home/wutiejun/Temp/sotest/test
    08049000-0804a000 r--p 00000000 08:02 46924194                           /home/wutiejun/Temp/sotest/test
    0804a000-0804b000 rw-p 00001000 08:02 46924194                           /home/wutiejun/Temp/sotest/test
    0804b000-0806c000 rw-p 00000000 00:00 0                                  [heap]
    f75d3000-f7736000 r-xp 00000000 08:02 68395411                           /lib/libc-2.11.3.so
    f7736000-f7738000 r--p 00162000 08:02 68395411                           /lib/libc-2.11.3.so
    f7738000-f7739000 rw-p 00164000 08:02 68395411                           /lib/libc-2.11.3.so
    f7739000-f773c000 rw-p 00000000 00:00 0
    f773c000-f7740000 r-xp 00000000 08:02 68395554                           /lib/libachk.so
    f7740000-f7741000 r--p 00003000 08:02 68395554                           /lib/libachk.so
    f7741000-f7742000 rw-p 00004000 08:02 68395554                           /lib/libachk.so
    f777a000-f777c000 rw-p 00000000 00:00 0
    f777c000-f7784000 r-xp 00000000 08:02 68395441                           /lib/librt-2.11.3.so
    f7784000-f7785000 r--p 00007000 08:02 68395441                           /lib/librt-2.11.3.so
    f7785000-f7786000 rw-p 00008000 08:02 68395441                           /lib/librt-2.11.3.so
    f7786000-f779d000 r-xp 00000000 08:02 68395437                           /lib/libpthread-2.11.3.so
    f779d000-f779e000 r--p 00016000 08:02 68395437                           /lib/libpthread-2.11.3.so
    f779e000-f779f000 rw-p 00017000 08:02 68395437                           /lib/libpthread-2.11.3.so
    f779f000-f77a2000 rw-p 00000000 00:00 0
    f77a2000-f77a5000 r-xp 00000000 08:02 68395417                           /lib/libdl-2.11.3.so
    f77a5000-f77a6000 r--p 00002000 08:02 68395417                           /lib/libdl-2.11.3.so
    f77a6000-f77a7000 rw-p 00003000 08:02 68395417                           /lib/libdl-2.11.3.so
    f77a7000-f77a8000 r-xp 00000000 08:02 46924193                           /home/wutiejun/Temp/sotest/libhello.so
    f77a8000-f77a9000 r--p 00000000 08:02 46924193                           /home/wutiejun/Temp/sotest/libhello.so
    f77a9000-f77aa000 rw-p 00001000 08:02 46924193                           /home/wutiejun/Temp/sotest/libhello.so
    f77aa000-f77ab000 rw-p 00000000 00:00 0
    f77ab000-f77ca000 r-xp 00000000 08:02 68395404                           /lib/ld-2.11.3.so
    f77ca000-f77cb000 r--p 0001e000 08:02 68395404                           /lib/ld-2.11.3.so
    f77cb000-f77cc000 rw-p 0001f000 08:02 68395404                           /lib/ld-2.11.3.so
    ffd99000-ffdba000 rw-p 00000000 00:00 0                                  [stack]
    ffffe000-fffff000 r-xp 00000000 00:00 0                                  [vdso]
    wutiejun@linux-00343520:~>
    

答案 3 :(得分:0)

在我看来,我会说:

  • 当您使用可执行文件(静态链接)直接编译库时,请认为函数将直接注入源代码中。如果检查可执行文件,您将看到每个部分(代码,数据......)将具有固定的&#34;虚拟内存&#34;地址。如果我记得很清楚,每个Linux可执行文件将从默认地址0x100000开始,因此您将看到每个静态链接函数将具有固定地址(0x100000 +固定偏移量)并且永远不会更改。每次加载可执行文件时,每个特定函数都将加载到虚拟内存中的精确地址,这意味着操作系统将决定使用哪个物理地址,但您不会看到它。在您的示例中, var 变量将始终具有0x0000000000601060的虚拟地址,但您永远不会知道它将驻留在物理内存中的哪个位置。

  • 在运行时加载动态库时,操作系统已将可执行文件加载到内存中,因此您不会拥有虚拟固定地址。相反,OS在可执行地址空间中保留一系列虚拟地址,从0x00007fxxxxxxxxxx开始,它将加载并映射新加载的符号和函数。根据已加载的内容和内存随机化算法,每次运行时这些地址可能不同。

鉴于这个简短的解释,可以很简单地假设你在点3)中进行比较的两个值是完全不同的变量(每个变量都加载在不同的内存位置),因此它们具有不同的值并且没有互动。