热修复系列——Android热修复技术进阶篇

热修复系列——Android热修复技术进阶篇,第1张

目录

1 前言

2 热修复技术

2.1 Dexposed

2.2 AndFix

2.3 QZone

2.4 Tinker

2.6 epic

2.7 YAHFA

2.8 FastHook

2.9 美团Robust

3 总结


1 前言

对热修复技术做一个总结,也是告一个段落(几年前的研究,有些可能过时了)

以AndFix入门,具体可以参考以前的使用步骤。

热修复系列——AndFix使用教程基础篇_疑是银河落九天的博客-CSDN博客_andfix使用

2 热修复技术 2.1 Dexposed

详情参考

Android中免Root实现Hook的Dexposed框架实现原理解析以及如何实现应用的热修复_尼古拉斯.赵四的博客-CSDN博客_dexposed

工作原理

  1. 基于Xposed的AOP框架
  2. 免root实现方法级粒度hook
  3. 通过JNI注册,从Java转Native,然后在native中修改被hook方法的一些信息,最后通过反射调用handleHookedMethod回到Java
  4. 立即生效

限制

不支持ART(xposed重新编译了自己的libart.so,并阻止内联,需要对不同版本兼容)

2.2 AndFix

详情参考

Android热修复框架AndFix原理解析及使用_尼古拉斯.赵四的博客-CSDN博客

工作原理

AndFixManager.java类的replaceMethod方法

