理解&实现 PLT-Hook

1,168 阅读18分钟

前言-三个问题

什么是hook?

目标函数改为自定义的包装函数包装函数中执行自定义的逻辑并调用原目标函数。平时我们所说的插桩Callback都是同一个道理,当然,你可以把原逻辑完全替换掉。可以看看维基百科的解释,简单讲就是插入一段代码逻辑。

什么是ELF?

ELF是一种Linux系统的文件格式,全称Executable and Linkable Format,「.so」就是一种ELF文件。Windows、Mac系统中都有类似的格式,Windows叫PE,Mac叫Mach-O,他们的格式都是类似的(Windows的.dll熟悉不,游戏软件里经常看到这种文件,也是遵循这种格式)。

什么是PLT-Hook?

ELF文件中会有很多段内容,其中一段叫「plt」,通过ELF文件中的「plt」表定位出目标方法位置,进行方法替换(hook),所以叫PLT-Hook

大纲

image.png

一、认识ELF文件

1、从四步编译认识ELF文件

(1)编译一个.c文件

声明一个简单的.c文件

// main.c
#include <stdio.h>
int main() {
    printf("Hello, World!");
}

使用GCC进行4步编译

// 1、-E:预处理(Preprocessing),替换「include」&「宏」为真正的内容
gcc -E main.c -o main.i
  
// 2、-S:编译(Compilation),预处理后的文件转「汇编代码」—— assembly code
gcc -S main.i -o main.s
  
// 3、-c:汇编(Assemble),「汇编代码」转「机器码」—— machine code
gcc -c main.s -o main.o
  
// 4、链接(Linking):将多个目标文件 & 所需的库文件(.so等)链接成最终的「可执行文件」—— executable file
gcc main.o -o main

最后生成的「main」即可执行文件,执行一下:

weitao@bogon untitled % main
Hello, World!

(2)过程讲解

image.png

编译过程

上图中的1、2、3步骤统称为编译过程,语言预处理器、编译器、汇编器三者统称为翻译器,这三步将高级语言转换为机器语言(0101...)生成可重定位目标文件(.o文件,内容都是以0为起始位置计算的)

图片 1.png

链接过程

第4步由链接器操作,称为链接过程,概括起来有两步操作:

  • 符号解析:将符号定义符号引用关联起来。符号:函数&变量
  • 重定位:将符号定义关联到内存位置(相对位置),然后修改所有的符号引用到该位置。(汇编器会生成重定位条目,而链接器会解析这些条目,推荐阅读「深入理解计算机系统第7章」)

.o、.out、.so是三种目标文件,都是适用于 Linux 系统的文件,我们也可以编译出适用于其他系统的文件,如 Windows 对应的就是.obj、.exe、.dll。

2、目标文件种类

  • 可重定位目标文件(relocatable):内容为二进制的代码和数据(.o文件,只编译未链接)
  • 可执行目标文件(executable):内容为二进制的代码和数据,可以直接复制到内存执行(.out文件,比如shell文本的解释器)
  • 共享目标文件(shared object):一种特殊类型的「可重定位目标文件」,可以在加载或者运行时被动态地加载运行(.so文件)

不同系统的目标文件:

LinuxWindowsMac
可重定位目标文件.o (Object).obj (Object)
静态库.a (Archive).lib (Library)
共享目标文件.so (Shared Object).dll(Dynamic Link Library)
可执行目标文件.out.exe
ELF (Executable and Linkable Format)PE (Portable Executable)Mach-O

上表中静态库是另外一种类型的文件,其他三个文件是三种目标文件(静态库.a是把所有引用的库全部都打包起来,动态库.so是不含引用到的外部库的,所以一般静态库都会比较大)

3、目标文件结构

(1)整体文件结构

image.png

ELF文件的内容会被分为很多段,从右边看是未被加载到内存的视图,都是一个个小分段,分段信息会保存在Section header table内;当文件加载到内存后,每个section会被归类为一个个segment,信息保存在Program header table

PS:PLT-hook 并不是修改磁盘上的 ELF 文件,而是在运行时修改内存中的数据

(2)生成一个目标文件看看

使用前面的main.c来生成一个共享目标文件(.so)看看

