💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
#### **前言** 在**Android 上进行Hook 需要跨进程操作(暂不知这句话是否正确,待议)**,我们知道在Linux 上的跨进程操作需要Root权限所以目前Hook 技术广泛地应用在安全类软件的主动防御上,所见到的Hook 类病毒并不多。 Android 系统在开发中会存在两种模式,一个是Linux 的Native 模式,而另一个则是建立在虚拟机上的Java 模式。所以,我们在讨论Hook 的时候,可想而知**在Android 平台上的Hook 分为两种**。一种是**Java 层级的Hoo**k ,另一种则是**Native 层级的Hook**。两种模式下,我们通常能够通过使用JNI 机制来进行调用。但我们知道,在Java 中我们能够使用native 关键字对C/C++代码进行调用,但是在C/C++中却很难调用Java 中的代码。所以,我们**能够在Java 层级完成的事基本也不会在Native 层去完成**。 #### **什么是Hook 技术** Hook是“钩子”的意思,在Android 操作系统中系统维护着自己的一套事件分发机制。应用程序,包括应用触发事件和后台逻辑处理,也是根据事件流程一步步地向下执行。而“钩子”的意思,就是在事件传送到终点前截获井监控事件的传输,像个钩子钩上事件一样,并且能够在钩上事件时,处理一些自己特定的事件。较为形象的流程如下图所示。 :-: ![](https://box.kancloud.cn/d989870a6fae3538671e3c4701fe2659_506x224.png) * Hook 的这个本领,使它能够将自身的代码“融入”被勾住( Hook )的程序的进程中,成为目标进程的一个部分。 * 在Android 系统中使用了沙箱机制,普通用户程序的进程空间都是独立的,程序的运行彼此间都不受干扰。 * 根据Hook 对象与Hook 后处理的事件方式不同, Hook 还分为不同的种类,如消息Hook 、APIHook 等。 #### **Hook 原理** Hook技术无论对安全软件还是恶意软件都是十分关键的一项技术,其**本质就是劫持函数调用**。但是由于处于Linux 用户态,每个进程都有自己独立的进程空间,所以必须先注入到所要Hook 的进程空间,修改其内存中的进程代码,替换其过程表的符号地址。在Android 中一般是通过ptrace函数附加进程,然后向远程进程注人so 库,从而达到监控以及远程进程关键函数挂钩。 **ptrace 函数** 为方便应用软件的开发和调试,从 Unix 的早期版本开始就提供了一种对运行中的进程进行跟踪和控制的手段,那就是系统调用 ptrace()。通过 ptrace(),一个进程可以动态地读/写另一个进程的内存和寄存器,包括其指令空间、数据空间、堆栈以及所有的寄存器。与信号机制(以及其它手段)相结合, 就可以实现让一个进程在另一个进程的控制和跟踪下运行的目的。 GNU 的调试工具gdb 就是一个典型的实例。通过gdb,软件开发人员可以使一个应用程序在gdb的“监视”和操纵下受控地运行。对于受gdb 控制的进程,可以通过在其程序中设置断点,检查其堆校以确定函数调用路线,检查并改变局部变 量或全局变量的内容等等方法,来进行调试。显然,所有这些手段从概念上说都确实属于进程间“通信”的范畴,但是必须指出,这只是为软件调试而设计和设立的,**不应该用于一般的进程间通信**。**一 般而言,通信是要由双方都介入且互相协调才能完成的**。就拿“管道”来说,虽然管道是单向的,但 一定得由一方写,另一方读才能达到目的。再拿信号来说,虽然信号是异步的,也就是接收信号的一 方并不知道信号会在什么时候到来,因而在应用程序中并不主动有意地去检查有否信号到达。但是从 总体而言,接收方知道信号可能会到来,并且为此在应用程序中作出了安排。而当信号真的到来时, 接收方也“知道”其到来,并根据事先的安排作出反应。然而,**由 ptrace()所实现的“通信”却完全是单方面的,被跟踪的进程甚至并不知道(从应用程序的角度而言)自己是在受到控制和监视的条件下运行。从这个角度讲,ptrace()其实又不属于“进程间通信”**。 ptrace 提供了一种**使父进程得以监视和控制其他进程的方式,它还能够改变子进程巾的寄存器和内核映像**,因而可以实现断点调试和系统调用的跟踪。使用ptrace ,你可以在用户层拦截和修改系统调用(这个和 Hook 所要达到的目的类似),父进程还可以便子进程继续执行,并选择是再忽略引起终止的信号。 * ptrace 函数定义如下所示: ~~~ int ptrace(int request, int pid, int addr, int data); ~~~ 参数含义如下: * request 是请求ptrace 执行的操作, * pid 是口标进程的ID 。进程号,指明了操作的对象 * addr 是目标进程的地址值 * data 是作用的数据。 而 request,则是具体的操作,文件`include/linux/ptrace.h`中定义了所有可能的操作码: 我们查看ptrace.h 源码会发现有很多路径下都有这个头文件,但是里面的参数随着内核版本的升级,会有所不同。 ~~~ #define PTRACE_TRACEME 0 #define PTRACE_PEEKTEXT 1 #define PTRACE_PEEKDATA 2 #define PTRACE_PEEKUSR 3 #define PTRACE_POKETEXT 4 #define PTRACE_POKEDATA 5 #define PTRACE_POKEUSR 6 #define PTRACE_CONT 7 #define PTRACE_KILL 8 #define PTRACE_SINGLESTEP 9 #define PTRACE_ATTACH 0x10 #define PTRACE_DETACH 0x11 #define PTRACE_SYSCALL 24 ~~~ 跟踪者(如gdb)先要通过 PTRACE_ATTACH 与被跟踪进程建立起关系,或者说“ Attach,到被 跟踪进程。然后,就可以通过各种 PEEK 和 POKE 操作来读/写被跟踪进程的指令空间、数据空间或 者各个寄存器,每次都是一个长字,由 addr 指明其地址:或者,也可以通过 PTRACE_SINGLESTEP、PTRACE_KILL,PTRACE_SYSCALL 和 PTRACE_CONT 等操作来控制被跟踪进程的运行。最后,通 过 PTRACE_DETACH跟被跟踪进程脱离关系。所有这些操作都是单方面的,被跟踪进程既不能拒绝, 也无需“合作”。惟一例外是 PTRACE_TRACEME,用来主动接受跟踪。 对于ptrace 来说,它的第一个参数决定ptrace 会执行什么操作常用的有跟踪指定的进程(PTRACE ATTACH)、结束跟踪指定进程(PTRACE DETACH)等。详细的参数与使用方式如下图所示: :-: ![](https://box.kancloud.cn/3d77202abd2ff48a56923067ad54915d_765x619.png) ![](https://box.kancloud.cn/084bf0b6d5d0082a31692446b87ffc2b_765x579.png) ![](https://box.kancloud.cn/352988987aab82539884ce10e5b5644a_762x240.png) 关于ptrace函数可以参考以下文章: * [linux 分析 ptrace()](http://blog.sina.com.cn/s/blog_4ac74e9a0100n7w1.html) * [ptrace运行原理及使用详解](http://blog.csdn.net/edonlii/article/details/8717029) * [使用ptrace向已运行进程中注入.so并执行相关函数](http://blog.csdn.net/MyArrow/article/details/9630377) * [玩转ptrace(利用ptrace下断、单步调试、修改代码执行、代码注入等)](http://blog.csdn.net/beyond702/article/details/50856559) * [Android ptrace简介](http://blog.csdn.net/myarrow/article/details/9617673) * [Android平台的 Ptrace, 注入, Hook 全攻略](http://blog.csdn.net/heikefangxian23/article/details/51579857) * * * * * * Android 系统本身就提供给了我们两种开发模式,基于AndroidSDK的Java 语言开发,基于AndroidNDK 的Native C/C++语言开发。 * 对于Native 层来说Hook 的难点其实是在理解ELF(Executable and Linking Format) 文件与学习ELF文件上,特别是对ELF 文件不太了解的读者来说; * 对于Java 层来说, Hook 就需要了解虚拟机的特性与Java 上反射的使用。 **ELF文件(目标文件)格式主要三种**: * **可重定向文件**:文件保存着代码和适当的数据,用来和其他的目标文件一起来创建一个可执行文件或者是一个共享目标文件。(目标文件或者静态库文件,即linux通常后缀为.a和.o的文件) * **可执行文件**:文件保存着一个用来执行的程序。(例如bash,gcc等) * **共享目标文件**:**共享库**。文件保存着代码和合适的数据,用来被下连接编辑器和动态链接器链接。(linux下后缀为.so的文件) 一般的 ELF 文件包括三个索引表: * **ELF header**:在文件的开始,保存了路线图,描述了该文件的组织情况。 * **Program header table**:告诉系统如何创建进程映像。用来构造进程映像的目标文件必须具有程序头部表,可重定位文件不需要这个表。 * **Section header table** :包含了描述文件节区的信息,每个节区在表中都有一项,每一项给出诸如节区名称、节区大小这类信息。用于链接的目标文件必须包含节区头部表,其他目标文件可以有,也可以没有这个表。 关于ELF文件格式可以参考以下文章: * [Linux内核分析——ELF文件格式分析](https://www.cnblogs.com/20135223heweiqin/p/5554922.html) * [Android平台ELF文件格式](http://mp.weixin.qq.com/s?src=11&timestamp=1516065448&ver=639&signature=8LqsNqGYgpH7immQVbs1UEPftiU1a5baT-UlDOur5DQcdDE97lY7Vt0SW1zAkaeqq-22i4XhXiJaMYnuFSixRFgui2JoOKZndJ0ThQ3MmhY0ci7obpNVsUP5W*kie1zo&new=1) * [可执行文件(ELF)格式的理解](http://www.cnblogs.com/xmphoenix/archive/2011/10/23/2221879.html) * [ELF文件格式详解](http://blog.csdn.net/tenfyguo/article/details/5631561) * [Android逆向之旅---SO(ELF)文件格式详解](http://blog.csdn.net/jiangwei0910410003/article/details/49336613) * [Elf文件格式](http://download.csdn.net/download/jiangwei0910410003/9204051#) * [elf文件格式分析](http://blog.csdn.net/wu5795175/article/details/7657580) #### **Hook 工作流程** Hook 的原理就是改变目标函数的指向,原理看起来并不复杂,但是实现起来却不是那么的简单。 这里我们将问题细分为两个 * 如何注入代码? * 需要注入的代码我们存放在哪里? * 如何注入代码? * 如何注人动态链接库? * 我们不能只在自己的进程载入动态链接库,如何使进程附着上目标进程? * 如何让目标进程调用我们的动态链接库函数? 对于**进程附着**, Android 的内核中有一个函数叫ptrace,它能够动态地attach (跟踪一个目标进程)、detach (结束跟踪一个目标进程)、peektext (获取内存字节)、poketext (向内存写入地址)等,它能够满足我们的需求。而Android 巾的另一个内核函数dlopen ,能够以指定模式打开指定的动态链接库文件。对于程序的指向流程,我们可以调用ptrace 让PC 指向LR 堆找。 最后调用,对目标进程调用diopen 则能够将我们希望注入的动态库注入至目标进程中。 对于**代码的注入**( Hook API ),我们可以使用mmap 函数分配一段临时的内存来完成代码的存放。 >[info] 对于目标进程中的mmap 函数地址的寻找与Hook API 函数地址的寻找都需要通过目标进程的虚拟地址空间解析与ELF 文件解析来完成,具体算法如下。 > * 通过读取`/proc/<PID>/maps` 文件找到链接库的基地址。 > * 读取动态库.解析ELF 文件,找到符号(需要对ELF 文件格式的深入理解)。 > * 计算目标函数的绝对地址。 **目标进程函数绝对地址=函数地址+动态库基地址** 向目标进程中注入代码总结后的步骤分为以下几步。 * ( I )用ptrace 函数attach 上目标进程。 * ( 2 )发现装载共享库so 函数。 * ( 3 )装载指定的 .so。 * ( 4 )让目标进程的执行流程跳转到注入的代码执行。 * ( 5 )使用ptrace 函数的detach 释放目标进程。 对应的工作原理流程如下图3 所示。 :-: ![](https://box.kancloud.cn/4a4a6f8a7696648d181d7743f8e9a5f2_646x432.png) #### **Hook 的种类** Hook ,也就是平时我们所说的**函数挂钩、函数注入、函数劫持等操作**。 * 针对Android 操作系统,根据API Hook 对应API 不一样我们可以分为 * 使用Android SDK 开发环境的Java API Hook * 使用Android NDK 开发环境的Native API Hook 。 * 对于Android 中so 库文件的函数Hook ,根据ELF 文件的特性能分为 * Got 表Hook * Sym 表Hook * inline Hook 等。 * 根据Hook 方式的应用范围我们在Android这样一个特殊的环境中还能分别出 * 全局Hook * 单个应用程序Hook。 * 对于Hook 程序的运行环境不同,还可以分为 * 用户级API Hook 用户级APIHook 主要是针对在操作系统上为用户所提供的API 函数方法进行重定向修改 * 内核级API Hook 内核级API Hook 则是针对Android 内核Linux 系统提供的内核驱动模式造成的函数重定向,多数是应用在Rootkit 中。 **Java 层API Hook** **通过对Android 平台的虚拟机注入与Java 反射的方式,来改变Android 虚拟机调用函数的方式(ClassLoader),从而达到Java 函数重定向的目的**。这里我们将此类操作称为Java API Hook。 >[warning] **注意**:因为是根据Java 中的发射机制来重定向函数的,那么很多Java 中反射出现的问题也会在此出现,如无法反射调用关键字为native 的方法函数(开q 实现的函数),基本类型的静态常量无法反射修改等。 **Native 层So 库Hook** **主要是针对使用NDK开发出来的so 库文件的函数重定向,其中也包括对Android 操作系统底层的Linux 函数重定向**,如使用so 库文件( ELF 格式文件)中的全局偏移表GOT 表或符号表SYM 表进行修改从而达到的函数重定向,我们有可以对其称为GOT Hook 和SYM Hook。针对其中的inline 函数(内联函数)的Hook 称为inline Hook。 **全局Hook** 在Android 系统中,应用程序进程都是由Zygote 进程孵化出来的,而Zygote 进程是由init 进程启动的。Zygote 进程在启动时会创建一个Dalvik 虚拟机实例,每当它孵化一个新的应用程序进程时,都会将这个Dalvik 虚拟机实例复制到新的应用程序进程里面去从而使每一个应用程序进程都有一个独立的Dalvik 虚拟机实例。所以**如果选择对Zygote 进程Hook ,则能够达到针对系统上所有的应用程序进程Hook ,即一个全局Hook**。 :-: ![](https://box.kancloud.cn/6acab3450769d625d6042a30db8a2795_674x292.png) 而**对应的app_process 正是zygote 进程启动一个应用程序的入口**,常见的Hook 框架Xposed与Cydiasubstrate 也是**通过替换app_process 来完成全局Hook** 的。 #### **Hook 的危害** API Hook 技术是一种用于改变API 执行结果的技术,能够将系统的API 函数执行重定向。一个应用程序调用的函数方法被第三方Hook 重定向后,其程序执行流程与执行结果是无法确认的,更别提程序的安全性了。而Hook 技术的出现并不是为病毒和恶意程序服务的, Hook 技术更多的是应用在安全管理软件上面。但是无论怎么说,**已经被Hook 后的应用程序,就毫无安全可言了**。 #### **参考文章:** [《Android安全技术揭秘与防范》—第8章8.节什么是Hook技术](https://yq.aliyun.com/articles/99809?spm=5176.100239.blogcont99909.17.6fd61614wfPkj8#)