Android下的GOT表Hook实现
背景
导入表Hook是比较常见的一个android hook技术,其主要是通过ptrace注入so到目标进程中,然后把自己的函数地址替换到GOT表对应位置,试图拦截函数,改变函数的执行流程。目前外网存在一些外挂修改器通过GOT表Hook的方式修改游戏的执行逻辑,以实现外挂功能。接下来,我们讨论下GOT表Hook的实现原理。
实现原理
一、ptrace注入
ptrace注入网上有很多参考资料,可以通过直接注入或者间接通过zygote注入到进程中。因为注入不是本文重点,这里只是简要介绍下其中一种可行的实现,过程如下:
1、获取目标进程的pid,一般通过查找/proc/pid/cmdline关联进程,ptrace_attach之。
2、ptrace获取目标进程寄存器值,保存,然后遍历/proc/pid/maps拿到本地和远程模块基址,和本地的绝对地址运算就得到dlopen,dlsym,mmap的远程地址。
3、用远程mmap分配内存并写入自己的shellcode等信息,修改寄存器执行,成功注入自己的so并执行某个init函数。
4、恢复远程进程寄存器,并detach之。
二、GOT表替换
Android下对导入表Hook一般是对加载的so进行hook,so是elf格式,我们可以通过解析elf来定位GOT表,然后直接修改GOT表的值,稍后再介绍一种用的比较多的方法。
首先从so文件开始处读取开始的一个Elf Header 大小的Ehdr结构体,然后直接从这个结构体索引Elf32_Off e_shoff,这个e_shoff 可以带我们走进section_table中,指向section_header的首地址处,即节信息。而e_shnum保存的是这个section的结构体数组个数。
如下图是section_header,我们看看,这个talble保存了一些节信息:
上面的Value是解析后的,我们自己解析的话要从.shstrtab入手,遍历每一个struct,然后判断section_table里面的type是不是SHT_STRTAB,是的话,就代表该结构是.shstrtab,当然一般不用这个方法,因为我们关注到其实Ehdr结构体里面有一个e_shstrndx,这个直接能索引到e_shstrndx结构体。然后通过其sh_offset得到section_table的字符串base,然后每个section_table的名字,就可以通过sh_name加这个base获得。简单理解可以为:
char *base = (char *)((Elf32_Shdr *)pEhrd->e_shoff + pEhrd->e_shstrndx)->sh_offset
用图来说的话,base的地址就是下面红色圈圈的地方:
然后我们找到了.got表,在通过里面的sh_offset可以定位到文件的偏移地址,配合自己在/proc/self/maps里面找到的模块基址,就可以直接定位到进程内so中GOT表的位置了。 假设我们要替换gettimeofday这个函数,只需要直接获取gettimeofday的地址,然后遍历GOT表,找到后替换就可以了。这是第一种解决方案。
第二种解决方案是通过.rel.plt表来定位GOT表。先拿到下面两个比较重要的信息:
然后到sh_offset位置中按照下面的struct遍历,通过ELF32_R_SYM(pERel->r_info)获取出symindex。
这个symindex是.symtab的索引,可以引导我们找出.rel.plt重定位每一项的信息。这里我们介绍下.dynstr表,这个表和.shstrtab有点类似,包含so中字符串区域的首地址和大小等信息。我们可以通过type或者.shstrtab的字符串信息找出.dynstr,然后取出里面的sh_offset就可以得到so字符串区域的首地址strtabbase了。
那如果我要找gettimeofday的字符串,可以通过symindex遍历每个字符串,即:strtabbase+((Elf32_sym)symtab+ELF32_R_SYM(pERel->r_info)).st_name
判断出是该字符串,就马上取出当前的r_offset,加上模块的base地址,就可以得到指向对应GOT表位置的指针,直接修改成自己的函数就可以了。
而第三种解决方案,对于替换gettimeofday这一类的导入函数,也是通过类似第二种的重定位方式实现,但是它可以减少我们的分析量和代码量,也是目前笔者发现的用的比较多的一种GOT表Hook方案。核心是通过dlopen获取已经加载的so的hanler,dlopen的返回值是void *,看linker头文件可以发现其实质是一个soinfo结构体指针。这个结构体包含了我们想要的很多的信息,如symtab,strtab,plt_rel,plt_count等等。
下面是葫芦侠的核心GOT表hook逻辑,是不是很熟悉,没错。它用的方法就是本文介绍的第三种方法。
遍历plt_rel的每一个项,通过r_info信息和strtab找到对应的字符串位置,然后比较是不是自己要找的字符串,直接对base + pERel ->r_offset 指向的value进行替换。
到了这里,我们就介绍完了这三种GOT表的Hook方法了。
小结
本文介绍android平台下GOT表Hook的三种方法。通过这些方法,我们也更深一步地了解elf链接视图的格式。这些方法的核心是不变的,就是注入so到进程,找到GOT表位置,地址替换。大道至简,其他细节请读者慢慢琢磨。