{
  "schemaVersion": "0.11.0",
  "canonical": "https://www.pystone.net/notes/native-hook-plt-hook/",
  "atlas": "https://www.pystone.net/?node=native-hook-plt-hook#knowledge-atlas",
  "markdown": "https://www.pystone.net/notes/native-hook-plt-hook.md",
  "context": "https://www.pystone.net/notes/native-hook-plt-hook.context.json",
  "knowledgeVersion": "224c990773de.5fa8af6e39fa",
  "build": {
    "siteCommit": "224c990773de166d23a886306577dd90379529ce",
    "notesCommit": "5fa8af6e39fa3891d1b9b4832bfa6c4e0ecaaf0a",
    "builtAt": "1970-01-01T00:00:00.000Z",
    "version": "224c990773de.5fa8af6e39fa"
  },
  "id": "note:native-hook-plt-hook",
  "slug": "native-hook-plt-hook",
  "title": "Native hook技术原理及应用-PLTHook",
  "type": "note",
  "visibility": "public",
  "idStability": "rename-stable",
  "author": {
    "name": "Perrin Yong",
    "profile": "https://www.pystone.net/profile/"
  },
  "publisher": {
    "name": "Perrin Yong",
    "profile": "https://www.pystone.net/profile/"
  },
  "aliases": [],
  "summary": "Native hook技术原理及应用 PLTHook",
  "contentRole": "unspecified",
  "isMoc": false,
  "mocRecognition": "none",
  "generated": false,
  "attribution": "unspecified",
  "domain": "10-计算机、信息技术与工程",
  "tags": [],
  "mocs": [],
  "contentHash": "10edb44917ace92ea11f5df2253a98e6bd4be1624eb1f8146e9863d6ae0292c6",
  "assets": [],
  "headings": [
    {
      "depth": 1,
      "text": "Native hook技术原理及应用-PLTHook",
      "anchor": "native-hook技术原理及应用-plthook",
      "citation": "https://www.pystone.net/notes/native-hook-plt-hook/#native-hook%E6%8A%80%E6%9C%AF%E5%8E%9F%E7%90%86%E5%8F%8A%E5%BA%94%E7%94%A8-plthook"
    },
    {
      "depth": 2,
      "text": "xhook",
      "anchor": "xhook",
      "citation": "https://www.pystone.net/notes/native-hook-plt-hook/#xhook"
    }
  ],
  "claims": [],
  "outgoing": [
    {
      "id": "note:elf-execution-dynamic-linking",
      "title": "ELF文件-程序的执行与动态链接",
      "url": "https://www.pystone.net/notes/elf-execution-dynamic-linking/",
      "atlas": "https://www.pystone.net/?node=elf-execution-dynamic-linking#knowledge-atlas",
      "label": "ELF文件-程序的执行与动态链接",
      "origin": "explicit",
      "humanReviewed": true,
      "context": "Native hook技术原理及应用-PLTHook中的“ELF文件-程序的执行与动态链接”导航项",
      "citation": "https://www.pystone.net/notes/native-hook-plt-hook/#native-hook%E6%8A%80%E6%9C%AF%E5%8E%9F%E7%90%86%E5%8F%8A%E5%BA%94%E7%94%A8-plthook"
    }
  ],
  "incoming": [
    {
      "id": "note:programming-languages-and-runtimes",
      "title": "编程语言与运行时",
      "url": "https://www.pystone.net/notes/programming-languages-and-runtimes/",
      "atlas": "https://www.pystone.net/?node=programming-languages-and-runtimes#knowledge-atlas",
      "label": "编程语言与运行时",
      "origin": "explicit",
      "humanReviewed": true,
      "context": "本层中的“Native hook技术原理及应用-PLTHook”导航项",
      "citation": "https://www.pystone.net/notes/programming-languages-and-runtimes/#%E6%9C%AC%E5%B1%82"
    }
  ],
  "contentMarkdown": "# Native hook技术原理及应用-PLTHook\n\n[ELF文件-程序的执行与动态链接](5%20信息技术领域/编程语言及特性/ELF文件-程序的执行与动态链接.md)\n\n动态链接（比如执行 dlopen）的大致步骤是：\n1. 检查已加载的 ELF 列表。（如果 libtest.so 已经加载，就不再重复加载了，仅把 libtest.so 的引用计数加一，然后直接返回。）\n2. 从 libtest.so 的 .dynamic section 中读取 libtest.so 的外部依赖的 ELF 列表，从此列表中剔除已加载的 ELF，最后得到本次需要加载的 ELF 完整列表（包括 libtest.so 自身）。\n3. 逐个加载列表中的 ELF。加载步骤：\n    - 用 `mmap` 预留一块足够大的内存，用于后续映射 ELF。（`MAP_PRIVATE` 方式）\n    - 读 ELF 的 PHT，用 `mmap` 把所有类型为 `PT_LOAD` 的 segment 依次映射到内存中。\n    - 从 .dynamic segment 中读取各信息项，主要是各个 section 的虚拟内存相对地址，然后计算并保存各个 section 的虚拟内存绝对地址。\n    - **执行重定位操作（relocate），这是最关键的一步。重定位信息可能存在于下面的一个或多个 secion 中：`.rel.plt`, `.rela.plt`, `.rel.dyn`, `.rela.dyn`, `.rel.android`, `.rela.android`。动态链接器需要逐个处理这些 `.relxxx` section 中的重定位诉求。根据已加载的 ELF 的信息，动态链接器查找所需符号的地址（比如 libtest.so 的符号 `malloc`），找到后，将地址值填入 `.relxxx` 中指明的目标地址中，这些“目标地址”一般存在于`.got` 或 `.data` 中。**\n    - ELF 的引用计数加一。\n4. 逐个调用列表中 ELF 的构造函数（constructor），这些构造函数的地址是之前从 .dynamic segment 中读取到的（类型为 `DT_INIT` 和 `DT_INIT_ARRAY`）。各 ELF 的构造函数是按照依赖关系逐层调用的，先调用被依赖 ELF 的构造函数，最后调用 libtest.so 自己的构造函数。（ELF 也可以定义自己的析构函数（destructor），在 ELF 被 unload 的时候会被自动调用）\n\n\n只要从这些 `.relxxx` 中获取到“**目标地址**”，然后在“**目标地址**”中重新填上一个新的函数地址，这样就完成 hook 了\n\n\ntest.h\n```c\n#ifndef TEST_H\n#define TEST_H 1\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoid say_hello();\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n```\n\ntest.c\n```c\n#include <stdlib.h>\n#include <stdio.h>\n\nvoid say_hello()\n{\n    char *buf = malloc(1024);\n    if(NULL != buf)\n    {\n        snprintf(buf, 1024, \"%s\", \"hello\\n\");\n        printf(\"%s\", buf);\n    }\n}\n```\n\nmain.c\n```c\n#include <test.h>\n\nint main()\n{\n    say_hello();\n    return 0;\n}\n```\n\n需要动态链接的符号  `malloc@LIBC`\n\n```bash\ncaikelun@debian:~/$ arm-linux-androideabi-readelf -s ./libtest.so\n\nSymbol table '.dynsym' contains 58 entries:\n   Num:    Value  Size Type    Bind   Vis      Ndx Name\n     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND\n     1: 00000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_finalize@LIBC (2)\n     2: 00000000     0 FUNC    GLOBAL DEFAULT  UND snprintf@LIBC (2)\n     3: 00000000     0 FUNC    GLOBAL DEFAULT  UND malloc@LIBC (2)\n     4: 00000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_atexit@LIBC (2)\n     5: 00000000     0 FUNC    GLOBAL DEFAULT  UND printf@LIBC (2)\n     6: 00000f61    60 FUNC    GLOBAL DEFAULT   12 say_hello\n...............\n...............\n```\n\n\n调用该函数的汇编指令:\n```assembly\n\n00000dd4 <malloc@plt>:\n dd4:   e28fc600    add ip, pc, #0, 12\n dd8:   e28cca03    add ip, ip, #12288  ; 0x3000\n ddc:   e5bcf1b4    ldr pc, [ip, #436]! ; 0x1b4\n\n\n00000f60 <say_hello@@Base>:\n     f60:   b5b0        push    {r4, r5, r7, lr}\n     f62:   af02        add r7, sp, #8\n     f64:   f44f 6080   mov.w   r0, #1024   ; 0x400\n     f68:   f7ff ef34   blx dd4 <malloc@plt>\n```\n\n计算下来,跳转的目标地址是3f90\n> 因为 arm 处理器使用 3 级流水线，所以第一条指令取到的 `pc` 的值是当前执行的指令地址 + `8`。 于是：`dd4` + `8` + `3000` + `1b4` = `3f90`。\n\n这与RELA表malloc项的offset值一致.\n\n```bash\ncaikelun@debian:~$ arm-linux-androideabi-readelf -r ./libtest.so\n\nRelocation section '.rel.plt' at offset 0xcb8 contains 30 entries:\n Offset     Info    Type            Sym.Value  Sym. Name\n00003f88  00000416 R_ARM_JUMP_SLOT   00000000   __cxa_atexit@LIBC\n00003f8c  00000116 R_ARM_JUMP_SLOT   00000000   __cxa_finalize@LIBC\n00003f90  00000316 R_ARM_JUMP_SLOT   00000000   malloc@LIBC\n...............\n...............\n```\n\n\n那么, “**目标地址**”就是 3f90, 在“**目标地址**”中重新填上一个新的函数地址，这样就完成 hook 了.\n\n\n3f90是相对于ELF 的基地址的偏移值, 我们还需要将3f90换算成绝对地址.\n\n使用以下代码即可hook成功.\n\n> 根据你使用的编译器不同，或者编译器的版本不同，生成的 libtest.so 中，也许 `malloc` 对应的地址不再是 `0x3f90`，这时你需要先用 readelf 确认，然后再到 `main.c` 中修改。\n\n\n```c\n#include <inttypes.h>\n#include <unistd.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <sys/mman.h>\n#include <test.h>\n\n#define PAGE_START(addr) ((addr) & PAGE_MASK)\n#define PAGE_END(addr)   (PAGE_START(addr) + PAGE_SIZE)\n\nvoid *my_malloc(size_t size)\n{\n    printf(\"%zu bytes memory are allocated by libtest.so\\n\", size);\n    return malloc(size);\n}\n\nvoid hook()\n{\n    char       line[512];\n    FILE      *fp;\n    uintptr_t  base_addr = 0;\n    uintptr_t  addr;\n\n    //find base address of libtest.so\n    if(NULL == (fp = fopen(\"/proc/self/maps\", \"r\"))) return;\n    while(fgets(line, sizeof(line), fp))\n    {\n        if(NULL != strstr(line, \"libtest.so\") &&\n           sscanf(line, \"%\"PRIxPTR\"-%*lx %*4s 00000000\", &base_addr) == 1)\n            break;\n    }\n    fclose(fp);\n    if(0 == base_addr) return;\n\n    //the absolute address\n    addr = base_addr + 0x3f90;\n\n    //add write permission\n    mprotect((void *)PAGE_START(addr), PAGE_SIZE, PROT_READ | PROT_WRITE);\n\n    //replace the function address\n    *(void **)addr = my_malloc;\n\n    //clear instruction cache\n    __builtin___clear_cache((void *)PAGE_START(addr), (void *)PAGE_END(addr));\n}\n\nint main()\n{\n    hook();\n\n    say_hello();\n    return 0;\n}\n```\n\n\n## xhook\n\n```c\n#include <stdlib.h>\n#include <stdio.h>\n#include <test.h>\n#include <xhook.h>\n\nvoid *my_malloc(size_t size)\n{\n    printf(\"%zu bytes memory are allocated by libtest.so\\n\", size);\n    return malloc(size);\n}\n\nint main()\n{\n    xhook_register(\".*/libtest\\\\.so$\", \"malloc\", my_malloc, NULL);\n    xhook_refresh(0);\n\n    say_hello();\n    return 0;\n}\n```\n\n原理参见: https://github.com/iqiyi/xHook/blob/master/docs/overview/android_plt_hook_overview.zh-CN.md\n\n仓库： https://github.com/iqiyi/xHook\n"
}