// PIC:Position-Independent Code(位置无关代码)
gcc -fPIC -shared main.c -o libmain.so

使用readelf -h查看头文件:

weitao@weitaodeMacBook-Pro bin % arm-linux-androideabi-readelf -h /Users/weitao/Desktop/PLT-Hook/CrossComple/libmain.so
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00          // 给操作系统和编译器辨别此文件是ELF二进制库
  Class:                             ELF32                          // 标识文件的类别
  Data:                              2's complement, little endian  // 给出处理器特定数据的数据编码方式
  Version:                           1 (current)                    // ELF 头部的版本号码
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)       // REL:Relocatable file = 1(.o.a), EXE:Executable file = 2, DYN:Shared object file = 3
  Machine:                           ARM                            // 目标架构
  Version:                           0x1                            // ELF版本,目前均为1
  Entry point address:               0x0                            // 程序入口地址(虚拟地址),可执行文件应该为_start的虚拟地址
  Start of program headers:          52 (bytes into file)           // segment表在文件中的偏移位置
  Start of section headers:          5248 (bytes into file)         // section表在文件中的偏移位置
  Flags:                             0x5000200, Version5 EABI, soft-float ABI // ABI版本
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)                     // segment表长度
  Number of program headers:         8                              // segment数量
  Size of section headers:           40 (bytes)                     // section表长度
  Number of section headers:         25                             // section数量
  Section header string table index: 24

如果是可执行文件的话,Entry point address会不为0,不过当前文件是共享目标文件(.so)

使用readlf -S -W查看section信息:

weitao@weitaodeMacBook-Pro bin % arm-linux-androideabi-readelf -S -W /Users/weitao/Desktop/PLT-Hook/CrossComple/libmain.so
There are 25 section headers, starting at offset 0x1480:
 
Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        00000134 000134 000013 00   A  0   0  1    // 包含动态链接的路径名,动态链接器本身就是一个共享目标文件(ld-linux.so)
  [ 2] .note.android.ident NOTE            00000148 000148 000098 00   A  0   0  4
  [ 3] .dynsym           DYNSYM          000001e0 0001e0 000080 10   A  4   1  4    // 动态链接符号的一个最小集合
  [ 4] .dynstr           STRTAB          00000260 000260 000062 00   A  0   0  1    // 保存了所有的字符串常量信息
  [ 5] .hash             HASH            000002c4 0002c4 000034 04   A  3   0  4
  [ 6] .gnu.version      VERSYM          000002f8 0002f8 000010 02   A  3   0  2
  [ 7] .gnu.version_d    VERDEF          00000308 000308 00001c 00   A  4   1  4
  [ 8] .gnu.version_r    VERNEED         00000324 000324 000020 00   A  4   1  4
  [ 9] .rel.dyn          REL             00000344 000344 000010 08   A  3   0  4    // 除.rel.plt以外的重定位信息(比如通过全局函数指针来调用外部函数)
  [10] .rel.plt          REL             00000354 000354 000018 08   A  3   0  4    // 对外部函数直接调用的重定位信息
  [11] .plt              PROGBITS        0000036c 00036c 000038 00  AX  0   0  4    // Procedure Linkage Table
  [12] .text             PROGBITS        000003a4 0003a4 000060 00  AX  0   0  4    // 代码段
  [13] .rodata           PROGBITS        00000404 000404 00000e 00   A  0   0  4    // 只读数据段
  [14] .fini_array       FINI_ARRAY      00001ef4 000ef4 000004 04  WA  0   0  4
  [15] .dynamic          DYNAMIC         00001ef8 000ef8 0000f0 08  WA  4   0  4    // 供动态链接器使用的各项信息,记录了当前 ELF 的外部依赖,以及其他各个重要 section 的起始位置等信息
  [16] .got              PROGBITS        00001fe8 000fe8 000018 00  WA  0   0  4    // Global Offset Table
  [17] .data             PROGBITS        00002000 001000 000004 00  WA  0   0  4    // 数据段
  [18] .bss              NOBITS          00002004 001004 000000 00  WA  0   0  1    // 未初始化的全部变量和局部变量放在“.bss”里,不占空间
  [19] .comment          PROGBITS        00000000 001004 000028 01  MS  0   0  1    // 注释信息段
  [20] .note.gnu.gold-version NOTE            00000000 00102c 00001c 00      0   0  4
  [21] .ARM.attributes   ARM_ATTRIBUTES  00000000 001048 000036 00      0   0  1
  [22] .symtab           SYMTAB          00000000 001080 000210 10     23  26  4    // 完整符号表(可以使用 readelf -s 查看 .dynsym 和 .symtab 不同)
  [23] .strtab           STRTAB          00000000 001290 000102 00      0   0  1
  [24] .shstrtab         STRTAB          00000000 001392 0000ed 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  y (noread), p (processor specific)

