本文目录 1 个章节
Native hook技术原理及应用-PLTHook
动态链接(比如执行 dlopen)的大致步骤是:
- 检查已加载的 ELF 列表。(如果 libtest.so 已经加载,就不再重复加载了,仅把 libtest.so 的引用计数加一,然后直接返回。)
- 从 libtest.so 的 .dynamic section 中读取 libtest.so 的外部依赖的 ELF 列表,从此列表中剔除已加载的 ELF,最后得到本次需要加载的 ELF 完整列表(包括 libtest.so 自身)。
- 逐个加载列表中的 ELF。加载步骤:
- 用
mmap预留一块足够大的内存,用于后续映射 ELF。(MAP_PRIVATE方式) - 读 ELF 的 PHT,用
mmap把所有类型为PT_LOAD的 segment 依次映射到内存中。 - 从 .dynamic segment 中读取各信息项,主要是各个 section 的虚拟内存相对地址,然后计算并保存各个 section 的虚拟内存绝对地址。
- 执行重定位操作(relocate),这是最关键的一步。重定位信息可能存在于下面的一个或多个 secion 中:
.rel.plt,.rela.plt,.rel.dyn,.rela.dyn,.rel.android,.rela.android。动态链接器需要逐个处理这些.relxxxsection 中的重定位诉求。根据已加载的 ELF 的信息,动态链接器查找所需符号的地址(比如 libtest.so 的符号malloc),找到后,将地址值填入.relxxx中指明的目标地址中,这些“目标地址”一般存在于.got或.data中。 - ELF 的引用计数加一。
- 用
- 逐个调用列表中 ELF 的构造函数(constructor),这些构造函数的地址是之前从 .dynamic segment 中读取到的(类型为
DT_INIT和DT_INIT_ARRAY)。各 ELF 的构造函数是按照依赖关系逐层调用的,先调用被依赖 ELF 的构造函数,最后调用 libtest.so 自己的构造函数。(ELF 也可以定义自己的析构函数(destructor),在 ELF 被 unload 的时候会被自动调用)
只要从这些 .relxxx 中获取到“目标地址”,然后在“目标地址”中重新填上一个新的函数地址,这样就完成 hook 了
test.h
#ifndef TEST_H
#define TEST_H 1
#ifdef __cplusplus
extern "C" {
#endif
void say_hello();
#ifdef __cplusplus
}
#endif
#endif
test.c
#include <stdlib.h>
#include <stdio.h>
void say_hello()
{
char *buf = malloc(1024);
if(NULL != buf)
{
snprintf(buf, 1024, "%s", "hello\n");
printf("%s", buf);
}
}
main.c
#include <test.h>
int main()
{
say_hello();
return 0;
}
需要动态链接的符号 malloc@LIBC
caikelun@debian:~/$ arm-linux-androideabi-readelf -s ./libtest.so
Symbol table '.dynsym' contains 58 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FUNC GLOBAL DEFAULT UND __cxa_finalize@LIBC (2)
2: 00000000 0 FUNC GLOBAL DEFAULT UND snprintf@LIBC (2)
3: 00000000 0 FUNC GLOBAL DEFAULT UND malloc@LIBC (2)
4: 00000000 0 FUNC GLOBAL DEFAULT UND __cxa_atexit@LIBC (2)
5: 00000000 0 FUNC GLOBAL DEFAULT UND printf@LIBC (2)
6: 00000f61 60 FUNC GLOBAL DEFAULT 12 say_hello
...............
...............
调用该函数的汇编指令:
00000dd4 <malloc@plt>:
dd4: e28fc600 add ip, pc, #0, 12
dd8: e28cca03 add ip, ip, #12288 ; 0x3000
ddc: e5bcf1b4 ldr pc, [ip, #436]! ; 0x1b4
00000f60 <say_hello@@Base>:
f60: b5b0 push {r4, r5, r7, lr}
f62: af02 add r7, sp, #8
f64: f44f 6080 mov.w r0, #1024 ; 0x400
f68: f7ff ef34 blx dd4 <malloc@plt>
计算下来,跳转的目标地址是3f90
因为 arm 处理器使用 3 级流水线,所以第一条指令取到的
pc的值是当前执行的指令地址 +8。 于是:dd4+8+3000+1b4=3f90。
这与RELA表malloc项的offset值一致.
caikelun@debian:~$ arm-linux-androideabi-readelf -r ./libtest.so
Relocation section '.rel.plt' at offset 0xcb8 contains 30 entries:
Offset Info Type Sym.Value Sym. Name
00003f88 00000416 R_ARM_JUMP_SLOT 00000000 __cxa_atexit@LIBC
00003f8c 00000116 R_ARM_JUMP_SLOT 00000000 __cxa_finalize@LIBC
00003f90 00000316 R_ARM_JUMP_SLOT 00000000 malloc@LIBC
...............
...............
那么, “目标地址”就是 3f90, 在“目标地址”中重新填上一个新的函数地址,这样就完成 hook 了.
3f90是相对于ELF 的基地址的偏移值, 我们还需要将3f90换算成绝对地址.
使用以下代码即可hook成功.
根据你使用的编译器不同,或者编译器的版本不同,生成的 libtest.so 中,也许
malloc对应的地址不再是0x3f90,这时你需要先用 readelf 确认,然后再到main.c中修改。
#include <inttypes.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/mman.h>
#include <test.h>
#define PAGE_START(addr) ((addr) & PAGE_MASK)
#define PAGE_END(addr) (PAGE_START(addr) + PAGE_SIZE)
void *my_malloc(size_t size)
{
printf("%zu bytes memory are allocated by libtest.so\n", size);
return malloc(size);
}
void hook()
{
char line[512];
FILE *fp;
uintptr_t base_addr = 0;
uintptr_t addr;
//find base address of libtest.so
if(NULL == (fp = fopen("/proc/self/maps", "r"))) return;
while(fgets(line, sizeof(line), fp))
{
if(NULL != strstr(line, "libtest.so") &&
sscanf(line, "%"PRIxPTR"-%*lx %*4s 00000000", &base_addr) == 1)
break;
}
fclose(fp);
if(0 == base_addr) return;
//the absolute address
addr = base_addr + 0x3f90;
//add write permission
mprotect((void *)PAGE_START(addr), PAGE_SIZE, PROT_READ | PROT_WRITE);
//replace the function address
*(void **)addr = my_malloc;
//clear instruction cache
__builtin___clear_cache((void *)PAGE_START(addr), (void *)PAGE_END(addr));
}
int main()
{
hook();
say_hello();
return 0;
}
xhook
#include <stdlib.h>
#include <stdio.h>
#include <test.h>
#include <xhook.h>
void *my_malloc(size_t size)
{
printf("%zu bytes memory are allocated by libtest.so\n", size);
return malloc(size);
}
int main()
{
xhook_register(".*/libtest\\.so$", "malloc", my_malloc, NULL);
xhook_refresh(0);
say_hello();
return 0;
}
原理参见: https://github.com/iqiyi/xHook/blob/master/docs/overview/android_plt_hook_overview.zh-CN.md