PLT/GOT hook原理

一、ELF(Executable and Linkable Format)

1.1、ELF(Executable and Linkable Format)格式

ELF是一种行业标准的二进制数据封装格式,主要用于封装可执行文件、动态库、object 文件和 core dumps 文件。

ELF文件格式.jpg

1.2、ELF 解析工具

使用 google NDK 对源代码进行编译和链接,生成的动态库或可执行文件都是 ELF 格式的。用 readelf 可以查看 ELF 文件的基本信息,用 objdump 可以查看 ELF 文件的反汇编输出。

工具的位置
arm-linux-androideabi-readelf 和arm-linux-androideabi-objdump 位于Android的NDK目录下

已我的mac为例,路径如下

 /Users/baixuefei/Library/Android/sdk/ndk/16.1.4479499/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/
  • arm-linux-androideabi-readelf -h libtestxhook.so 查看ELF 头信息
baixuefei@baixuefeideMacBook-Pro xhook % ./arm-linux-androideabi-readelf -h libtestxhook.so
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           ARM
  Version:                           0x1
  Entry point address:               0x12728
  Start of program headers:          52 (bytes into file)
  Start of section headers:          1146224 (bytes into file)
  Flags:                             0x5000200, Version5 EABI, soft-float ABI
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         9
  Size of section headers:           40 (bytes)
  Number of section headers:         35
  Section header string table index: 33
  
  • arm-linux-androideabi-objdump 查看so的反汇编代码

./arm-linux-androideabi-objdump -D libtestxhook.so