可以关注下.got.plt,如果一个目标模块调用定义在共享库(非自己)中的任何函数,就会在数据段.text前生成.got,在代码段.data前生成.plt

.got:全局偏移量表(Global Offset Table):用于记录外部调用的「入口地址」,动态链接器(dynamic linker)执行重定位(relocate)操作时,这里会被填入真实的外部调用的绝对地址

看到这里,会不会突然想到我们hook的目标函数地址是不是就在这个.got表内?\color{red}{看到这里,会不会突然想到我们hook的目标函数地址是不是就在这个.got表内?}

答案是“是的”,不过可以看下.got表的内容,你会啥也看不懂:(objdump -D 返汇编)

Disassembly of section .got:
 
00001fe8 <_GLOBAL_OFFSET_TABLE_>:
    ...
    1ff4:   00000390    muleq   r0, r0, r3
    1ff8:   00000390    muleq   r0, r0, r3
    1ffc:   00000390    muleq   r0, r0, r3 

嗯,我们先继续往下看。

.plt:过程链接表(Procedure Linkage Table):PLT用于实现延迟加载 (Lazy Binding)特性,即对外部函数的重定位不必发生在加载一个动态链接库时,而是可以延迟到真正调用对一个外部函数时才发生,于是提高加载动态链接库的速度。不过android并没有支持延迟加载,而是会在加载一个共享库时,完成所有的重定位工作

如果不明白section信息中每一列的信息的意义,看一参考下 Session Head 数据结构:

typedef struct
{
  Elf32_Word    sh_name;                /* Section name (string tbl index) */
  Elf32_Word    sh_type;                /* Section type */  // STRTAB符号表,PROGBITS程序信息
  Elf32_Word    sh_flags;               /* Section flags */
  Elf32_Addr    sh_addr;                /* Section virtual addr at execution */
  Elf32_Off     sh_offset;              /* Section file offset */
  Elf32_Word    sh_size;                /* Section size in bytes */
  Elf32_Word    sh_link;                /* Link to another section */
  Elf32_Word    sh_info;                /* Additional section information */
  Elf32_Word    sh_addralign;           /* Section alignment */
  Elf32_Word    sh_entsize;             /* Entry size if section holds table */
} Elf32_Shdr;

最后再看一眼segment信息,使用readlf -l查看segment信息:

weitao@weitaodeMacBook-Pro bin % arm-linux-androideabi-readelf -l /Users/weitao/Desktop/PLT-Hook/CrossComple/libmain.so
 
Elf file type is DYN (Shared object file)
Entry point 0x0
There are 8 program headers, starting at offset 52
 
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x00000034 0x00000034 0x00100 0x00100 R   0x4
  INTERP         0x000134 0x00000134 0x00000134 0x00013 0x00013 R   0x1
      [Requesting program interpreter: /system/bin/linker]
  LOAD           0x000000 0x00000000 0x00000000 0x00412 0x00412 R E 0x1000
  LOAD           0x000ef4 0x00001ef4 0x00001ef4 0x00110 0x00110 RW  0x1000
  DYNAMIC        0x000ef8 0x00001ef8 0x00001ef8 0x000f0 0x000f0 RW  0x4
  NOTE           0x000148 0x00000148 0x00000148 0x00098 0x00098 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10
  GNU_RELRO      0x000ef4 0x00001ef4 0x00001ef4 0x0010c 0x0010c RW  0x4
 
 Section to Segment mapping:
  Segment Sections...
   00    
   01     .interp
   02     .interp .note.android.ident .dynsym .dynstr .hash .gnu.version .gnu.version_d .gnu.version_r .rel.dyn .rel.plt .plt .text .rodata
   03     .fini_array .dynamic .got .data
   04     .dynamic
   05     .note.android.ident
   06    
   07     .fini_array .dynamic .got

