不同的android hook姿势

type
status
date
slug
tags
summary
category
icon
password
😭
被拷打一排甚至没听过,之前确实一点不懂这个,爬来快速过一下

一、GOT表HOOK

仅能hook got表中引用的函数,替换某个 SO 的外部调用,通过将外部函数调用跳转成我们的目标函数。
当需要使用一个 Native 库(.so 文件)的时候,我们需要调用dlopen("libname.so")来加载这个库。在我们调用了dlopen("libname.so")之后,系统首先会检查缓存中已加载的 ELF 文件列表。如果未加载则执行加载过程,如果已加载则计数加一,忽略该调用。然后系统会用从 libname.so 的dynamic节区中读取其所依赖的库,按照相同的加载逻辑,把未在缓存中的库加入加载列表。
notion image
  • Relocation Outputs(输出)
  1. .got.plt - 外部函数的绝对地址。
  1. .data,.data.rel.ro - 外部数据(包括函数指针)的绝对地址。
  • Relocation Tables(基本信息)
  1. .rel.plt,.rela.plt用于“关联”.dynsym和.got.plt。
  1. .rel.dyn,.rela.dyn,.rel.dyn.aps2,.rela.dyn.aps2用于“关联”.dynsym和.data,.data.rel.ro。
  1. .relr.dyn是Android 11新增的,仅用于ELF的内部相对relocation(基地址重写)
  • 符号信息(.dynsym 和 .dynstr)
  1. .dynstr是“字符串池”,保存了动态链接过程中用到的所有字符串信息,比如:函数名,全局变量名。
  1. .dynsym中包含了与符号关联的各种“索引”信息,起到“关联”和“描述(符号类型func/ifunc/object等等)”的作用。
  1. .dynsym中的符号分为“导入符号”和“导出符号”。SHN_UNDEF == st_shndx 为导入符号,SHN_UNDEF != st_shndx 为导出符号。
加载ELF文件:
  1. 读 ELF 的程序头部表,把所有 PT_LOAD 的节区 mmap 到内存中。
  1. 从“.dynamic”中读取各信息项,计算并保存所有节区的虚拟地址,然后执行重定位操作。
  1. 最后 ELF 加载成功,引用计数加一。
重定位:
  • The Global Offset Table (GOT)。简单来说就是在数据段的地址表,假定我们有一些代码段的指令引用一些地址变量,编译器会引用 GOT 表来替代直接引用绝对地址,因为绝对地址在编译期是无法知道的,只有重定位后才会得到 ,GOT 自己本身将会包含函数引用的绝对地址。
  • The Procedure Linkage Table (PLT)。PLT 不同于 GOT,它位于代码段,动态库的每一个外部函数都会在 PLT 中有一条记录,每一条 PLT 记录都是一小段可执行代码。 一般来说,外部代码都是在调用 PLT 表里的记录,然后 PLT 的相应记录会负责调用实际的函数。我们一般把这种设定叫作“蹦床”(Trampoline)。
PLT 和 GOT 记录是一一对应的,并且 GOT 表第一次解析后会包含调用函数的实际地址。既然这样,那 PLT 的意义究竟是什么呢?PLT 从某种意义上赋予我们一种懒加载的能力。当动态库首次被加载时,所有的函数地址并没有被解析。

实现(待自行探究)

(1)在内存中找到目标ELF
方法1:dl_iterate_phdr
优点:
  • Linux的标准 dl API,NDK提供了支持,使用方便。
缺点:
  • arm32中,Android version >= 5.0(API level 21)时NDK才支持。
  • Android 5.0和5.1(API level 21和22),dl_iterate_phdr 的实现不持linker全局锁,需要自己找linker的符号(dlZL10g_dl_mutex)自己加锁。
  • x86平台Android 4.x的dl_iterate_phdr()也不持锁,而且Android 4.x的linker全局锁符号未导出。
  • Android < 8.1(API level 27)时,不能通过 dl_iterate_phdr 遍历到 linker / linker64。(aosp从8.0开始已经包含了linker/linker64,但是大量的其他厂商的设备是从Android 8.1开始包含linker/linker64的)
  • 部分Android 4.x和Android 5.x设备的dl_iterate_phdr只能返回ELF的basename,而不是pathname。
