本篇我们来看看adbi的实现原理,其实里面的知识点前面差不多都有涉及了,没多少新知识。adbi利用hijack程序将libexample.so注入到指定的进程中,并且在进程中加载libexample.so;而libexample.so在加载过程中会执行其.init_array section里的代码,代码中实现函数hook(替换原先的函数为自定义函数)。这样运行hijack就自动实现了函数hook。

  hijack流程图:

    1.得到pid进程下的mprotect()函数地址

    2.得到piid进程下的dlopen()函数地址

    3.利用ptrace attach pid进程,并得到regs并保存

    4.将sc结构体push进pin 进程的栈空间中;sc包含特定指令、regs、mprotect、dlopen和libexample.so的绝对路径

    5.修改regs值并设置到pin进程,接着PTRACE_DETACH释放pid进程,使之得以继续运行;此时hijack已全部执行完毕

    6.此时pid进程会去执行先前压入栈中的特定指令:

  这里涉及到2个知识点:

    1.如何得到pid进程下的指定的fun函数地址:

    假设fun在lib.so中,首先得到本进程lib.so的地址addrLib(如何得到加载的so库地址:/proc/pid/maps存储共享库的内存地址),接着得到本进程fun地址addrFun,再得到pin进程的addrPidLib地址,则pid下的fun地址=addrFun-addrLib+addrPidLib;另外一种方式是根据so中dynamic section得到dynsym和dynstr section,利用dynsym->name作为在dynstr的字符得到字符串str1和fun比较,即可得到fun的地址(具体参考之前的文章:elf格式和linker源码分析)。

    2.特定指令究竟是什么,它干了什么事?指令如下:

    特定指令用mprotect改变page读写权限;dlopen加载libexample.so;利用保存的regs恢复attach 前的pid进程状态。至于这些指令的细节看参考资料1,这里就不展开了。需要提及的是arm_pc这个寄存器,在hijack修改了pid进程的pc寄存器,使pid进程在DETACH后直接执行压入栈中的特定指令。但我们知道pc是取指令的地址,arm架构中在执行指令和取指令中还有译码,怎么会在改变pc值后直接去执行pc所执行地址的指令呢?网上是说在修改pc值后,先前的流水线(取指令、译码、执行)丢弃,然后从pc出开始取指令、译码、执行。没查到arm官方资料,望知情人告知!

  libexample执行流程:

    1.得到要hook函数hookedfun的地址;如何得到函数地址看上面方法1

    2.修改hookedfun函数指令前几个指令为特定指令,使hookedfun函数替换成自定义的函数

    3.在进程执行hookedfun函数时,会去执行自定义函数而不是hookedfun函数;再次修改hookedfun函数指令来卸载函数hook

  这里的知识点主要在于如何替换hookedfun中涉及到的汇编指令。arm分为arm指令和thum指令,在adbi中这么判断的:

  1. if (addr % 4 == 0) {
  2. arm指令
  3. }else {
  4. thumb指令
  5. }

   接着arm指令涉及到的替换函数汇编指令:

  1. h->patch = (unsigned int)hook_arm; //自定义函数的地址
  2. h->orig = addr; //hookedfun函数的地址
  3. h->jump[0] = 0xe59ff000; // LDR pc, [pc, #0]
  4. h->jump[1] = h->patch; //自定义函数的地址
  5. h->jump[2] = h->patch; //自定义函数的地址
  6. for (i = 0; i < 3; i++)
  7. h->store[i] = ((int*)h->orig)[i]; //保存hookedfun函数的指令
  8. for (i = 0; i < 3; i++)
  9. ((int*)h->orig)[i] = h->jump[i]; //修改hookedfun函数的指令,当调用hookedfun时,执行h->jump[0]

  看上面代码,执行hookedfun时其实是执行LDR pc,[pc,#0]。我们知道pc值为当前执行的指令+8,即pc值为h->jump[2] = 自定义函数的值。ok,这相当于就是jump去执行自定义函数了。下面看thumb汇编指令:

  1. if ((unsigned long int)hook_thumb % 4 == 0)
  2. log("warning hook is not thumb 0x%lx\n", (unsigned long)hook_thumb)
  3. h->thumb = 1;
  4. log("THUMB using 0x%lx\n", (unsigned long)hook_thumb)
  5. h->patch = (unsigned int)hook_thumb;
  6. h->orig = addr;
  7. h->jumpt[1] = 0xb4;
  8. h->jumpt[0] = 0x60; // push {r5,r6}
  9. h->jumpt[3] = 0xa5;
  10. h->jumpt[2] = 0x03; // add r5, pc, #12; 这里r5实质是指向jumpt[18]那为什么会说其执行自定义函数地址junpt[16]呢?
  11. h->jumpt[5] = 0x68;
  12. h->jumpt[4] = 0x2d; // ldr r5, [r5]
  13. h->jumpt[7] = 0xb0;
  14. h->jumpt[6] = 0x02; // add sp,sp,#8
  15. h->jumpt[9] = 0xb4;
  16. h->jumpt[8] = 0x20; // push {r5}
  17. h->jumpt[11] = 0xb0;
  18. h->jumpt[10] = 0x81; // sub sp,sp,#4
  19. h->jumpt[13] = 0xbd;
  20. h->jumpt[12] = 0x20; // pop {r5, pc}
  21. h->jumpt[15] = 0x46;
  22. h->jumpt[14] = 0xaf; // mov pc, r5 ; just to pad to 4 byte boundary
  23. memcpy(&h->jumpt[16], (unsigned char*)&h->patch, sizeof(unsigned int));  //存自定义函数地址到jumpt[16]——jumpt[19]
  24. unsigned int orig = addr - 1; // sub 1 to get real address
    注意这里减1了,thumb的函数被编译后其函数符号地址都会在真正地址+1,这是为了辨别thumb函数还是arm函数,arm函数4字节对齐最低位永远为0
  25. for (i = 0; i < 20; i++) {
  26. h->storet[i] = ((unsigned char*)orig)[i];
  27. //log("%0.2x ", h->storet[i])
  28. }
  29. //log("\n")
  30. for (i = 0; i < 20; i++) {
  31. ((unsigned char*)orig)[i] = h->jumpt[i];
  32. //log("%0.2x ", ((unsigned char*)orig)[i])
  33. }

    利用栈的push和pop将保存自定义函数地址(jump[16])的r5赋值为pc,具体原理看参考资料2。但下面这段话需要注意

这里还有一点需要注意,对于Thumb的“Add Rd, Rp, #expr”指令来说,如果Rp是PC寄存器的话,那么PC寄存器读出的值应该是(当前指令地址+4)& 0xFFFFFFFC,也就是去掉最后两位,算下来正好可以减去2。但这里也有个假设,就是被hook函数的起始地址必须是4字节对齐的,哪怕被hook函数使用Thumb指令集写的。

  也就说当被hook函数4字节对齐时,add r5, pc, #12这条指令的地址刚好是2字节对齐,那么根据上面这段话刚好可以减2即r5指向的是jumpt[16]而不是jumpt[18],所以这对hook函数有要求。替换函数的指令讲解完毕,卸载hook自然就清楚了——把原来改变的指令复原就ok了。我们是改变了函数指令达到替换函数的效果。但处理都包含指令cache,若执行的时候从cache中取呢,那我就再刷新下cache;在这里我们用系统调用号的方式来执行:

  1. void inline hook_cacheflush(unsigned int begin, unsigned int end)
  2. {
  3. const int syscall = 0xf0002;
  4. __asm __volatile (
  5. "mov r0, %0\n"
  6. "mov r1, %1\n"
  7. "mov r7, %2\n"
  8. "mov r2, #0x0\n"
  9. "svc 0x00000000\n"
  10. :
  11. : "r" (begin), "r" (end), "r" (syscall)
  12. : "r0", "r1", "r7"
  13. );
  14. }

  r0=begin,r1=end,r7=0xf0002(cacheflush的系统调用号),直接svc来执行系统调用。

  hijack和libexample的流程就这样了,但怎么从hijack到libexample啊?

  1. // this file is going to be compiled into a thumb mode binary
  2. // 这里是重点,当进程第一次打开lib操作时,linker会执行此函数
  3. void __attribute__ ((constructor)) my_init(void);

  还记得hijack执行完毕后,pid进程执行的指令吗;它会去执行dlopen(libexample),此时回去执行libexample中的.init_array中的指令;而加了"__attribute__ ((constructor))"则my_init就是在.init_array中。知道了吧,在dlopen中执行my_init,而在my_init中实现函数的替换即hook。当然my_init的函数可以不同,但必须有在.init_array中的函数去执行函数替换功能(对于adbi来说是调用hook())。

  最后我们看看libinject:

  1.利用ptrace来注入pid进程

  2.得到pid进程中的函数地址,用的是上面第一种方法:fun地址=addrFun-addrLib+addrPidLib

  3.libinject不注入so没实现函数挂钩,它实现了hijack的功能,但是它不仅仅是dlopen还在这里执行了自定义函数(实现方式与adbi相同)

  下篇我们来看看adbi是如何实现dalvik hook

参考资料:

  1 Android平台下hook框架adbi的研究(上)

  2 Android平台下hook框架adbi的研究(下)

adbi学习:so hook实现机制的更多相关文章

  1. adbi学习:java hook实现机制

    adbi的java hook实现代码ddi不在之前下载的文件中,下载地址:https://github.com/crmulliner/ddi,具体的编译看readme里面很详细的介绍了.注意ddi代码 ...

  2. 九、Android学习第八天——广播机制与WIFI网络操作(转)

    (转自:http://wenku.baidu.com/view/af39b3164431b90d6c85c72f.html) 九.Android学习第八天——广播机制与WIFI网络操作 今天熟悉了An ...

  3. php中的钩子(hook插件机制)

    对"钩子"这个概念其实不熟悉,最近看到一个php框架中用到这种机制来扩展项目,所以大概来了解下. hook插件机制的基本思想: 在项目代码中,你认为要扩展(暂时不扩展)的地方放置一 ...

  4. java学习笔记09--反射机制

    java学习笔记09--反射机制 什么是反射: 反射是java语言的一个特性,它允许程序在运行时来进行自我检查并且对内部的成员进行操作.例如它允许一个java的类获取他所有的成员变量和方法并且显示出来 ...

  5. Storm学习笔记 - 消息容错机制

    Storm学习笔记 - 消息容错机制 文章来自「随笔」 http://jsynk.cn/blog/articles/153.html 1. Storm消息容错机制概念 一个提供了可靠的处理机制的spo ...

  6. php hook编程机制

    说明 hook,中文翻译为钩子,编程中的钩子类似我们现实中的钩子,需要挂在东西的时候    直接挂载到上面即可.程序中也是,需要运行的代码挂载到上面即可.         具体思想就是:在项目代码中, ...

  7. 阿里系产品Xposed Hook检测机制原理分析

    阿里系产品Xposed Hook检测机制原理分析 导语: 在逆向分析android App过程中,我们时常用的用的Java层hook框架就是Xposed Hook框架了.一些应用程序厂商为了保护自家a ...

  8. dubbo学习之路-SPI机制

    dubbo学习之路-SPI机制 1.SPI 1.1Java SPI 原理 SPI是service provider interface简称.在java JDK中 内置的一种服务提供发现机制.它解决在一 ...

  9. adbi学习:安装和使用

    adbi 是一个android平台(arm 32 )的so注入+挂钩框架,源码开放在github上 :  ADBI 项目 .从github上下载来目录如下: 执行主目录下build.sh编译后目录如下 ...

随机推荐

  1. POJ-3436(网络流+最大流+输出路径)

    ACM Computer Factory POJ-3436 题目就是一个工厂n个加工机器,每个机器有一个效率w,q个材料入口,q个材料出口,每个口有三个数表示状态,1表示一定有入/出的材料,0表示没有 ...

  2. 【Arduino学习笔记06】上拉电阻和下拉电阻

    为什么要用上拉电阻和下拉电阻?--避免输入引脚处于"悬空"状态 下图是一个没有使用上拉电阻/下拉电阻的电路图: 在按键没有按下时,要读取的输入引脚没有连接到任何东西,这种状态就称为 ...

  3. C#扩展方法的一分钟小例子

    扩展方法是静态方法,是类的一部分,但没有在类的源代码中,就像一个补丁 首先创建一个静态类,然后创建一个静态方法,重点是静态方法的参数 public static class xExtension { ...

  4. httpPost的两种方式

    1,post-Body流和post参数,以下客户端代码和服务端代码可共用 客户端代码 /** * post 方法 * 抛送给EDI * @param url http://127.0.0.1:9003 ...

  5. sprintgboot+springsecurity的跨域问题,

    整个项目是使用前后端分离的形式开发,登录接口部分出现了问题, 重写了security的登录接口,返回json数据 到这一步已经没有没有问题了,使用postman测试,也可以看到接口返回的结果,但是使用 ...

  6. 自己挖的坑自己填--Mybatis mapper文件if标签中number类型及String类型的坑

    1.现象描述 (1)使用 Mybatis 在进行数据更新时,大部分时候update语句都需要通过动态SQL进行拼接.在其中,if标签中经常会有 xxx !='' 这种判断,若 number 类型的字段 ...

  7. linux云服务搭建Minecraft服务器

    1 准备工作 以下内容全部要在root用户内完成 1.1 安装文件传输工具 为了方便传文件到服务器上,这里先装一个远程传输工具. yum -y install lrzsz 1.2 安装java Min ...

  8. 痞子衡嵌入式:盘点国内MCU级RISC-V内核IP厂商

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是国内MCU级RISC-V内核IP厂商. 自RISC-V指令集2010年诞生以来,业界普遍认为,RISC-V将会改变现有的由Arm和Int ...

  9. 上万字详解Spark Core(建议收藏)

    先来一个问题,也是面试中常问的: Spark为什么会流行? 原因1:优秀的数据模型和丰富计算抽象 Spark 产生之前,已经有MapReduce这类非常成熟的计算系统存在了,并提供了高层次的API(m ...

  10. python3 list合并

    1 t1=[x for x in range(5)] 2 t2=[x for x in range(5,10)] 3 4 #way1:通过方法extend(),直接修改列表,无返回值 5 # t1.e ...