可以看到section都被组织为一个个segment

二、共享目标文件加载过程

1、.so加载过程

image.png

看一下上图的2、3、4步骤

  • 第2步:当创建可执行文件时,静态执行一些链接,此时没有复制代码到文件,仅复制「重定位和符号表」信息。
  • 第3步:文件加载到内存
  • 第4步:在程序加载时,动态完成链接过程,此时利用「重定位和符号表」信息来解析.so中代码和数据的引用,将外部库也加载到内存

2、方法调用过程

带有「延迟绑定」的调用过程示意图:

image.png

Android系统链接&调用过程:

image.png

ps: .plt .got 其实都是数组,.plt中的item为调用用户代码的函数(一个item为一个函数),.got中的item对应一个被调用的函数

上图的调用过程可以协助我们找到目标函数:

.text (代码段) ➞ .plt ➞ .got ➞ 目标函数

再来看下 Android Linker dlopen 的大致过程:

  1. 检查已加载的ELF列表,不重复加载
  2. 获取目标.so的外部依赖的所有ELF文件,保存列表
  3. 加载列表中的ELF,同时找到目标.so中所需的符号地址( rel.plt, .rela.plt等section中的重定位诉求),填入目标.so的.got表中。

那么我们可以确认,要替换的地址就存在于.got表中,而且通过.plt可以协助我们找到.got中的对应item

三、实现PLT-Hook

1、交叉编译

编译出一个可以在Android系统上执行的文件

头文件:

// test.h
#include <stdio.h>
void hello_world();

实现:

// test.c
#include "test.h"
#include <stdio.h>
 
void hello_world(){
    printf("Hello World!");
}

生成动态库:

gcc ./test.c -fPIC -shared -o libtest.so
 
// 交叉编译命令
arm-linux-androideabi-gcc --sysroot=/Users/weitao/Library/Android/sdk/ndk/17.2.4988734/platforms/android-21/arch-arm -isystem /Users/weitao/Library/Android/sdk/ndk/17.2.4988734/sysroot/usr/include -isystem /Users/weitao/Library/Android/sdk/ndk/17.2.4988734/sysroot/usr/include/arm-linux-androideabi -pie /Users/weitao/Desktop/PLT-Hook/test.c -fPIC -shared -o /Users/weitao/Desktop/PLT-Hook/libtest.so

定义可执行文件,调用上面定义的hello_world方法

// main.c
#include "test.h"
 
int main(){
    hello_world();
    return 0;
}

使用-ltest链接libtest.so

gcc main.c -L. -ltest -o main

Linux动态链接库的默认搜索路径是/lib和/usr/lib,因此动态库被创建后,我们需要将 libtest.so复制到这两个目录下面,或者是 执行 export LD_LIBRARY_PATH= 添加 test.so的目录路径

// cd 到交叉编译程序所在目录
weitao@weitaodeMacBook-Pro ~ % cd /Users/weitao/Library/Android/sdk/ndk/17.2.4988734/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin

// 交叉编译命令,注意:先将libtest.so移动到 --sysroot 指定的目录下的/usr/lib文件夹下
arm-linux-androideabi-gcc --sysroot=/Users/weitao/Library/Android/sdk/ndk/17.2.4988734/platforms/android-21/arch-arm -isystem /Users/weitao/Library/Android/sdk/ndk/17.2.4988734/sysroot/usr/include -isystem /Users/weitao/Library/Android/sdk/ndk/17.2.4988734/sysroot/usr/include/arm-linux-androideabi -pie /Users/weitao/Desktop/PLT-Hook/main.c -L. -ltest -o /Users/weitao/Desktop/PLT-Hook/main

Android真机中执行一下(不用root):