方法2:dlopen("libdl.so")返回 linker 内部的 struct soinfo list header,自己遍历
优点:
  • 能支持 Android 4.x。
缺点:
  • Android 4.x 的linker全局锁(gDlMutex)没有符号导出,直接遍历 struct soinfo list容易挂。
  • 有一定的兼容性风险。
  • 部分Android 4.x和Android 5.x设备只能返回ELF的basename,而不是pathname。
方法3:读maps自己解析 (/proc/self/maps)
优点:
  • 不用考虑Android 4.x linker全局锁的问题。
缺点:
  • 有一定的兼容性风险。
最佳实践
Android 4.x:
  • 解析maps,使用“权限 r-xp” + “offset == 0” 来过滤,再检查ELF magic header。(4.x上ELF结构还是比较保守的,目前没有发现 r-xp 判定失败的情况)
  • 也可以先使用dlopen("libdl.so")的方式,但是兼容性需要更多的测试,如果读取失败,需要回到读maps的方式来处理。
Android >=5.0:
  • 使用 dl_iterate_phdr。
  • 对于5.0/5.1,自己用dlZL10g_dl_mutex加锁。
  • 发现ELF名称为basename时,读maps,从maps中查找对应的pathname。
  • 需要linker/linker64的话,< 8.1时需要从maps中读取。
  1. hook导入表,即“调用方”。如果需要hook进程中对于某个函数的所有调用,这种方法是比较麻烦的,需要逐个hook内存中已经加载的所有ELF,还需要监控dlopen和android_dlopen_ext(以便感知到新加载的ELF,再对它执行导入表hook)。
  1. hook导出表,即修改被调用方对应函数符号的offset值(.dynsym中对应表项的st_value),使linker通过修改后的新st_value来查找对应函数符号的内存绝对地址时,实际查找到的是内存中外部ELF的hook函数的地址,这样linker对新加载的ELF执行完relocation操作后,新ELF的相应调用就自然被hook到了我们指定的函数。
  • 查找导入表符号的方法
当存在 SYSV hash 时,先尝试通过 SYSV hash 来查找。先后按顺序尝试查找:
  • 查找导出表符号的方法
优先使用 GNU hash,比 SYSV hash 更高效。先后按顺序尝试查找:
  • 查找 .got.plt 中函数地址的方法
  1. 逐项遍历 .rel.plt 和.rela.plt 表,用上面已经找到的符号信息去比对,对应到即找到了地址的offset(r_offset项)
  1. 将 r_offset 加上ELF的“内存加载基地址”(load_bias)即为所得。
  • 查找 .data 和 .data.rel.ro 中数据地址的方法
  1. 与上面查找 .got.plt 的过程相同,只是改为了遍历 .rel.dyn 和 .rela.dyn,以及 .rel.dyn.aps2 和 .rela.dyn.aps2。
  • 修改数据(hook)
  1. 修改内存权限:使用 mprotect 将目标地址所在内存页改为可读可写
  1. 修改数据:
方法1:直接赋值,再使用 builtin_clear_cache 清除目标地址的 CPU cache。
方法2:使用atomic方式来赋值,比如:atomic_store_n((uintptr_t *)got_addr, (uintptr_t)new_func, ATOMIC_SEQ_CST);

二、LDPRELOAD_HOOK

虚假的导入函数

三、inline hook

这个在frida源码里面看的比较多,这里简单看一下怎么检测

检测手段

CRC校验:inline hook会改函数写跳板,这样的话一个常见的思路就是对函数

四、异常 hook

五、依赖库篡改 hook

六、linker hook

七、UNICOR

上一篇
shiro安全
下一篇
selinux初探
Loading...
文章列表
Hi~, I ‘m moyao
reverse
pwn
pentest
iot
android
others
ctf
iOS