android 安全讲座第五层(三) android so注入的研究

最近公司研究一个变速软件,本来就是一个很小的功能,就是在用户游戏的时候能够加速减速这个功能,并且对于网络的游戏并没有做要求,实现的思路主要是给目标进程注入自己写的so,然后再hook住gettimeofday,clock_gettime 这两个比较关键的刷帧的时间函数,改变其返回值,达到刷帧变慢或者变快的效果,然后实现游戏变速的功能。 功能要求那是相当的简单,但是实现起来发现却没有那么简单,有几个技术关键点要完成,并且对月linux下的so加载要有一些了解才行。 技术关键点 1 能够顺利的将自己编写的so注入到目标进程中 2 能够正确找到目标进程调用要hook的函数的so库,并且替换其got表中的地址, 换成自己的函数地址。 3 直接找到目标进程中的地址空间中的目标函数的地址,比如send,recv,gettimeofday 这些基础的 函数一定在libc.so中也就是glibc中的,所以根据目标进程的maps表这些函数的实现地址也一定在 libc.so所占的地址空间中。找到这个函数的实际地址以后,首先通过mprotect 修改页面的属性, 让页面属性能够写入,然后喂入一段自己编写的汇编代码,跳转到自己的函数里面,达到hook的目的。 4 使用ptrace函数附着到目标进程以后,截获相关的系统调用,当系统调用出现以后,修改系统调用的 参数,达到修改函数实现的目的。 上面几个关键的技术点,其中2和3 需要以1 为基础点,而4 则可以脱离1独立存在。 下面分别描述一下几个技术的关键点的实现方法和需要注意的事项。 关键技术点1 将自己编写的so注入到目标进程中 这个的实现网上一搜索一大堆,关键核心点就是attach到目标进程,然后找到目标进程的mmap函数的地址 调用mmap函数 通过 map_base = (uint8_t *)regs.ARM_r0; 得到mmap函数的返回值,这个时候就可以将精心编写好的 一小段汇编代码写入到这个map_base这个地址上了,其实这个地址是目标进程的地址。 并且权限是 parameters[2] = PROT_READ | PROT_WRITE | PROT_EXEC; // prot 可读,可写,可执行的。 同时还需要得到 目标进程的 dlopen dlsym dlclose 这三个加载库的函数的地址,这是需要在汇编中 调用的,这些都准备好了以后,给汇编代码准备了栈空间传递了要注入的库的路径的空间以后,然后将 那一小段汇编代码拷贝到目标进程的地址 ptrace_writedata( target_pid, remote_code_ptr, local_code_ptr, 0x400 ); 接着就执行 memcpy( &regs, &original_regs, sizeof(regs) ); regs.ARM_sp = (long)remote_code_ptr; // change pc to execute instructions at remote_code_ptr regs.ARM_pc = (long)remote_code_ptr; sp指针是r13 堆栈寄存器,然后pc是r15 执行寄存器,强制赋值以后就执行汇编代码了, 而汇编代码也很简单 就是调用了一下 dlopen 将要注入的so注入到目标进程中去了,仅仅将so注入到目标进程中其实没有什么 用的,所以需要dlsym 中获取要注入的so库的一个函数,再调用一下这个函数,这个函数要干很多事情的 最重要的就是修改目标进程中加载的so的got表,,修改成要hook的自己的函数,这部分具体要在关键技术点 2 详细说明,记住这个函数是 so_entry 即可 关于技术关键点1 就这么点东西需要分析的。当然最后还需要父进程的上下文,然后detach子进程。 关键技术点2: 根据第一步,将编写好的so给注入到目标进程了,而注入的so就开始 so_entry so_entry 这个函数首先需要注入者写好要修改目标进程的哪个so库,和哪个函数,哪个函数还好说,毕竟做 不同的功能hook的函数是很明确的,比如你做游戏变速,肯定要hook gettimeofday和clock_gettime 这些 函数了,你做流量监控肯定要hook connet send recvmsg 这些函数了,你做电话或者短信防火墙就要hook 电话或者短信相关的函数。 但是修改目标进程的哪个so库,的确是一个比较麻烦的事情,android上层应用普通的进程都会挂载很多很多 的so库,并且你修改了a.so 的got表,但是java应用程序是通过b.so 调用的要hook函数,那么对不起,你 同样没有办法hook住想要的hook的函数,因为你只是修改了a.so中的got表的该函数的跳转地址,所以这种方 法的弊端在于你需要要了解你要hook的函数在这个应用中使用哪个so库调用的,或者你把maps表所有的so的 got表都修改一遍,反正就是挺麻烦的。 其中具体方法就是 do_hook 这个函数,找到so的基地址以后,再通过解析elf文件,找到got表地址的偏移 量,根据这个偏移量再加上基地址 得到got表的地址 got_shdr->sh_offset + module_base 接着再把 所在页的 mprotect((uint32_t *) entry_page_start, page_size, PROT_READ | PROT_WRITE); 属性变换成可读可写,最后将hookfun的地址替换got表的跳转地址 // replace GOT entry content with hook_func’s address memcpy((uint32_t *) entry_addr, &hook_func, sizeof(uint32_t)); 下面的这段trace分析很好的说明了这个过程maps表(部分)400dc000-4011f000 r-xp 00000000 103:02 565 /system/lib/libc.so4011f000-40122000 rw-p 00043000 103:02 565 /system/lib/libc.so40729000-407cb000 r-xp 00000000 103:02 585 /system/lib/libdvm.so407cc000-407ce000 r–p 000a2000 103:02 585 /system/lib/libdvm.so407ce000-407cf000 rw-p 000a4000 103:02 585 /system/lib/libdvm.so407cf000-407d4000 rw-p 000a5000 103:02 585 /system/lib/libdvm.so41c20000-41c23000 r-xp 00000000 103:04 588087 /data/data/com.example.socketcomm/lib/libsocketclient.so41c23000-41c25000 rw-p 00002000 103:04 588087 /data/data/com.example.socketcomm/lib/libsocketclient.so5c06c000-5c070000 r-xp 00000000 00:0c 84482 /dev/libhook.so5c070000-5c071000 r–p 00003000 00:0c 84482 /dev/libhook.so5c071000-5c072000 rw-p 00004000 00:0c 84482 /dev/libhook.so11-20 13:12:31.509: I/cheatecore-hookso(19676): [+] lib loaded …11-20 13:12:31.509: I/hook(19676): [+] base address of /system/lib/libdvm.so: 0x40729000(libdvm.so 虽然got表中有send函数,但是应用并没有通过这个库来调用socket 的send函数,所以修改这个so的got表项其实是没有用处的)11-20 13:12:31.517: I/hook(19676): [+] got entry offset of send: 0xa5f4011-20 13:12:31.517: I/hook(19676): [—-]1-407cef4011-20 13:12:31.517: D/hook(19676): [+] hook_fun addr: 0x5c06d24d(这个地址在libhook.so中r-xp)11-20 13:12:31.517: D/hook(19676): [+] got entry addr: 0x407cef40(这个地址在libdvm.so中rw-p)11-20 13:12:31.517: D/hook(19676): [+] original addr: 0x400f5f15(这是send的真实实现地址,在glib也就是libc.so这个库中r-xp中)11-20 13:12:31.517: D/hook(19676): [+] page size: 0x100011-20 13:12:31.517: D/hook(19676): [+] entry page start: 0x407ce000(mprotect 修改页属性的时候需要从整页开始)11-20 13:12:31.517: I/cheatecore-hookso(19676): [+] module_path /system/lib/libdvm.so function send address is : 0x400f5f1511-20 13:12:31.524: I/hook(19676): [+] base address of /data/data/com.example.socketcomm/lib/libsocketclient.so: 0x41c20000(libsocketclient.so got表中有send函数,但是应用通过这个库来调用socket 的send函数,所以修改这个so的got表项是可以生效的)11-20 13:12:31.524: I/hook(19676): [+] got entry offset of send: 0x3fdc11-20 13:12:31.524: I/hook(19676): [—-]1-41c23fdc11-20 13:12:31.524: D/hook(19676): [+] hook_fun addr: 0x5c06d24d11-20 13:12:31.524: D/hook(19676): [+] got entry addr: 0x41c23fdc11-20 13:12:31.524: D/hook(19676): [+] original addr: 0x400f5f15 (这是send的真实实现地址,在glib也就是libc.so这个库中r-xp中和上面的一样)11-20 13:12:31.524: D/hook(19676): [+] page size: 0x100011-20 13:12:31.524: D/hook(19676): [+] entry page start: 0x41c2300011-20 13:12:31.524: I/cheatecore-hookso(19676): [+] module_path /data/data/com.example.socketcomm/lib/libsocketclient.so function send address is : 0x400f5f15 目前这种调试方法在实践中有一个问题,就是在变速 unity3d比如神庙逃亡 这类游戏的时候,即便修改了所有的so的got表,发现也没有生效,这个问题尚在研究, 当中。 关键技术点3: 这个方法比较关键技术点2来说可以说是简单粗暴,不美观,但是很霸道,分析elf文件,查找got表等操作都和关键技术点2是一样,唯一不同的是 对于 original addr: 0x400f5f15(这是send的真实实现地址,在glib也就是libc.so这个库中r-xp中) 这个send函数的真实地址的处理方式不同,因为已经找到send函数的地址了 entry_page_start = PAGE_START(original_addr, page_size); LOGD(“[+] entry page start: 0x%x”, entry_page_start); result = mprotect((uint32_t *) entry_page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC); 找到页的边沿地址,然后通过mprotect 函数给其赋予 PROT_READ | PROT_WRITE | PROT_EXEC 这样权限 在那个地址上喂入一段汇编代码,完成跳转即可。 关键技术点4: 只是以前实验了一下,觉得原理可行,但是尚未深究…

附件有个例子,里面包含两个程序,一个是android应用,一个是命令行的注入程序,里面实现了so注入和拦截socket 的send recv函数,但是没有是实现直接向地址里面写入自己的汇编代码的功能…

世界上那些最容易的事情中,拖延时间最不费力。

android 安全讲座第五层(三) android so注入的研究

相关文章:

你感兴趣的文章:

标签云: