在 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
)。
为了挂钩 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
)。