在 Android 6.0 上读取 Elf32 的 PT_DYNAMIC 个条目时出现内存访问错误

memory access error when reading PT_DYNAMIC entries of a Elf32 on Android 6.0

为了挂钩 Android 应用程序(即 app_process32)的 libc 函数,我首先读取保存在 /proc/self/maps 中的整个地址 space 和每个加载的 ELF,我覆盖匹配的重定位条目。

读取一行 /proc/self/maps:

Elf32_Addr start, end;
sscanf(line, "%8x-%8x", &start, &end);

然后我通过检查魔法来检查它是否是ELF。如果是 ELF,我会读取它的 PT_DYNAMIC 段并循环遍历其条目:

Elf32_Ehdr *ehdr = (Elf32_Ehdr *) start;
Elf32_Phdr *phdr = (Elf32_Phdr *) ((unsigned char *) ehdr + ehdr->e_phoff);
Elf32_Half phnum = ehdr->e_phnum;
Elf32_Addr dynamic = 0;    

for (; phnum > 0; --phnum, ++phdr) {
    if (phdr->p_type == PT_DYNAMIC) {
        dynamic = start + phdr->p_vaddr;
        break;
    }
}

下面是我如何迭代动态条目:

Elf32_Dyn *dyn;
for (dyn = (Elf32_Dyn *) dynamic; dyn->d_tag; dyn++) {
    Elf32_Addr addr = dyn->d_un.d_ptr;
    Elf32_Sword val = dyn->d_un.d_val;

    switch (dyn->d_tag) {
        // rest of the code.
    }
}

对于某些共享对象,这很好,但是对于一些共享对象,我在检查循环条件 dyn->d_tag 时得到了一个 SIGSEGV。为什么 PT_DYNAMIC 指向一个我无法读取的位置?我还注意到 dynamic 通常是 > end,可以吗?

我正在使用设备 运行 32 位 Android 6.0.

本次计算:

dynamic = start + phdr->p_vaddr;

仅适用于链接在地址 0 的 ELF 图像,这对于共享库和位置无关的可执行文件来说是典型的,但不是必需的。

我猜您有一些共享库已预链接到非 0 地址。

这个库没有预链接:

readelf -Wl foo.so | egrep 'LOAD|DYNAMIC'
  LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x000684 0x000684 R E 0x200000
  LOAD           0x000e40 0x0000000000200e40 0x0000000000200e40 0x0001e0 0x0001e8 RW  0x200000
  DYNAMIC        0x000e50 0x0000000000200e50 0x0000000000200e50 0x000190 0x000190 RW  0x8

prelink -r 0x120000 foo.so 之后的同一个库:

readelf -Wl foo.so | egrep 'LOAD|DYNAMIC'
  LOAD           0x000000 0x0000000012000000 0x0000000012000000 0x000684 0x000684 R E 0x200000
  LOAD           0x000e40 0x0000000012200e40 0x0000000012200e40 0x0001e0 0x0001e8 RW  0x200000
  DYNAMIC        0x000e50 0x0000000012200e50 0x0000000012200e50 0x000190 0x000190 RW  0x8

如果预链接库在预链接地址 (start==0x12200000) 加载到您的应用程序中,您最终会得到 dynamic == 0x12200e50+0x12200000,这显然是伪造的。

要解决这个问题,您需要:

dynamic = start + phdr->p_vaddr - first_pt_load->p_vaddr;

其中 first_pt_load 是最低的 PT_LOAD 段(非预链接库有 .p_vaddr == 0,预链接库有 0x12000000)。