private void replaceMethod(ClassLoader classLoader, String clz,
			String meth, Method method) {
		try {
			String key = clz + "@" + classLoader.toString();
			//key为新类名@默认类加载器名
			Class clazz = mFixedClass.get(key);
			if (clazz == null) {// class not load
			//使用默认类加载器加载修复类对应的原始类
				Class clzz = classLoader.loadClass(clz);
	...
}

ArtMethod结构替换代码

void replace_7_0(JNIEnv* env, jobject src, jobject dest) {
	art::mirror::ArtMethod* smeth =
			(art::mirror::ArtMethod*) env->FromReflectedMethod(src);

	art::mirror::ArtMethod* dmeth =
			(art::mirror::ArtMethod*) env->FromReflectedMethod(dest);

//	reinterpret_cast(smeth->declaring_class_)->class_loader_ =
//			reinterpret_cast(dmeth->declaring_class_)->class_loader_; //for plugin classloader
	reinterpret_cast(dmeth->declaring_class_)->clinit_thread_id_ =
			reinterpret_cast(smeth->declaring_class_)->clinit_thread_id_;
	reinterpret_cast(dmeth->declaring_class_)->status_ =
			reinterpret_cast(smeth->declaring_class_)->status_ -1;
	//for reflection invoke
	reinterpret_cast(dmeth->declaring_class_)->super_class_ = 0;

	smeth->declaring_class_ = dmeth->declaring_class_;
	smeth->access_flags_ = dmeth->access_flags_  | 0x0001;
	smeth->dex_code_item_offset_ = dmeth->dex_code_item_offset_;
	smeth->dex_method_index_ = dmeth->dex_method_index_;
	smeth->method_index_ = dmeth->method_index_;
	smeth->hotness_count_ = dmeth->hotness_count_;

	smeth->ptr_sized_fields_.dex_cache_resolved_methods_ =
			dmeth->ptr_sized_fields_.dex_cache_resolved_methods_;
	smeth->ptr_sized_fields_.dex_cache_resolved_types_ =
			dmeth->ptr_sized_fields_.dex_cache_resolved_types_;

	smeth->ptr_sized_fields_.entry_point_from_jni_ =
			dmeth->ptr_sized_fields_.entry_point_from_jni_;
	smeth->ptr_sized_fields_.entry_point_from_quick_compiled_code_ =
			dmeth->ptr_sized_fields_.entry_point_from_quick_compiled_code_;

	LOGD("replace_7_0: %d , %d",
			smeth->ptr_sized_fields_.entry_point_from_quick_compiled_code_,
			dmeth->ptr_sized_fields_.entry_point_from_quick_compiled_code_);

}

限制

  1. 以Field为切入点,使用自定义类加载器生成新的ArtMethod,使用默认类加载器生成旧ArtMethod,再进行结构内字段的替换。
  2. AndFix采用的是替换结构体中的字段,但由于厂商可能会修改ArtMethod结构体,因此会有兼容问题。Sophix(是阿里推出的移动热修复方案,闭源,有本书《深入探索Android热修复原理》介绍了Sophix,支持)采用的是替换整个ArtMethod结构体,这样就不会存在兼容问题。
  3. 立即生效
  4. 目前支持2.3 - 7.0
  5. 《深入探索Android热修复原理》讲述sophix的对比图如下
2.3 QZone

详情参考

安卓App热补丁动态修复技术介绍

工作原理

  1. classloader可以包含多个dex文件,排成一个有序数组dexElements,当查找类时会按顺序遍历dex文件,如果不同的dex有相同的类存在,那么会优先选择排在前面的dex文件中的类
  2. 如果一个类与它直接引用的类在同一个dex文件中,那么该类会被打上preverify标签。因此如果需要修复类与引用类所在dex文件不同则会报错。解决方案是往所有类的构造函数插桩引用一个不存在的类,防止被打上标签
  3. 重启生效,因为已经加载过的类无法被卸载,想要重新加载新的类就需要重启APP。

限制

  1. 由于插桩导致所有类都非preverify,因此加载类时会触发verify与optimize *** 作,由于启动时会加载大量的类,因此效率影响较大
  2. 存在art下内存地址混乱问题,因为dex2oat将类能确定的各个地址写死,运行时补丁包的地址改变,原始类调用就会出错
2.4 Tinker

详情参考

GitHub - WeMobileDev/article: articles by WeChat Mobile Development Team

工作原理

使用DexDiff算法,在编译时通过新旧Dex生成差异包,将差异包与旧Dex还原为新Dex,重启生效

限制

  1. 占用Rom体积,大约是修改Dex数量的1.5倍(dexopt与dex压缩成jar)的大小
  2. 需要额外进程合成新的Dex,合成时间的长短与内存消耗也会影响最终的成功率
2.6 epic

详情参考

我为Dexposed续一秒——论ART上运行时 Method AOP实现 | Weishu's Notes

https://github.com/tiann/epic/blob/master/README_cn.md

工作原理

通过callee的ArtMethod对象得到方法的entrypoint,根据方法入口点找到对应地址,直接将内存的前8个字节修改为一段跳转指令,然后在跳转指令中执行二段跳板,进而跳转到新方法中执行

限制

  1. 受限于dynamic callee-side rewrite机制,如果被Hook函数的code段太短以至于一个简单的trampoline跳转都放不下,那么epic无效
  2. 如果ART中有深度内联,直接把本函数的代码内联到调用者,那么epic无效
  3. 仅支持Android 5-11
  4. 当前仅支持thumb2/arm64指令集,arm32/x86/x86_64/mips/mips64还没有适配
  5. 在支持硬浮点的cpu架构,比如(armeabi-v7a, arm64-v8a)上,带有double/float参数的函数Hook可能有问题,没有充分测试
  6. 还有一些其他机型上可能存在的闪退
  7. 目前已测机型:5.0, 5.1, 6.0, 7.0, 7.1个别机型,以及这些机型的thumb2指令集,和6.0/7.1 的arm64指令集(作者博客所述)
  8. 无法修改类初始化过程中执行的函数
2.7 YAHFA

详情参考

YAHFA--ART环境下的Hook框架 - 记事本

在Android N上对Java方法做hook遇到的坑 - 记事本

工作原理

修改目标方法的ArtMethod结构体,将hook方法的ArtMethod地址保存在原方法的entry_point_from_jni,并修改原方法的entry_point_from_quick_compiled_code,使其指向一段辅助代码,在这里完成eax的设置和跳转,即时生效。

从原理上看,还是走的虚拟机调用流程,与AndFix的不同之处在于,AndFix直接替换了Method,而YAHFA使用一种简单方式实现了AOP,能够实现方法的hook并回调原始方法

限制

  1. 无法解决方法直接跳转,也就是不走ArtMethod入口,目标方法地址直接写死(其实这种情况在高版本里比较少了,特别是第三方应用的方法更少)
  2. 由于class加载后才进行的结构替换,因此field在class中的相对地址已经确定,无法进行字段的新增与删除
  3. 因为直接替换方法,此时已经执行了loadClass初始化,因此在该过程中执行的方法如构造函数、静态成员都有问题
2.8 FastHook

详情参考

[原创]FastHook——一种高效稳定、简洁易用的Android Hook框架-Android安全-看雪论坛-安全社区|安全招聘|bbs.pediy.com

[原创]FastHook——实现.dynsym段和.symtab段符号查询-Android安全-看雪论坛-安全社区|安全招聘|bbs.pediy.com

[原创]FastHook——远超YAHFA的优异稳定性-Android安全-看雪论坛-安全社区|安全招聘|bbs.pediy.com

[原创]FastHook——巧妙利用动态代理实现非侵入式AOP-Android安全-看雪论坛-安全社区|安全招聘|bbs.pediy.com

https://github.com/turing-technician/FastHook

工作原理

这个框架很优秀,其实还是native替换,fasthook支持内联替换和entrypoint两种方式,而且手动jit的过程中处理了很多细节,值得参考。

限制

还是native替换的通病,不能新增类/字段,以及类初始化过程中执行的函数

2.9 美团Robust

详情参考

Android热更新方案Robust - 美团技术团队

工作原理

Instant Run类型方案,基于编译过程进行插桩,有代码侵入,对运行效率,包体积,方法数有一定影响。

限制

插桩原始代码,因此会有代码冗余

3 总结

总结,具体对比我就略过了,技术参考链接已经很详细了。

总的来说,就是Java整体替换的方式可以新增类/字段,native内存替换只能修改方法体本身。

整体全面的热修复方案以Sophix为代表,不过它不开源。

native替换以FastHook为代表,不过有点黑科技,处理适配和兼容应该会花不少时间。

Robust插桩方案除开对代码有侵入性的话,从稳定性和兼容性来说,应该是最好的(但是这套方案没有详细了解)

有错误欢迎指正,也欢迎技术交流!

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/904628.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-15
下一篇 2022-05-15

发表评论

登录后才能评论

评论列表(0条)

保存