Android免Root无侵入AOP框架Dexposed
Dexposed框架是阿里巴巴无线事业部近期开源的一款在Android平台下的免Root无侵入运行期AOP框架,该框架基于AOP思想,支持经典的AOP使用场景,可应用于日志记录,性能统计,安全控制,事务处理,异常处理等方面。
针对Android平台,Dexposed只支持函数级别的在线热更新,如对已经发布在应用市场上的宿主APK,当我们从crash统计平台上发现某个函数调用有bug,导致经常性crash,这时,可以在本地开发一个补丁APK,并发布到服务器中,宿主APK下载这个补丁APK并使用Dexposed框架集成后,就可以很容易修复这个crash
如何在Android Studio集成Dexposed
关于在Android Studio中集成Dexposed,很简单,只需要导入两个jar包和两个so文件,jar包和so文件都可以在github上下载传送门,关于jar的导入大家肯定很清楚,只需要把对应的jar包放入libs目录下,然后添加项目依赖即可,而关于so文件的导入,不同的AS版本便不一样了,较早期的版本可能更麻烦些,而比较新的版本则非常方便,我用的是AS1.4Beta2版的,所以只需要在src/main目录下新建一个jniLibs目录,然后把so文件放入进去即可,当我们打包成apk的时候自到会将so文件添加进去。所以在Android Studio中集成Dexposed的配置如下:
1、添加so文件:
2、添加jar依赖:
dependencies {//...compile files('libs/dexposedbridge.jar')compile files('libs/patchloader.jar')
}
Dexposed支持的SDK版本
Dexposed目前只支持从Android2.3到4.4(除了3.0)的所有Dalvid运行时arm架构的设备,而Android 5.0以后摒弃了Dalvid而使用ART模式,所以说在api19以上的系统目前来说都不支持,不过阿里的团队也正在测试Android5.0加入对ART的支持。
—–支持的系统版本:
Dalvik 2.3
Dalvik 4.0~4.4
—–不支持的系统版本:
Dalvik 3.0
ART 5.1
ART M
ART 6.0
Dexposed应用场景
Dexposed实现的hooking,不仅可以hook应用中的自定义函数,也可以hook应用中调用的Android框架的函数。不过Dexposed只能hook函数和构造器,我们从源码中就可以看出:
应用场景:
1、 AOP编程
2、插桩(例如测试,性能监控等)
3、在线热更新,修复严重的,紧急的或者安全性的bug
4、SDK hooking以提供更好的开发体验
Dexposed框架是基于动态类加载技术,运行中的app可以加载一小段经过编译的Java AOP代码,在不需要重启app的前提下实现修改目标app的行为。
Dexposed的用法
1、检查当前系统是否支持Dexposed
首先上面我们讲了由于Dexposed框架目前并不是支持所有的系统,所以我们在使用Dexposed框架的时候应该对当前系统的版本进行检查是否支持Dexposed,不过这段检查代码Dexposed内部已经封装好了,所以我们只需要调用DexposedBridge.canDexposed(this)方法即可,我们应该尽可能的在程序一启动时候完成检查,如:
public class MyApplication extends Application {private static boolean canDexPosed = false;@Overridepublic void onCreate() {super.onCreate();canDexPosed = DexposedBridge.canDexposed(this);if(canDexPosed){//do something}}public static boolean isCanDexPosed() {return canDexPosed;}
}
我们可以看看Dexposed源码
里面是怎么完成对系统的检查的:
public static synchronized boolean canDexposed(Context context) {return !DeviceCheck.isDeviceSupport(context)?false:loadDexposedLib(context);}private static boolean loadDexposedLib(Context context) {try {if(VERSION.SDK_INT != 10 && VERSION.SDK_INT != 9) {if(VERSION.SDK_INT > 19) {System.loadLibrary("dexposed_l");} else {System.loadLibrary("dexposed");}} else {System.loadLibrary("dexposed2.3");}return true;} catch (Throwable var2) {return false;}}public static synchronized boolean isDeviceSupport(Context context) {boolean var2;try {if(!isCheckedDeviceSupport) {if(isDalvikMode() && isSupportSDKVersion() && !isX86CPU() && !isYunOS()) {isDeviceSupportable = true;return isDeviceSupportable;}isDeviceSupportable = false;return isDeviceSupportable;}var2 = isDeviceSupportable;} finally {Log.d("hotpatch", "device support is " + isDeviceSupportable + "checked" + isCheckedDeviceSupport);isCheckedDeviceSupport = true;}return var2;}
2、Dexposed基本功能
Dexposed框架有三个注入点可供选择:函数执行前注入(before),函数执行后注入(after),替换函数执行的代码段(replace)。
其中函数执行前注入和函数执行后注入用的是同一个抽象类:XC_MethodHook,而替换函数执行代码段的抽象类为:XC_MethodReplacement。
这三点注入点对应的抽象类中的方法名分别为:
1、XC_MethodHook抽象类中函数执行前注入和函数执行后注入的两个对应方法为:
protected void beforeHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable {}protected void afterHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable {}
2、XC_MethodReplacement抽象类中替换执行的代码段的方法为:
protected abstract Object replaceHookedMethod(MethodHookParam var1) throws Throwable;
所以我们只要实现上述中相应的方法,便可以在某个函数执行前、函数执行后或者在要替换的函数中执行相应的代码。那么怎么使用呢?就是通过DexposedBridge.findAndHookMethod(Class<?> clazz, String methodName, Object... parameterTypesAndCallback)
该方法的参数意思为:
1、clazz - 就是hook的方法所在的类的字节码对象
2、methodName - 就是hook的方法名
3、parameterTypesAndCallback - 可变参数,如果hook的方法中有参数的话,需要在此传入参数类型,如String则传入String.class,这和反射一样的,如果没有参数则不传。在最后还需要传入XC_MethodHook或者XC_MethodReplacement对象
如:
1、在getMsg方法执行前或者执行后做相应的操作:
DexposedBridge.findAndHookMethod(clazz, "getMsg", new XC_MethodHook() {@Overrideprotected void beforeHookedMethod(MethodHookParam param) throws Throwable {super.beforeHookedMethod(param);//do something}@Overrideprotected void afterHookedMethod(MethodHookParam param) throws Throwable {super.afterHookedMethod(param);//do something}});
2、替换getMsg方法中的代码:
DexposedBridge.findAndHookMethod(clazz, "getMsg", new XC_MethodReplacement() {@Overrideprotected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {//replace codereturn null;}});
我们可以看到在上面三个方法中,都会传入一个MethodHookParam
类型的参数,这个参数包含了一些很重要的信息:
- MethodHookParam.thisObject:被hook方法所在类的一个实例(相当于clazz类的一个实例)
- MethodHookParam.args:用于传递被hook方法的所有参数,它返回的是一个Object[]对象,通过它如果hook方法有参数传入的话,用这个可以获取到这些参数的值
- MethodHookParam.setResult:用于修改hook方法调用的结果,如果在beforeHookedMethod回调函数中调用setResult,可以阻止对hook方法的调用。但是如果有返回值的话仍然需要通过hook处理器进行return操作
Dexposed的应用示例
AOP编程
AOP(Aspect Oriented Programming)也就是面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,AOP编程最大的特点就是低耦合,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP一般应用在日志记录,性能统计,安全控制,事务处理,异常处理等方面,它的主要意图是将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
比如我们可以在应用的某些Activity中onCreate(Bundle)方法调用之前和之后做一些处理:
在MainActivity中onCreate方法调用之前预分配一些值或者调用一些方法:
public class MyApplication extends Application {private static boolean canDexPosed = false;@Overridepublic void onCreate() {super.onCreate();canDexPosed = DexposedBridge.canDexposed(this);if(canDexPosed){Class<?> clazz = null;try {clazz = getClassLoader().loadClass("com.sunzxyong.dexposeddemo.MainActivity");} catch (ClassNotFoundException e) {e.printStackTrace();}DexposedBridge.findAndHookMethod(clazz, "onCreate", Bundle.class, new XC_MethodHook() {@Overrideprotected void beforeHookedMethod(MethodHookParam param) throws Throwable {super.beforeHookedMethod(param);Log.v("zxy", "before onCreate ");//为Activity预先分配一些值
XposedHelpers.setObjectField(param.thisObject,"content","sunzxyong");}@Overrideprotected void afterHookedMethod(MethodHookParam param) throws Throwable {super.afterHookedMethod(param);Log.v("zxy", "after onCreate ");}});}}public static boolean isCanDexPosed() {return canDexPosed;}
}
然后在MainActivity:
public class MainActivity extends Activity {private String content;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Log.v("zxy", "in onCreate ");Log.v("zxy","content="+content);
}
运行后可以看到打印的Log为:
09-10 13:16:34.950 28373-28373/? V/zxy﹕ before onCreate
09-10 13:16:35.265 28373-28373/? V/zxy﹕ in onCreate
09-10 13:16:35.265 28373-28373/? V/zxy﹕ content=sunzxyong
09-10 13:16:35.265 28373-28373/? V/zxy﹕ after onCreate
可以看到预分配值的方法beforeHookedMethod()确实是在onCreate方法执行前执行,而onCreate方法执行后又执行了afterHookedMethod()方法。
其中XposedHelpers
这个帮助类提供了很多方法,比如调用某个类中的某个方法,设置变量的值等等。
当然也可以使用如下代码替换onCreate方法中的代码:
DexposedBridge.findAndHookMethod(clazz, "onCreate", Bundle.class, new XC_MethodReplacement() {@Overrideprotected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {//relpace codereturn null;}});
在线热更新
在线热更新一般用于修复已经上线app中严重的,紧急的或者安全性的bug,这里会涉及到两个apk文件,一个我们称为宿主apk,也就是已经上线的apk,一个称为补丁apk。宿主apk出现bug时,通过在线下载的方式从服务器下载补丁apk,然后使用补丁apk中的函数替换掉宿主apk的函数,从而实现在线修复bug的功能。
我们假设刚刚上线的apk中的MainActivity类中的getMsg(String str)方法出现了bug,然后我们采用在线热更新的方法去修复这个bug。
getMsg()方法:
public void getMsg(String str){new AlertDialog.Builder(this,AlertDialog.THEME_HOLO_LIGHT).setTitle("提示").setMessage(str).setPositiveButton("确定", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {dialog.dismiss();}}).create().show();}
1、首先我们需要一个补丁apk
我们创建一个叫PatchApk的Module作为补丁apk的工程,然后为它添加jar包依赖:
dependencies {provided fileTree(include: ['*.jar'], dir: 'libs')provided files('libs/patchloader.jar')provided files('libs/dexposedbridge.jar')
}
这里需要注意的是,由于补丁apk也添加了jar包依赖,而我们的宿主apk中也有jar包依赖,如果补丁apk中添加的jar包依赖是以compile
的方式添加的话,则会报重复包引用错误,错误如下:
Caused by: java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
所以我们在补丁apk中需要以provided
提供的方式添加jar包依赖。
然后我们创建一个PatchMsg
类,意思就是getMsg
方法的补丁类,此时我们需要将PatchMsg
补丁类实现IPatch
接口,然后实现该接口的handlePatch
方法,顾名思义,这个方法就是处理补丁的方法。然后通过DexposedBridge.findAndHookMethod()
方法来hook在MainActivity中的getMsg方法,然后实现replaceHookedMethod
方法进而替换掉宿主apk中getMsg()
中的代码。其中MainActivity的字节码对象可以由包名+类名反射得到。
整个PatchMsg类实现如下:
PatchMsg.java
public class MsgPatch implements IPatch {@Overridepublic void handlePatch(PatchParam patchParam) throws Throwable {Class<?> clazz = patchParam.context.getClassLoader().loadClass("com.sunzxyong.dexposeddemo.MainActivity");DexposedBridge.findAndHookMethod(clazz, "getMsg", String.class, new XC_MethodReplacement() {@Overrideprotected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {Activity activity = (Activity) methodHookParam.thisObject;new AlertDialog.Builder(activity, AlertDialog.THEME_HOLO_LIGHT).setTitle("提示").setMessage("这是补丁信息--->使用的是无侵入AOP插件Dexposed!").setPositiveButton("确定", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {dialog.dismiss();}}).create().show();String msg = (String) methodHookParam.args[0];//得到宿主apk中getMsg方法传递过来的参数值Log.v("zxy", msg);//Dexposed hello!//由于getMsg方法没有返回值则返回nullreturn null;}});}
}
好了,补丁类已经创建好了,此时我们可以把补丁apk中的无关类删了,比如MainActivity。
2、宿主apk加载补丁apk从而修复bug
我们假如宿主apk已经在线下下载好了补丁apk,并将补丁apk放在了cache目录中。
最后我们在宿主apk中通过调用PatchMain.load()
方法来加载补丁apk,代码如下:
public void click(View view){if(!MyApplication.isCanDexPosed()){Log.v("zxy","can not support DexPosed!");return;}String patchPath = this.getCacheDir().getPath()+ File.separator+"patch.apk";PatchResult result = PatchMain.load(this, patchPath, null);if(result.isSuccess()){new AlertDialog.Builder(this,AlertDialog.THEME_HOLO_LIGHT).setTitle("提示").setMessage("加载MsgPatch补丁成功!").setPositiveButton("确定", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {dialog.dismiss();}}).create().show();}}
这样,当加载补丁成功后,即用补丁中的代码替换了宿主apk中getMsg中的代码,从而修复了bug。
效果图:
这个demo用genymotion演示不了,只能用真机,然后360录屏太渣渣,录的有点不好,鼠标点击顺序依次是:展示信息->加载补丁->展示信息->退出->再次打开,展示信息。这个效果充分说明了在线热更新修复bug非常方便
Demo源码下载
参考资料:
- Dexposed:Android平台免Root无侵入AOP框架
- Dexposed源码
Android免Root无侵入AOP框架Dexposed相关推荐
- Android新技术学习——阿里巴巴免Root无侵入AOP框架Dexposed
阿里巴巴无线事业部近期开源的Android平台下的无侵入运行期AOP框架Dexposed,该框架基于AOP思想,支持经典的AOP使用场景.可应用于日志记录,性能统计,安全控制.事务处理.异常处理等方面 ...
- Dexposed:Android平台免Root无侵入AOP框架
本文来自阿里巴巴技术协会(ATA) 本文首发于 http://www.infoq.com/cn/news/2015/07/dexposed 近日,阿里巴巴无线事业部推出首个重量级Android开源项目 ...
- Android免Root环境下Hook框架Legend原理分析
0x1 应用场景 现如今,免Root环境下的逆向分析已经成为一种潮流! 在2015年之前的iOS软件逆向工程领域,要想对iOS平台上的软件进行逆向工程分析,越狱iOS设备与安装Cydia是必须的!几乎 ...
- 干货|Android免Root最全Hook插件(免Root Hook任意App)
支持276个API接口的Hook,覆盖Android系统级API和50个常见开源框架的Hook,包括对网络.算法.TextView.WebView.JSONObject等API接口的Hook支持,可以 ...
- Android免root查看数据库内容
Android免root查看数据库内容 用Android Studio 的一个小工具 1.首先需要Android Studio 3.0 还没有升级AS 3.0的小伙伴赶快升级啦. AS升级方式:菜单栏 ...
- 【android免root脚本制作】基于控件的操作——auto.js进阶
在[android免root脚本制作]自动坐标操作手机--京东金融程序金果摇钱树自动收金果 之后,想要做进一步优化,因为基于坐标点击如何屏幕被移动之类就会点击错位,不同手机还需去适配坐标,甚是麻烦,所 ...
- Anti-recall 防撤回神器 -- Android 免root查看撤回消息和闪照 官网
Anti-recall 防撤回神器官方下载 Android 免root查看撤回消息和闪照 官网 下载地址 anti-recall.com 哪里撤回点哪里 妈妈再也不用担心我错过劲爆消息了呢 爆照撤回没 ...
- android免root运行adb高级权限命令,例如修改手机设置等(转)
免Root实现静默安装和点击任意位置 0 前言 最近有了个需求:免 root 实现任意位置点击和静默安装.这个做过的小伙伴应该都知道正常情况下是不可能实现的.无障碍只能实现对已知控件的点击,并不能指定 ...
- Android逆向安全-无侵入找关键call之trace日志分析大法
标题 找关键call是逆向的基本技能和分析目标,找到关键call后便可以进一步利用.在安卓App的逆向分析中,人肉逆向分析虽说不难,但是繁琐,特别是现在App体积动辄几十MB甚至几百MB,反编译出的j ...
最新文章
- 怎么测并发 PHP,PHP接口并发测试的方法(推荐)
- linux内核__force,Linux内核学习:I2C_SLAVE_FORCE
- LuoguP4841 城市规划
- Jmeter——for循环控制器和if逻辑控制器
- 服务器不在工作_DHCP的工作原理
- delphi 用户可以点击格式修改进行模板修改
- windows Service 之调试过程
- 论文笔记_S2D.44_自监督的从稀疏到稠密:用激光雷达和单目摄像机进行自监督深度补全
- python最小特征值_阿里巴巴举荐,Python视频,免费分享,用python求解特征向量和拉普拉斯矩阵...
- Socket Tools的使用
- 直线插补 圆弧插补 步进电机二维直线插补圆弧插补控制算法 C语言 STM32移植
- 在英文邮件中,如何在开头对收件人进行称呼?
- 【Js】JavaScript数据类型隐式转换
- 从麻将到“农药”,细数 AI 攻占的游戏领域
- VCM 音圈电机 (马达)Voice Coil Motor
- 深度报道 第1个从太空发回的LoRa信号(含视频)
- 卡巴斯基实验室被独立研究机构评选为领导者
- 阿里云部署项目详细步骤
- DCDC的开关节点SW能打孔吗?
- 用docker快速搭建chevereto图床
热门文章
- matlab 排课表,TOMLAB课表编排问题
- 【转】CTP相关知识全攻略_Detective_ALong_新浪博客
- Javascript的对象再认识
- element-plus中自定义el-pagination中的文字
- 详细解读Maven中pom.xml
- 晁盖为何不传位给宋江
- ffmpeg——在Windows平台上面使用的一个坑
- 爆肝一个月,从博物馆到自然景区,整合了100个值得逛的3D游园景点,让你足不出户玩转中秋!
- python爬虫知乎代码_python爬虫知乎的简单代码实现
- 海尔破局之术:如何用2年时间拿下自清洁空调4成市场