转载一篇关于Android5.1的ART HOOK方案。

首先简单介绍一下hook。所谓hook就是通过一些手段改变一个函数的执行逻辑,比如在函数调用前更改一下参数或者在调用后修改返回值,甚至直接返回,不执行原函数。这篇文章就是介绍Android ART虚拟机上的关于java 函数的hook技术。

废话不说,切入正题。 
之前看过低端码农关于ART HOOK的思路,有了启发,大家可以先到他的博客上看看他对于ART虚拟机的理解以及他做的hook方案。 
http://blog.csdn.net/l173864930/article/details/45035521 
他做的是基于android 4.4的hook方案,但是是停留在仅仅打log的阶段,在我后面的测试中发现这其实离真正的hook应用还相去甚远。下面我罗列了一些我需要解决的问题。这些问题,低端码农有解决过一些,有的没有;xposed有解决过一些,有的没有。 
我们需要解决如下的几个问题: 
1. 如何hook到一个函数 
2. 如何处理参数 
3. 如何处理返回值

0x00 
老调重弹,我先简单介绍一下ART虚拟机关于方法的调用方式。不同于dalvik虚拟机,ART其实包含了两种调用方式——解释执行和机器码执行,首先他没有完全丢弃解释执行的调用方式,因为有些情况下还是需要通过解释执行完成一个函数的运行;接着ART不同于dalvik是因为引入了机器码的运行方式,其实就是在dex opt的时候dex里的一个函数体被优化成了汇编语言编写的机器码,这样运行效率当然高了。 
下面看一下oatdump出的某函数片段

4: void com.example.atry.MainActivity.onClick(android.view.View) (dex_method_idx=18)DEX CODE:0x0000: const/4 v0, #+20x0001: const-wide/16 v2, #+50x0003: invoke-virtual {v4, v0, v2, v3}, void com.example.atry.MainActivity.nativeTest(int, long) // method@170x0006: return-voidOAT DATA:frame_size_in_bytes: 64core_spill_mask: 0x00008060 (r5, r6, r15)fp_spill_mask: 0x00000000 vmap_table: 0xf722d58a (offset=0x0000258a)v3/r5, v4/r6, v65535/r15mapping_table: 0xf722d584 (offset=0x00002584)gc_map: 0xf722d590 (offset=0x00002590)CODE: 0xf722d51d (offset=0x0000251d size=104)...0xf722d51c: f8d9c010  ldr.w   r12, [r9, #16]  ; stack_end_0xf722d520: e92d4060  push    {r5, r6, lr}0xf722d524: f2ad0e34  subw    lr, sp, #520xf722d528: 45e6      cmp     lr, r120xf722d52a: f0c08024  bcc.w   +72 (0xf722d576)0xf722d52e: 46f5      mov     sp, lr0xf722d530: 9000      str     r0, [sp, #0]0xf722d532: 1c0e      mov     r6, r10xf722d534: 9212      str     r2, [sp, #72]0xf722d536: 2202      movs    r2, #20xf722d538: 9208      str     r2, [sp, #32]0xf722d53a: 2305      movs    r3, #50xf722d53c: f04f0c00  mov.w   r12, ThumbExpand(0)0xf722d540: e9cd3c0a          0xf722d544: 9b0b      ldr     r3, [sp, #44]0xf722d546: 1c31      mov     r1, r60xf722d548: f8d1e000  ldr.w   lr, [r1, #0]0xf722d54c: 9304      str     r3, [sp, #16]0xf722d54e: 9304      str     r3, [sp, #16]0xf722d550: f8dee034  ldr.w   lr, [lr, #52]0xf722d554: 9b0a      ldr     r3, [sp, #40]0xf722d556: 2202      movs    r2, #20xf722d558: f8de0544  ldr.w   r0, [lr, #1348]0xf722d55c: f8d0e028  ldr.w   lr, [r0, #40]0xf722d560: 47f0      blx     lrsuspend point dex PC: 0x0003
…

包含了smali代码和汇编代码。