weitao@bogon bin % adb push /Users/weitao/Desktop/PLT-Hook/libtest.so /Users/weitao/Desktop/PLT-Hook/main /data/local/tmp
/Users/weitao/Desktop/PLT-Hook/libtest.so: 1 file pushed, 0 skipped. 2.0 MB/s (6256 bytes in 0.003s)
/Users/weitao/Desktop/PLT-Hook/main: 1 file pushed, 0 skipped. 4.1 MB/s (6412 bytes in 0.002s)
2 files pushed, 0 skipped. 0.3 MB/s (12668 bytes in 0.038s)
weitao@bogon bin % adb shell "chmod +x /data/local/tmp/main"
weitao@bogon bin % adb shell "export LD_LIBRARY_PATH=/data/local/tmp; /data/local/tmp/main"
Hello World!%   // 执行成功

2、找出目标函数地址

我们的逻辑是:

  1. 代码段调用某个方法,跳转到.plt
  2. .plt定位到.got
  3. .got会调用外部方法,在运行时,.got地址指向的地址就是方法地址,获取方法地址并进行替换,完成hook

(1)反汇编查看代码段和.plt

weitao@bogon bin % arm-linux-androideabi-objdump -D /Users/weitao/Desktop/PLT-Hook/libtest.so
 
/Users/weitao/Desktop/PLT-Hook/libtest.so:     file format elf32-littlearm
 
......
 
Disassembly of section .text:
 
