最近公司研究一个变速软件,本来就是一个很小的功能,就是在用户游戏的时候能够加速减速这个功能,
并且对于网络的游戏并没有做要求,实现的思路主要是给目标进程注入自己写的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.so
4011f000-40122000 rw-p 00043000 103:02 565       /system/lib/libc.so

40729000-407cb000 r-xp 00000000 103:02 585       /system/lib/libdvm.so
407cc000-407ce000 r--p 000a2000 103:02 585       /system/lib/libdvm.so
407ce000-407cf000 rw-p 000a4000 103:02 585       /system/lib/libdvm.so
407cf000-407d4000 rw-p 000a5000 103:02 585       /system/lib/libdvm.so

41c20000-41c23000 r-xp 00000000 103:04 588087    /data/data/com.example.socketcomm/lib/libsocketclient.so
41c23000-41c25000 rw-p 00002000 103:04 588087    /data/data/com.example.socketcomm/lib/libsocketclient.so

5c06c000-5c070000 r-xp 00000000 00:0c 84482      /dev/libhook.so
5c070000-5c071000 r--p 00003000 00:0c 84482      /dev/libhook.so
5c071000-5c072000 rw-p 00004000 00:0c 84482      /dev/libhook.so


11-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: 0xa5f40
11-20 13:12:31.517: I/hook(19676): [----]1-407cef40
11-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: 0x1000
11-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 : 0x400f5f15


11-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: 0x3fdc
11-20 13:12:31.524: I/hook(19676): [----]1-41c23fdc
11-20 13:12:31.524: D/hook(19676): [+] hook_fun addr: 0x5c06d24d
11-20 13:12:31.524: D/hook(19676): [+] got entry addr: 0x41c23fdc
11-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: 0x1000
11-20 13:12:31.524: D/hook(19676): [+] entry page start: 0x41c23000
11-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函数,但是没有是实现直接向地址里面写入自己的汇编代码的功能...