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

C++ Linux中加载时链接与运行时链接期间的符号地址,c++,c,linux,gcc,dynamic-linking,C++,C,Linux,Gcc,Dynamic Linking,我试图了解Linux中动态库的加载时链接(使用gcc-l)与运行时链接(使用dlopen(),dlsym())的底层机制的区别,以及这些机制如何影响库的状态及其符号的地址 实验 我有三个简单的文件: libhello.c: int var; int func() { return 7; } libhello.h: extern int var; int func(); 主要条款c: #include <inttypes.h> #include <stdio.h>

我试图了解Linux中动态库的加载时链接(使用
gcc-l
)与运行时链接(使用
dlopen(),dlsym()
)的底层机制的区别,以及这些机制如何影响库的状态及其符号的地址

实验 我有三个简单的文件:

libhello.c:

int var;
int func() {
    return 7;
}
libhello.h:

extern int var;
int func();
主要条款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"));
}
加载时链接地址保持不变,但每次运行时运行时链接地址都会更改

问题
  • 为什么每次运行时运行时地址都会更改?它们是否因以下原因而改变
  • 如果是这种情况,为什么不更改加载时间链接的地址?加载时间链接不容易受到处理随机化目的的攻击吗
  • 在上面的程序中,同一个库被加载两次—加载时加载一次,然后在运行时使用
    dlopen()
    。第二个加载不会复制第一个加载的状态。也就是说,如果在
    dlopen()
    之前更改了
    var
    的值,则该值不会反映在通过
    dlsym()
    加载的
    var
    版本中。有没有办法在第二次加载期间保持此状态

  • 在我看来,我要说:

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

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


    给出这个简短的解释后,很容易假设在第3点中比较的两个值是完全不同的变量(每个值加载在不同的内存位置),因此它们具有不同的值并且不会相互作用。

    您看到的取决于许多变量。这是我第一次尝试的Debian 64位

    Address  Load-time linking    Run-time linking
    -------  -----------------    ----------------
    &var     0x0000000000600d58   0x0000000000600d58
    &func    0x00000000004006d0   0x00000000004006d0
    
    这意味着,dlopen使用了已经链接的库,而您的系统似乎没有这样做。为了利用ASLR,您需要使用位置无关代码编译
    main.c
    :gcc-fPIC main.c./libhello.so-ldl

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

    我希望这个提示能对你有所帮助

  • 主程序是一个ELF文件,需要重新定位。重新定位发生在装载时。因此,在调用dlsym之前,主程序中的var和func地址已重新定位

  • dlsym func在OS ad运行时返回符号地址,无需重新定位,此地址位于SO映射区域中

  • 您可以使用映射信息查找不同的:

    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:~>
    
  • 是的,是ASLR

  • 因为PIE(位置独立可执行文件)非常昂贵(在性能上)。因此,许多系统都会在随机化库的情况下进行权衡,因为它们无论如何都必须是位置独立的,但不会随机化可执行文件,因为这样会降低性能。是的,这种方式更容易受到攻击,但大多数安全性是一种权衡

  • 是的,不要通过句柄搜索符号,而是使用
    RTLD\u DEFAULT
    。像这样加载同一动态库的两个实例通常不是一个好主意。如果某些系统知道已加载同一个库,并且动态链接器认为“同一个库”的内容可能会因库路径的不同而发生变化,则可以跳过在
    dlopen
    中加载库。在这里,您的行为定义非常糟糕/薄弱,多年来,这些行为更多地是为了处理bug和问题,而不是通过精心设计

  • 请注意,
    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
    

    请注意,OP在这里不执行任何静态链接,他以两种不同的方式链接共享库。是的,你当然是对的。将我的答案留在这里,因为它可能对其他人有帮助。相关,请参阅和。如果你想随机化数据段,那么我相信你需要设置
    随机化空间=2
    ;注意:我使用此命令链接可执行文件:
    gcc-o main.c-L-lhello-ldl-Wl,-rpath$(pwd-L)
    在AIX/PowerPC中也尝试过,这对地址('func'和'var')是相等的。链接命令是:
    gcc-omain.c-L-lhello-ldl-Wl,-brtl,-blibpath:$(pwd-L):/usr/lib
    这比我预想的要微妙得多。
    $ 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