由于ART是这种大杂烩的执行函数的方式,因此他就要确定一个函数是通过解释执行来运行,还是通过机器码来运行,所以在4.4及以后的版本的art上出现了bridge的概念,他可以被理解为解释执行方式跳转到机器码执行方式或者机器码执行方式跳转到解释执行方式的桥梁。举例说明,就是本来a,b,c,d四个函数都是顺序执行在机器码执行的方式下,突然在调用e这个函数的时候发现需要跳转到解释执行的方式,这就需要一个bridge。 
下面结合代码看一下,首先是art_method.h(忽略了无关代码)

class MANAGED ArtMethod : public Object {
…
protected:Class* declaring_class_;uint32_t access_flags_;uint32_t code_item_offset_;const void* entry_point_from_compiled_code_;EntryPointFromInterpreter* entry_point_from_interpreter_;
….
}

这里entry_point_from_compiled_code_和entry_point_from_interpreter_就是2个bridge。一个是说从code(机器码)转来的,去哪里由这个bridge决定,一个是说从interpreter转来的,去哪里由这个bridge决定。其实每次函数调用,调用者都是执行被调用者的bridge。举例说明,如果一个函数是在机器码执行流程里,他调用下一个函数的时候会调用被调用者的成员接口entry_point_from_compiled_code_(意思是告诉被调用者,这是来自机器码的执行流程),如果被调用者的这个接口被设为机器码的执行入口,那么被调用者就直接被执行了,也就是是说被调用者也是在机器码执行流程中;否则,这个接口如果被设为一个解释执行函数的入口函数,被调用者就会在解释执行中被运行了。下面介绍的一个就是一个解释执行的入口函数。