00000404 <hello_world>:
 404:   e92d4800    push    {fp, lr}
 408:   e28db004    add fp, sp, #4
 40c:   e59f300c    ldr r3, [pc, #12]   ; 420 <hello_world+0x1c>
 410:   e08f3003    add r3, pc, r3
 414:   e1a00003    mov r0, r3
 418:   ebffffe7    bl  3bc <puts@plt>    // gcc会把printf转为puts,所以这里是puts方法
 41c:   e8bd8800    pop {fp, pc}
 420:   0000000c    andeq   r0, r0, ip
 
......
 
Disassembly of section .plt:
 
000003bc <puts@plt>:
 3bc:   e28fc600    add ip, pc, #0, 12              // 由于ARM三级流水, pc = 0x3bc + 0x8
 3c0:   e28cca01    add ip, ip, #4096   ; 0x1000    // 4096 = 0x1000,ip = ip + 0x1000
 3c4:   e5bcfc38    ldr pc, [ip, #3128]!    ; 0xc38 // 3128 = 0xc38,pc = ip + 0xc38,ip = ip + 0xc38
 
......

首先,代码段中的bl命令跳转.plt段,.plt中的操作:

  1. ARM三级流水结论:pc = 当前正在执行指令在内存中的地址 + 0x8  = 0x3bc + 0x8
  2. add指令:ip = pc  + (0 << 12) = pc + 0 = 0x3bc + 0x8
  3. add指令:ip = ip + 0x1000 = 0x3bc + 0x8 + 0x1000
  4. ldr指令:pc = ip + 0xc38,ip = ip + 0xc38

最终执行「ldr」指令后,pc & ip 寄存器中地址为:0x3bc + 0x8 + 0x1000 + 0xc38 = 0x1ffc

即下一个执行地址为0x1ffc

(2)查看.got和重定位段的内容

Disassembly of section .got:
 
00001fe8 <_GLOBAL_OFFSET_TABLE_>:
    ...
    1ff4:   00000390    muleq   r0, r0, r3
    1ff8:   00000390    muleq   r0, r0, r3
    1ffc:   00000390    muleq   r0, r0, r3  // 0x1ffc 找到地址

重点来了:0x1ffc这个地址只是当前libtest.so中的一个地址,它属于.got段。在运行时,这个地址会指向外部的目标函数,我们要实现hook,就要把这个地址的内容替换为我们自定义的方法。

我们可以再看一眼.rel.plt,是否存在对应地址的重定位:

weitao@bogon bin % arm-linux-androideabi-readelf -r /Users/weitao/Desktop/PLT-Hook/libtest.so
 
Relocation section '.rel.dyn' at offset 0x368 contains 2 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00001ef4  00000017 R_ARM_RELATIVE  
00002000  00000017 R_ARM_RELATIVE  
 
Relocation section '.rel.plt' at offset 0x378 contains 3 entries:
 Offset     Info    Type            Sym.Value  Sym.Name
00001ff4  00000316 R_ARM_JUMP_SLOT   00000000   __cxa_atexit@LIBC
00001ff8  00000216 R_ARM_JUMP_SLOT   00000000   __cxa_finalize@LIBC
00001ffc  00000116 R_ARM_JUMP_SLOT   00000000   puts@LIBC   // 地址0x1ffc指向了这个外部方法

3、实现Hook

确认外部函数地址后,我们就可以进行hook替换了

C语言有个骚操作,可以直接把整型转换为指针:INT36-C. Converting a pointer to integer or integer to pointer(Carnegie Mellon 😱)

而对于0x1ffx地址的内容,应该是一个二级指针,指向内容为函数指针,是一级指针,所以我们可以这么hook:

// main.c
#include "test.h"
 
// hook替换的方法
int my_puts(const char *str){
    puts("hook!!!!");
    return printf(str);
}
 
int main(){
    void **p = (void **)0x1ffc;
    *p = (void *)my_puts;       // do hook
    hello_world();
}

但是,这样执行还是会失败:(引自:爱奇艺xhook so原理详解

  • 0x1ffc 是个相对内存地址,需要把它换算成绝对地址。
  • 0x1ffc 对应的绝对地址很可能没有写入权限,直接对这个地址赋值会引起段错误。
  • 新的函数地址即使赋值成功了,my_puts 也不会被执行,因为处理器有指令缓存(instruction cache)(解决问题一大法宝:清缓存)

解决上述三个问题:

  • 通过「/proc/self/maps」找到.so包加载的基地址
  • 通过「mprotect」方法获取写入权限
  • 通过「__builtin___clear_cache」方法清理缓存

修改后的main文件:

// hookmain.c
#include "test.h"
#include <inttypes.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/mman.h>
 
#define PAGE_START(addr) ((addr) & PAGE_MASK)
#define PAGE_END(addr)   (PAGE_START(addr) + PAGE_SIZE)
 
int my_puts(const char *str){
    puts("hook!!!!");
    return printf(str);
}
 
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 + 0x1ffc;
 
    //add write permission
    mprotect((void *)PAGE_START(addr), PAGE_SIZE, PROT_READ | PROT_WRITE);
 
    //replace the function address
    *(void **)addr = my_puts;
 
    //clear instruction cache
    __builtin___clear_cache((void *)PAGE_START(addr), (void *)PAGE_END(addr));
}
 
int main(){
    hook();
    hello_world();
    return 0;
}

本地交叉编译命令:

// 编译生成hookmain可执行文件(注意libtest.so放到--sysroot指向目录下的/usr/lib下,同时头文件test.h要放在hookmain.c的同一目录下)
weitao@weitaodeMacBook-Pro bin % arm-linux-androideabi-gcc --sysroot=/Users/weitao/Library/Android/sdk/ndk/17.2.4988734/platforms/android-21/arch-arm -isystem /Users/weitao/Library/Android/sdk/ndk/17.2.4988734/sysroot/usr/include -isystem /Users/weitao/Library/Android/sdk/ndk/17.2.4988734/sysroot/usr/include/arm-linux-androideabi -pie /Users/weitao/Desktop/PLT-Hook/CrossHook/hookmain.c -L. -ltest -o /Users/weitao/Desktop/PLT-Hook/CrossHook/hookmain

执行一下:

weitao@bogon bin % adb push /Users/weitao/Desktop/PLT-Hook/hookmain /data/local/tmp
/Users/weitao/Desktop/PLT-Hook/hookmain: 1 file pushed, 0 skipped. 10.7 MB/s (7448 bytes in 0.001s)
weitao@bogon bin % adb shell "chmod +x /data/local/tmp/hookmain"                          
weitao@bogon bin % adb shell "export LD_LIBRARY_PATH=/data/local/tmp; /data/local/tmp/hookmain"
hook!!!!
Hello World!% // 执行成功

image.png

最后尝试了直接使用addr中的函数指针调用函数:(在上面的代码中插入了两行新代码)

image.png

主要参考