失效链接处理 |
Native+Hook+技术 PDF 下载 本站整理下载:
提取码:uynx
相关截图:
主要内容:
35 | Native Hook 技术,天使还是魔鬼?
2019-03-23 张绍文
Android开发高手课 进入课程
讲述:冯永吉
时长 15:23 大小 14.10M
相信一直坚持学习专栏的同学对 Hook 一定不会陌生,在前面很多期里我无数次提到
Hook。可能有不少同学对于 Hook 还是“懵懵懂懂”,那今天我们从来头了解一下什么
是 Hook。
Hook 直译过来就是“钩子”的意思,是指截获进程对某个 API 函数的调用,使得 API 的
执行流程转向我们实现的代码片段,从而实现我们所需要得功能,这里的功能可以是监控、
修复系统漏洞,也可以是劫持或者其他恶意行为。
相信许多新手第一次接触 Hook 时会觉得这项技术十分神秘,只能被少数高手、黑客所掌
握,那 Hook 是不是真的难以掌握?希望今天的文章可以打消你的顾虑。
Native Hook 的不同流派
下载APP
对于 Native Hook 技术,我们比较熟悉的有 GOT/PLT Hook、Trap Hook 以及 Inline
Hook,下面我来逐个讲解这些 Hook 技术的实现原理和优劣比较。
1. GOT/PLT Hook
在Chapter06-plus中,我们使用了 PLT Hook 技术来获取线程创建的堆栈。先来回顾一下
它的整个流程,我们将 libart.so 中的外部函数 pthread_create 替换成自己的方法
pthread_create_hook。
你可以发现,GOT/PLT Hook 主要是用于替换某个 SO 的外部调用,通过将外部函数调用
跳转成我们的目标函数。GOT/PLT Hook 可以说是一个非常经典的 Hook 方法,它非常稳
定,可以达到部署到生产环境的标准。
那 GOT/PLT Hook 的实现原理究竟是什么呢?你需要先对 SO 库文件的 ELF 文件格式和动
态链接过程有所了解。
ELF 格式
ELF(Executableand Linking Format)是可执行和链接格式,它是一个开放标准,各种
UNIX 系统的可执行文件大多采用 ELF 格式。虽然 ELF 文件本身就支持三种不同的类型
(重定位、执行、共享),不同的视图下格式稍微不同,不过它有一个统一的结构,这个结
构如下图所示。
网上介绍 ELF 格式的文章非常多,你可以参考《ELF 文件格式解析》。顾名思义,对于
GOT/PLT Hook 来说,我们主要关心“.plt”和“.got”这两个节区:
.plt。该节保存过程链接表(Procedure Linkage Table)。
.got。该节保存着全局的偏移量表。
我们也可以使用readelf -S来查看 ELF 文件的具体信息。
链接过程
接下来我们再来看看动态链接的过程,当需要使用一个 Native 库(.so 文件)的时候,我
们需要调用dlopen("libname.so")来加载这个库。
在我们调用了dlopen("libname.so")之后,系统首先会检查缓存中已加载的 ELF 文件
列表。如果未加载则执行加载过程,如果已加载则计数加一,忽略该调用。然后系统会用从
libname.so 的dynamic节区中读取其所依赖的库,按照相同的加载逻辑,把未在缓存中的
库加入加载列表。
你可以使用下面这个命令来查看一个库的依赖:
复制代码
1 readelf -d <library> | grep NEEDED
下面我们大概了解一下系统是如何加载的 ELF 文件的。
读 ELF 的程序头部表,把所有 PT_LOAD 的节区 mmap 到内存中。
从“.dynamic”中读取各信息项,计算并保存所有节区的虚拟地址,然后执行重定位操
作。
最后 ELF 加载成功,引用计数加一。
但是这里有一个关键点,在 ELF 文件格式中我们只有函数的绝对地址。如果想在系统中运
行,这里需要经过重定位。这其实是一个比较复杂的问题,因为不同机器的 CPU 架构、加
载顺序不同,导致我们只能在运行时计算出这个值。不过还好动态加载器
(/system/bin/linker)会帮助我们解决这个问题。
如果你理解了动态链接的过程,我们再回头来思考一下“.got”和“.plt”它们的具体含
义。
The Global Offset Table (GOT)。简单来说就是在数据段的地址表,假定我们有一些
代码段的指令引用一些地址变量,编译器会引用 GOT 表来替代直接引用绝对地址,因为
绝对地址在编译期是无法知道的,只有重定位后才会得到 ,GOT 自己本身将会包含函数
引用的绝对地址。
The Procedure Linkage Table (PLT)。PLT 不同于 GOT,它位于代码段,动态库的每
一个外部函数都会在 PLT 中有一条记录,每一条 PLT 记录都是一小段可执行代码。 一般
来说,外部代码都是在调用 PLT 表里的记录,然后 PLT 的相应记录会负责调用实际的函
数。我们一般把这种设定叫作“蹦床”(Trampoline)。
PLT 和 GOT 记录是一一对应的,并且 GOT 表第一次解析后会包含调用函数的实际地址。
既然这样,那 PLT 的意义究竟是什么呢?PLT 从某种意义上赋予我们一种懒加载的能力。
当动态库首次被加载时,所有的函数地址并没有被解析。下面让我们结合图来具体分析一下
首次函数调用,请注意图中黑色箭头为跳转,紫色为指针。
|