ENTRY art_quick_to_interpreter_bridgeSETUP_REF_AND_ARGS_CALLEE_SAVE_FRAMEmov     r1, r9                 @ pass Thread::Currentmov     r2, sp                 @ pass SPblx     artQuickToInterpreterBridge    @ (Method* method, Thread*, SP)ldr     r2, [r9, #THREAD_EXCEPTION_OFFSET]  @ load Thread::Current()->exception_ldr     lr,  [sp, #44]         @ restore lradd     sp,  #48               @ pop frame.cfi_adjust_cfa_offset -48cbnz    r2, 1f                 @ success if no exception is pendingbx    lr                       @ return on success
1:DELIVER_PENDING_EXCEPTION
END art_quick_to_interpreter_bridge

可以看到最终调用到artQuickToInterpreterBridge中去了,在那里就会对这个函数进行了解释执行。

把之前的com.example.atry.MainActivity.onClick函数的内容再拿来分析一下(去掉无关代码)

4: void com.example.atry.MainActivity.onClick(android.view.View) (dex_method_idx=18)CODE: 0xf722d51d (offset=0x0000251d size=104)...0xf722d51c: f8d9c010  ldr.w   r12, [r9, #16]  ; stack_end_0xf722d520: e92d4060  push    {r5, r6, lr}0xf722d524: f2ad0e34  subw    lr, sp, #520xf722d528: 45e6      cmp     lr, r120xf722d52a: f0c08024  bcc.w   +72 (0xf722d576)
//上面都是检查是否调用函数层数太多,防止栈溢出。0xf722d52e: 46f5      mov     sp, lr0xf722d530: 9000      str     r0, [sp, #0]0xf722d532: 1c0e      mov     r6, r10xf722d534: 9212      str     r2, [sp, #72]0xf722d536: 2202      movs    r2, #20xf722d538: 9208      str     r2, [sp, #32]0xf722d53a: 2305      movs    r3, #50xf722d53c: f04f0c00  mov.w   r12, ThumbExpand(0)0xf722d540: e9cd3c0a          0xf722d544: 9b0b      ldr     r3, [sp, #44]0xf722d546: 1c31      mov     r1, r60xf722d548: f8d1e000  ldr.w   lr, [r1, #0]0xf722d54c: 9304      str     r3, [sp, #16]0xf722d54e: 9304      str     r3, [sp, #16]0xf722d550: f8dee034  ldr.w   lr, [lr, #52]0xf722d554: 9b0a      ldr     r3, [sp, #40]0xf722d556: 2202      movs    r2, #2//上面都是在构造参数,准备调用下个函数0xf722d558: f8de0544  ldr.w   r0, [lr, #1348] //找到了被调用函数nativeTest0xf722d55c: f8d0e028  ldr.w   lr, [r0, #40]//取出被调用函数首地址偏移40的地址0xf722d560: 47f0      blx     lr//跳转到偏移40处的地址…

从上的代码可以看到,机器码直接跳转到被调用函数偏移40的位置,而那就是被调用函数的entry_point_from_compiled_code_接口。(4.4是偏移40,5.1是偏移44)

机器码执行的函数在初始化的时候会设置entry_point_from_compiled_code_为机器码执行入口;而如果这个函数需要解释执行,则entry_point_from_compiled_code_会被设为art_quick_to_interpreter_bridge(绝大多数的).

说了这么多,就引出了第一个问题的答案,如何hook一个函数?我们可以把一个函数偏移40处的地址存的值设为我们自己写的函数地址,这样,一个函数的执行流程就被hook到了。 
代码示例:

static jint hook_zposed_method(JNIEnv* env, jobject thiz, jobject method) {jmethodID methid = (*env)->FromReflectedMethod(env, method);int artmeth = (int) methid;int* quick_entry_32 = (int*) (artmeth + 40);jint ptr = (jint)* quick_entry_32;*quick_entry_32 = (int) (&art_quick_proxy);
/*int* access_flag = (int*) (artmeth + METHOD_ACCESS_FLAG);*access_flag = *access_flag | kAccNative;int* mapping_table = (int*) (artmeth + METHOD_MAPPING_TABLE);*mapping_table = 0;*/return ptr;
}

art_quick_proxy就是我们自己写的函数,事实证明在调用被hook函数的时候,调用的其实是art_quick_proxy。

0x01 
如何处理参数 
处理参数问题就和主流的参数处理方法一致了,我这里就是遍历堆栈,获取参数,然后通过调用java函数对基本类型装箱,最后由一个Object数组的形式封装所有的参数。下面具体介绍一下。 
首先要介绍一下art虚拟机上参数是如何传递的。在汇编层面,参数组织如下: 
r0 = method 
r1 = this 
r2 = arg0 
r3 = arg1 
[sp] = N/A 
[sp + 4] = N/A 
[sp + 8] = N/A 
[sp + 12] = N/A 
[sp + 16] = arg2 
需要注意的就是堆栈中的前4个存储单元里存的东西未知,不管是什么,肯定是我们不需要的,但是又不建议妄自修改的东西。 
然后我们可以从r2寄存器开始遍历,取出所有的参数。基本参数的装箱就是指将int, short等类型转换为Integer, Short这样的Object,java里已经有这样的函数供我们使用了: 
Integer.valueOf(int); 
Short.valueOf(short); 

0x02 
如何处理返回值 
我想到的办法就是,为每一个被hook的函数都分配一个与其返回值对应的hook处理函数,由于返回值类型是确定的(8种基本类型加Object),所有我枚举的构造了9种不同返回值的hook处理函数(直接java编写)。

    private static int onHookInt(Object artmethod, Object receiver, Object[] args) {return (Integer) HookManager.onHooked(artmethod, receiver, args);}private static long onHookLong(Object artmethod, Object receiver, Object[] args) {return (Long) HookManager.onHooked(artmethod, receiver, args);}
...

大概都是以上的形式,不一一列举了。 
而HookManager.onHooked返回的是Object类型,对于基本类型来说,我们只要对其拆箱就可以了。

0x03 
结束

【转载】Android 5.1 Art Hook 技术分享相关推荐

  1. android so hook技术,【原创】Android5.1 Art Hook 技术分享,求加精转正式会员

    标 题:[原创]Android5.1 Art Hook 技术分享,求加精转正式会员作 者: luciya 时 间: 2015-09-16,22:42:53 链 接: http://bbs.pediy. ...

  2. 【原创】Android5.1 Art Hook 技术分享

    [原创]Android5.1 Art Hook 技术分享 Hi,大家好,很多次的在各种技术论坛上看到大牛的分享,学到了很多.本着共建社区,共享知识的目的,在这里我和大家分享一下我最近研究到的关于And ...

  3. Android5.1 Art Hook 技术分享

    转自:http://bbs.pediy.com/thread-204183.htm Hi,大家好,很多次的在各种技术论坛上看到大牛的分享,学到了很多.本着共建社区,共享知识的目的,在这里我和大家分享一 ...

  4. Android Art Hook 技术方案

    Android Art Hook 技术方案 by 低端码农 at 2015.4.13 www.im-boy.net 0x1 开始 Anddroid上的ART从5.0之后变成默认的选择,可见ART的重要 ...

  5. Android APP热更新中的插件化(Hook技术:反射或动态代理),Demo (2)

    修改AAPT,资源分区,用于Android插件化- https://github.com/BaoBaoJianqiang/AAPT -- Android下的挂钩(hook)和代码注入(inject) ...

  6. 【Android 插件化】Hook 插件化框架总结 ( 插件包管理 | Hook Activity 启动流程 | Hook 插件包资源加载 ) ★★★

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  7. android socket_盘点Android常用Hook技术

    Android平台开发测试过程中,Hook技术是每个开发人员都常用的技术.可以用于绕过系统限制.修改别人发布的代码.动态化.调用隐藏API.插件化.组件化.自动化测试.沙箱等等. Hook如果要跨进程 ...

  8. android4 设置栈大小,【技术分享】Android内核漏洞利用技术实战:环境搭建栈溢出实战...

    [技术分享]Android内核漏洞利用技术实战:环境搭建&栈溢出实战 2017-08-14 16:22:02 阅读:0次 预估稿费:300RMB 投稿方式:发送邮件至linwei#360.cn ...

  9. 转载:使用 Frida 来 hook 加固的 Android 应用的 java 层

    Android 加固应用Hook方式 --- Frida:https://github.com/xiaokanghub/Android 转载:使用 frida 来 hook 加固的 Android 应 ...

最新文章

  1. 【Paper】2017_水下潜航器编队海洋勘测的协调控制方法研究
  2. vs2017c语言程序添加图标,笔试编程必备技巧——Visual Studio 2017添加自定义代码片段...
  3. 使用GitGUI创建上传本地工程
  4. Objective-C模版方法(TemplateMethod)
  5. linux系统get命令详解,Ubuntu Linux系统下apt-get命令详解
  6. nacos 持久化 mysql(windows/linux环境)
  7. java 日期操作工具类_java8操作日期的工具类
  8. MTK 驱动开发(29)---TP 驱动移植
  9. UI设计师应该知道的汉字体种类的用途(免费素材)
  10. 您可能(或可能不)知道的5条便捷的Transact-SQL技巧
  11. 2018.4.3 做lab0
  12. java socket 断开连接_Socket.IO-client.java重复断开连接并重新连接
  13. ipad浏览器安装java_360浏览器苹果平板下载
  14. CMDN创新应用推荐:搜狗号码通
  15. VMware Fusion网络配置相关原理
  16. 1.TCL/TK脚本学习——入门基础
  17. 修复手机通讯服务器软件,手机通讯录误删怎么恢复?恢复原来如此简单,后悔太晚知道...
  18. ’Hive快速入门课程视频【菜鸟窝出品】
  19. 六代单传的老代码,到底能不能动
  20. 北大美女王婷婷辞去公司副总职务创业养狗(图)

热门文章

  1. 浅析能耗管理系统在企业中的应用
  2. MATLAB学习——获取官方学习资料的方式
  3. 转载和积累系列 - 基于JavaScript的DDoS攻击
  4. 【路径规划-VRP问题】基于模拟退火 (SA)求解车辆配送 (VPR)附Matlab代码
  5. Unicode,ASCII和UTF-8 编码区别与联系(通俗易懂)
  6. 倍福TwinCAT(贝福Beckhoff)基础教程 松下伺服驱动器报错 88怎么办
  7. 《极品飞车21:热度》图文攻略
  8. 【转帖】基于NDIS(网络驱动接口标准)包拦截技术
  9. pytorch查看通道数 维数 尺寸大小
  10. USB转SPI 选型