【问题标题】:Get loaded address of a ELF binary, dlopen is not working as expected获取 ELF 二进制文件的加载地址,dlopen 未按预期工作
【发布时间】:2013-10-27 10:09:02
【问题描述】:

我正在尝试获取 ELF 二进制文件的加载地址,but dlopen doesn't 按预期工作:

void *elf = (char *)dlopen (0, RTLD_NOW);
printf ("%p\n", elf);
sleep (100);

它打印0xb772d918,但从/proc/1510/maps 告诉我们,它并不指向dlfn 二进制文件的加载地址,而是ld-2.15.so

08048000-08049000 r-xp 00000000 fc:00 1379       /root/dlfn
08049000-0804a000 r--p 00000000 fc:00 1379       /root/dlfn
0804a000-0804b000 rw-p 00001000 fc:00 1379       /root/dlfn
b7550000-b7552000 rw-p 00000000 00:00 0 
b7552000-b76f5000 r-xp 00000000 fc:00 9275       /lib/i386-linux-gnu/libc-2.15.so
b76f5000-b76f7000 r--p 001a3000 fc:00 9275       /lib/i386-linux-gnu/libc-2.15.so
b76f7000-b76f8000 rw-p 001a5000 fc:00 9275       /lib/i386-linux-gnu/libc-2.15.so
b76f8000-b76fb000 rw-p 00000000 00:00 0 
b76fb000-b76fe000 r-xp 00000000 fc:00 9305       /lib/i386-linux-gnu/libdl-2.15.so
b76fe000-b76ff000 r--p 00002000 fc:00 9305       /lib/i386-linux-gnu/libdl-2.15.so
b76ff000-b7700000 rw-p 00003000 fc:00 9305       /lib/i386-linux-gnu/libdl-2.15.so
b7708000-b770b000 rw-p 00000000 00:00 0 
b770b000-b770c000 r-xp 00000000 00:00 0          [vdso]
b770c000-b772c000 r-xp 00000000 fc:00 9299       /lib/i386-linux-gnu/ld-2.15.so
b772c000-b772d000 r--p 0001f000 fc:00 9299       /lib/i386-linux-gnu/ld-2.15.so
b772d000-b772e000 rw-p 00020000 fc:00 9299       /lib/i386-linux-gnu/ld-2.15.so
bfc34000-bfc55000 rw-p 00000000 00:00 0          [stack]

那么,除了解析/proc/pid/maps,有没有办法检索ELF二进制文件的加载地址? (在这种情况下为 0x0848000)

【问题讨论】:

    标签: c linux dlopen


    【解决方案1】:

    在 Linux 上,dlopen 不会返回加载 ELF 二进制文件的地址。它返回struct link_map,它有.l_addr 成员。所以你会想要这样的东西:

    struct link_map *lm = (struct link_map*) dlopen(0, RTLD_NOW);
    printf("%p\n", lm->l_addr);
    

    然而,尽管/usr/include/link.h 中的评论说,.l_addr 实际上也不是加载地址。相反,它是 ELF 图像链接到加载位置与实际加载位置之间的差异

    对于非 PIE 主可执行文件,差异始终为 0。对于非预链接共享库,差异始终是加载地址(因为非预链接 ELF 共享库链接到地址 0 处加载)。

    那么如何找到主可执行文件的基地址呢?最简单的方法是使用此代码(链接到主可执行文件):

    #ifndef _GNU_SOURCE
    #define _GNU_SOURCE
    #endif
    
    #include <link.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    static int
    callback(struct dl_phdr_info *info, size_t size, void *data)
    {
      int j;
      const char *cb = (const char *)&callback;
      const char *base = (const char *)info->dlpi_addr;
      const ElfW(Phdr) *first_load = NULL;
    
      for (j = 0; j < info->dlpi_phnum; j++) {
        const ElfW(Phdr) *phdr = &info->dlpi_phdr[j];
    
        if (phdr->p_type == PT_LOAD) {
          const char *beg = base + phdr->p_vaddr;
          const char *end = beg + phdr->p_memsz;
    
          if (first_load == NULL) first_load = phdr;
          if (beg <= cb && cb < end) {
            // Found PT_LOAD that "covers" callback().
            printf("ELF header is at %p, image linked at 0x%zx, relocation: 0x%zx\n",
                   base + first_load->p_vaddr, first_load->p_vaddr, info->dlpi_addr);
            return 1;
          }
        }
      }
      return 0;
    }
    
    int
    main(int argc, char *argv[])
    {
      dl_iterate_phdr(callback, NULL);
      exit(EXIT_SUCCESS);
    }
    

    这是您在 32 位系统上应该看到的:

    $ gcc -g t.c -ldl -m32  && ./a.out
    ELF header is at 0x8048000, image linked at 0x8048000, relocation: 0x0
    $ gcc -g t.c -ldl -m32 -pie -fPIE  && ./a.out
    ELF header is at 0xf779a000, image linked at 0x0, relocation: 0xf779a000
    

    (最后一个地址:0xf779a000 如果您启用了地址随机化(如您所愿),则每次运行都会有所不同)。

    【讨论】:

    • 有趣...dlopen(3) 中甚至没有提到。谢谢!
    • 非常有用。如果您想在 Android 上执行此操作,dl_iterate_phdr 可从平台版本 21 开始使用。
    • 如果第一个PT_LOAD段不包含这个函数怎么办?
    • @KelvinHu 我已经更新了代码以在这种情况下工作。
    猜你喜欢
    • 2021-12-04
    • 2011-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-21
    • 2017-06-23
    • 1970-01-01
    相关资源
    最近更新 更多