00012928 <Java_com_example_testxhook_XHookUtils_sayHello>:
   12928:   b580        push    {r7, lr}
   1292a:   466f        mov r7, sp
   1292c:   b082        sub sp, #8
   1292e:   9001        str r0, [sp, #4]
   12930:   9100        str r1, [sp, #0]
   12932:   f011 ec5e   blx 241f0 <__ThumbV7PILongThunk__ZNSt9exceptionD2Ev+0x104>
   12936:   b002        add sp, #8
   12938:   bd80        pop {r7, pc}

1.3ELF 格式文件构成

image

二、动态链接过程

2.1、静态链接和动态链接

程序对于外部函数的调用需要在生成可执行文件时将外部函数链接到程序中,链接的方式分为静态链接和动态链接。

  • 静态链接得到的可执行文件包含外部函数的全部代码,
  • 动态链接得到的可执行文件中并不包含外部函数的代码,而是运行时将动态链接库(若干外部函数的集合)加载到内存的某个位置,再在发生调用时去链接库定位所需的函数。

2.2、 链接过程

当需要使用一个 Native 库(.so 文件)的时候,我们需要调用dlopen("libname.so")来加载这个库。

在我们调用了dlopen("libname.so")之后,系统首先会检查缓存中已加载的 ELF 文件列表。如果未加载则执行加载过程,如果已加载则计数加一,忽略该调用。然后系统会用从 libname.so 的dynamic节区中读取其所依赖的库,按照相同的加载逻辑,把未在缓存中的库加入加载列表。

下面命令来查看一个库的依赖:

readelf -d <library> | grep NEEDED

可以看到libtestxhook.so 依赖liblog.so、libm.so、libdl.so、libc.so四个外部库

baixuefei@baixuefeideMacBook-Pro xhook % ./arm-linux-androideabi-readelf -d ./libtestxhook.so 

Dynamic section at offset 0x2632c contains 27 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [liblog.so]
 0x00000001 (NEEDED)                     Shared library: [libm.so]
 0x00000001 (NEEDED)                     Shared library: [libdl.so]
 0x00000001 (NEEDED)                     Shared library: [libc.so]
 0x0000000e (SONAME)                     Library soname: [libtestxhook.so]
 0x0000001e (FLAGS)                      BIND_NOW
 0x6ffffffb (FLAGS_1)                    Flags: NOW
 0x00000011 (REL)                        0x9fcc
 0x00000012 (RELSZ)                      11744 (bytes)
 0x00000013 (RELENT)                     8 (bytes)
 0x6ffffffa (RELCOUNT)                   1002
 0x00000017 (JMPREL)                     0xe234
 0x00000002 (PLTRELSZ)                   1200 (bytes)
 0x00000003 (PLTGOT)                     0x274a0
 0x00000014 (PLTREL)                     REL
 0x00000006 (SYMTAB)                     0x210
 0x0000000b (SYMENT)                     16 (bytes)
 0x00000005 (STRTAB)                     0x4e74
 0x0000000a (STRSZ)                      20824 (bytes)
 0x6ffffef5 (GNU_HASH)                   0x2cc8
 0x00000004 (HASH)                       0x3b8c
 0x0000001a (FINI_ARRAY)                 0x27324
 0x0000001c (FINI_ARRAYSZ)               8 (bytes)
 0x6ffffff0 (VERSYM)                     0x27d0
 0x6ffffffe (VERNEED)                    0x2c88
 0x6fffffff (VERNEEDNUM)                 2
 0x00000000 (NULL)                       0x0

2.2.1、 系统是如何加载的 ELF 文件的

  • 读 ELF 的程序头部表,把所有 PT_LOAD 的节区 mmap 到内存中。
  • 从“.dynamic”中读取各信息项,计算并保存所有节区的虚拟地址,然后执行重定位操作。
  • 最后 ELF 加载成功,引用计数加一。

2.2.2、 “.got”和“.plt”它们的具体含义

  • The Global Offset Table (GOT)。用来保存外部函数的的绝对地址

简单来说就是在数据段的地址表,假定我们有一些代码段的指令引用一些地址变量,编译器会引用 GOT 表来替代直接引用绝对地址,因为绝对地址在编译期是无法知道的,只有重定位后才会得到 ,GOT 自己本身将会包含函数引用的绝对地址。

  • The Procedure Linkage Table (PLT)。

PLT 不同于 GOT,它位于代码段,动态库的每一个外部函数都会在 PLT 中有一条记录,每一条 PLT记录都是一小段可执行代码。一般来说,外部代码都是在调用 PLT 表里的记录,然后 PLT 的相应记录会负责调用实际的函数。我们一般把这种设定叫作“蹦床”(Trampoline)。

2.2.3、首次加载外部函数

PLT 和 GOT 记录是一一对应的,并且 GOT 表第一次解析后会包含调用函数的实际地址。

[图片上传失败...(image-ea86bd-1710343090921)]

  • 我们在代码中调用 func,编译器会把这个转化为 func@plt,并在 PLT 表插入一条记录。
  • PLT 表中第一条(或者说第 0 条)PLT[0] 是一条特殊记录,它是用来帮助我们解析地址的。通常在类 Linux 系统,这个的实现会位于动态加载器。/system/bin/linker。
  • 其余的 PLT 记录都均包含以下信息:
(1)跳转 GOT 表的指令(jmp *GOT[n])
(2)为上面提到的第 0 条解析地址函数准备参数。
(3)调用 PLT[0],这里 resovler 的实际地址是存储在 GOT[2] 。

在解析前 GOT[n] 会直接指向 jmp *GOT[n] 的下一条指令。在解析完成后,我们就得到了 func 的实际地址,动态加载器会将这个地址填入 GOT[n],然后调用 func。

外部函数第一次被调用过程

2.2.4、第二次加载外部函数

[图片上传失败...(image-8843a2-1710343090921)]
当第一次调用发生后,之后再调用函数 func 就高效简单很多。首先调用 PLT[n],然后执行 jmp *GOT[n]。GOT[n] 直接指向 func

image

三、PLT Hook基本原理

PLT Hook就是改变了原来的relocation后的地址。主要流程:

  • 通过符号名,在hash table中找到对应的符号信息
  • 再找到对应的PLT信息
  • 最后找到GOT表中的绝对地址的值
  • 修改这个绝对地址的值,为我们的“代理函数”的地址

四、运行时加载(dlopen、dlsysm、dlerror、dlclose)

运行时加载so库,让程序自己在运行时控制加载指定的模块,并且可以在不需要该模块时将其卸载。

动态库的装载通过4个函数完成:打开动态库(dlopen)、查找符号(dlsym)、错误处理(dlerror)以及关闭动态库(dlclose)。

这几个API的实现是在/lib/libdl.so.2里面,它们的声明和相关常量在系统标准头文件<dlfcn.h>。

4.1、dlopen()

dlopen()函数用来打开一个动态库,并将其加载到进程的地址空间,完成初始化过程,它的原型定义为

void *dlopen(const char *filename, int flags);

dlopen的返回值是被加载的模块的句柄,这个句柄在后面使用dlsym或者dlclose时要用到。如果加载模块失败,则返回NULL。如果被加载的模块之间有依赖关系,比如模块A依赖于模块B,那么程序员必须手工加载被依赖的模块,比如先加载B,再加载A。

(1)第一个参数是被加载动态库的路径,如果这个路径是绝对路径(以”/”开始的路径),则该函数会将尝试直接打开动态库;如果是相对路径,那么dlopen()会尝试在以一定的顺序查找该动态库文件

(2)第二个参数flag表示函数符号的解析方式,其中RTLD_LAZY和RTLD_NOW必须要设置其中一种

  • 常量RTLD_LAZY表示使用延迟绑定,当函数第一次被用到时才进行绑定,即PLT机制;
  • RTLD_NOW表示当模块被加载时即完成所有的函数绑定工作,如果有任何未定义的符号引用的绑定工作没法完成,那么dlopen()就返回错误。
  • RTLD_GLOBAL 表示将加载的模块的全局符号合并到进程的全局符号表中,使得以后加载的模块可以使用这些符号。
  • RTLD_LOCAL RTLD_LOCAL则与RTLD_GLOBAL相反,使以后加载的模块不能使用这些符号,如果没有定义,默认就是RTLD_LOCAL。
  • RTLD_NODELETE 在dlclose()期间不要卸载共享对象。因此,如果稍后使用dlopen()重新加载对象,则共享对象的静态变量不会被重新初始化。
  • RTLD_DEEPBIND 将这个共享对象中符号的查找作用域放在全局作用域之前。这意味着包含对象将优先使用自己的符号,而不是已经加载的其他对象中包含的具有相同名称的全局符号。

4.2、dlsym()

dlsym函数就是运行时装载的核心,通过这个函数找到所需要运行的符号,函数原型如下:

void *dlsym(void *handle, const char *symbol); 

两个参数,第一个参数是有dlopen()返回的动态库的句柄;第二个参数即所查找的符号的名字,一个以\0结尾的C字符串。

如果查找的符号是个函数,那么它返回函数的地址;如果是个变量,它返回变量的地址;如果这个符号是个常量,那么它返回的是该常量的值。

4.3、dlerror()

每次我们调用dlopen()、dlsym()或dlclose()以后,都可以调用dlerror()函数来判断上一次调用是否成功。dlerror()的返回值类型是char*,如果返回NULL,则表示上一次调用成功,如果不是,则返回相应的错误消息。

4.4、dlclose()

dlclose()的作用跟dlopen()刚好相反,它的作用是将上一个已经加载的模块卸载。系统会维持一个加载引用计数器,每次使用dlopen()加载某模块时,相应的计数器加一;每次使用dlclose()卸载某模块时,相应计数器减一。只有当计数器值减到0时,模块才被真正的卸载掉。

参考文章

xhook
PLT Hook基本原理
PLT HOOK
GOT表和PLT表
[Android Native Hook技术你知道多少?](https://zhuanlan.zhihu.com/p/13269

解决函数名冲突问题(dlopen,dlsym,dlclose)
ca

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容