Xposed模块编写的那些事
作者:admin | 时间:2016-9-26 00:42:40 | 分类:技术文章 隐藏侧边栏展开侧边栏
背景阐述
Android是一种基于Linux的自由及开放源代码的操作系统,由Google公司和开放手机联盟领导及开发。由于其开放的特质,吸引了一大批硬件厂商和软件开发者。第三方的统计数据显示,2016年Android占有的市场份额高达76.4%,远远超过其他智能手机厂商。
大量的Android os装机量,在丰富安卓系统使用场景的同时,也催生出了许多安全问题。xposed框架提供了不需要修改系统源码就能灵活定制系统功能的能力,极大的方便了安全研究人员的工作。且xposed利用了JNI机制修改Java framework的功能实现,没有涉及arm本地指令的适配工作,所以很少出现兼容性问题。本文是为了带领大家了解下此框架的能力以及实现方式,最终打造一款属于自己的“神器”。
本文总计5个章节,以xposed的使用需求作为切入点,由浅入深地介绍了模块的编写实战、模块编写进阶篇和常用模块编写以及异常情况&后续展望。本文来源于平时的实践,用作大家互相交流与学习。
xposed 使用需求
我们在选择使用xposed功能模块的时候,可能基于以下需求之一:
[1]监控app行为:查看关键api 的调用日志,用于特定目标的行为分析。
[2]定制系统功能:改变原先函数的处理逻辑,自定义api行为。
[3]沙箱功能定制:主要关注反环境检测(上述两点关注app本身),如:恶意样本分析,模拟器需要尽可能的“真实”以便触发样本行为。
当然,实际的需求并不会仅仅局限于此,可能会更多。这里列出的需求点也只是个人的总结,如有遗漏,敬请告知。毕竟需求驱动学习,文章的出发点也是希望聚集有着共同需求点的小伙伴,大家有个讨论地方,共同学习和进步。好,其它话不多说,接下来进入xposed模块的编写实战。
xposed 模块编写实战
xposed 模块的能力包括以下几个方面:
[1] 对普通函数或者构造函数有作用(针对具体实现类,不包括接口,抽象类的实现函数也可以hook)。
[2] 对目标函数进行 before、after 代码插桩,多用于操作(查看或修改)api的入参以及返回值。
[3] 目标函数替换,多用于功能变更、版本升级。
接下来,我们列举下 xposed 模块编写可能遇到的实际场景(假设阅读本文之前,读者拥有基本的模块编写经验)。在这个demo中,我将尽可能全面的再现需要hook操作的场景。比如:函数体、构造函数、匿名类、匿名内部类以及类的值域。
上述demo中存在
(1)静态field变量sMoney
(2)隐藏函数hidden_fun(触发的条件相对苛刻)
(3)内部类inner_class
(4)匿名内部类Animal animal = new Animal(){}
(5)构造函数demo()
我们逐个回顾下相应问题的解决方法:
(1)静态field变量sMoney的值的修改和获取,可以直接使用xposed提供的XposedHelpers类相关功能函数。具体操作可以类比以下示例代码片段:
/*
* Hook field
* class: com.example.inner_class_demo.demo
* field: sMoney
*/ Class clazz = XposedHelpers.findClass("com.example.inner_class_demo.demo",lpparam.classLoader);
XposedHelpers.setStaticObjectField(clazz,"sMoney",110);
Field sMoney = clazz.getDeclaredField("sMoney");
sMoney.setAccessible(true);
System.out.println(sMoney.get(null));
(2)主动调用隐藏函数hidden_fun(这一类函数是指触发条件比较苛刻的函数,但是我们又需要了解它的输入、输出的大致关系),需要通过clazz来新建实例,最后将此实例与函数名组装成XposedHelpers.callMethod() 的实参需求形式。具体操作可以类比以下示例代码片段:
/*
* Call hidden function
* class : com.example.inner_class_demo.demo
* function: hidden_fun()
*/
Class clazz = XposedHelpers.findClass("com.example.inner_class_demo.demo",lpparam.classLoader);
XposedHelpers.callMethod(constructor.newInstance(),"hidden_fun");
以上代码仅适用于存在无参构造函数的类,如果目标类没有无参构造函数,那就麻烦一点了,需要根据构造函数参数类型,反射寻找构造函数,接着才能类似上述操作。具体操作可以类比以下示例代码片段:
假设此时的构造函数仅有以下函数,即public demo(){}不存在的情形: public demo(String str){...} /*
* Call hidden function
* class : com.example.inner_class_demo.demo
* function: hidden_fun()
*/ Class clazz = XposedHelpers.findClass("com.example.inner_class_demo.demo",lpparam.classLoader);
Constructor constructor = clazz.getConstructor(String.class);
XposedHelpers.callMethod(constructor.newInstance("..."),"hidden_fun");
(3)内部类inner_class作为Android编程过程常见的一种编程方式,这里为了demo的全面,也将其列出。其实内部类整个处理过程与普通类极其相似,具体操作可以类比以下示例代码片段:
/*
* Hook the function of inner class
* class : com.example.inner_class_demo.demo$inner_class
* function: secret(String , boolean)
*/ XposedHelpers.findAndHookMethod("com.example.inner_class_demo.demo$inner_class", lpparam.classLoader, "secret", String.class, boolean.class, new XC_MethodHook() { protected void beforeHookedMethod(MethodHookParam param) throws Throwable { for (int i = 0; i < param.args.length; i++) {
XposedBridge.log(" argument is:" + param.args[i]);
} int field_result = (int) XposedHelpers.getObjectField(param.thisObject,"pMoney");
XposedBridge.log(String.valueOf(field_result));
}
});
需要注意的是,这里打印目标函数参数列表的时候,用了XposedBridge.log()。这样的输出方式对日志的长度有限制,即长度不超过1024。特殊场合(比如:文件读取时,需要查看文件的内容)需要注意处理下,不然会出现截断的现象。
(4)匿名内部类Animal animal = new Animal(){}的处理
同内部类,一般是class_name$1之类,具体可以反编译目标程序查看下。常见的反编译工具,比如:apktool、jeb、baksmali 均可以方便达到目的。
(5)构造函数demo()的处理,可以使用xposed提供的XposedHelpers类,具体操作可以类比以下示例代码片段:
/*
* Hook the constructor of class
* class : om.example.inner_class_demo.demo
* function: demo()
*/ Class clazz = XposedHelpers.findClass("com.example.inner_class_demo.demo",lpparam.classLoader);
XposedHelpers.findAndHookConstructor(clazz, new XC_MethodHook() {
...
});
需要注意的是,由于构造函数不同于普通函数,函数名不需要提供(因为与类名相同,xposed框架处理函数名问题)。
模块编写进阶篇
在实际的模块编写时候,我们都或多或少地遇到一些问题。接下来我将列出一些我在实践过程中遇到的一些问题和解决思路,期待帮助有同样困惑的小伙伴。如果你有问题,这里很不幸的又没有列出,那你可以拿出来大家一起讨论下。
一:同时监控多个构造函数、多个重载函数
通过上一小节模块的编写,我们现在已经可以顺利hook某一特定目标函数了。如果遇到某一类函数需要“批量”hook操作的时候,比如:需要同时监控多个构造函数、多个重载函数,我们此时不可能去挨个hook每个具体目标,那么应该怎么操作呢?我们可以这样来实现:
/*
* Hook all constructors of class
* class : om.example.inner_class_demo.demo
* function: demo()、demo(String)
*/ hookAllConstructors(clazz, new XC_MethodHook() {
...
}); /*
* Call all methods of class
* class : om.example.inner_class_demo.demo
* function: method()、method(String)
*/ hookAllMethods(clazz, new XC_MethodHook() {
...
});
我们可以总结一个规律:hook重载函数时候,只需要忽略参数的具体类型即可。这种方式其实可以达到两种效果:1. 高效的处理函数重载问题 2.目标函数参数类型太复杂,自定义的类型太多。忽略参数类型,可以简化我们的hook工作。
二:目标app功能丰富,用到multidex加载技术,我们又该怎么办?
由于dalvik环境下xposed对multidex的支持没有很好的通用解决方案,寻找目标函数会发生ClassNotFoundError,所以处理multidex需要一些技巧(Tips): 此问题因为classloader出错引起的,所以要寻找attachBaseContext 的classloader,而非lpparam.classLoader(此思路来自非虫前辈)。
下图即为xposed作者对不支持multidex的解释,详细的内容可以去github上查看对应的issue。
常用模块编写
在这一章节,我将列出一些常见的功能模块。希望发散下大家的思路、节约模块开发的时间成本(毕竟重复劳动会消耗些时间、精力,积少成多嘛)。
第一部分
Hook org.apache.http 包中的网络请求,忽略参数然后使用hookAllMethods就可以同时拦截HttpPost、HttpGet、HttpUriRequest类型的网络请求参数。
/*
* Hook net access
* abstract class: org.apache.http.impl.client.AbstractHttpClient
* function : execute(HttpHost target, HttpRequest request,HttpContext context)
* :execute(HttpUriRequest request, HttpContext context)
*/ hookAllMethods("org.apache.http.impl.client.AbstractHttpClient", lpparam.classLoader, "execute", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
print_args(param);
}
});
需要注意点有二:
1. 这里就是0×02章节里面第[1]点提到的,xposed针对抽象类中的具体实现函数的hook。
2. 这里参数特殊,如果直接强转成String类型然后输出,将会得到无意义的输出,形如:org.apache.http.client.methods.HttpPost@41d45200。所以输出之前可以判断下,具体操作可以类比以下示例代码片段:
Object arg = param.args[i]; String argValue = "null"; if(arg instanceof HttpPost){
URI uri = ((HttpPost)arg).getURI();
argValue = String.format("uri=%s ", uri.toString());
}else if(arg instanceof HttpGet){
URI uri = ((HttpGet)arg).getURI();
argValue = uri.toString();
}else if(arg instanceof HttpUriRequest){
URI uri = ((HttpUriRequest)arg).getURI();
argValue = uri.toString();
}else argValue = arg.toString();
这里需要注意的是,HttpPost 之类的包在高版本的sdk中已经不存在了,顺利通过编译需要进行以下操作:1. Android studio中修改编译文件,添加
android { useLibrary 'org.apache.http.legacy' }
3. ADT中可以按照以下教程,The import org.apache.http.HttpResponce cannot resolved error solution
第二部分
网络重定向,修改网络请求地址,“模拟“”中间人攻击效果,具体操作可以类比以下示例代码片段:
/*
* Redirect net access
* class: java.net.URL
*
*/ XposedHelpers.findAndHookConstructor("java.net.URL", lpparam.classLoader, String.class, new XC_MethodHook() {
@Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
String url = (String) param.args[0];
param.args[0] = "http://www.baidu.com/";
XposedBridge.log("new URL to " + param.args[0]);
}
});
[3] IO异常监控,这里的IO异常包括所有网络IO异常和本地异常,具体操作可以类比以下示例代码片段:
/*
* Monitor IO Exception
* class: java.io.IOException
*
*/ XposedBridge.hookAllConstructors(IOException.class, new XC_MethodHook() {
@Override protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log((Throwable) param.thisObject);
}
});
异常情况和后续展望
常用的hook操作总结如下,Object…代表着变参,实际编程过程中,需要保证提供的参数列表中最后一个参数一定是hook操作的回调,前面是函数参数class类型。
/*
* Hook any method (or constructor) with the specified callback
*
* @param targetclass The method in which
* @param hookMethod The method to be hooked.
* @param callback The callback to be executed when the hooked method is called.
* @return An object that can be used to remove the hook.
*/ XposedHelpers#findAndHookMethod(String, ClassLoader, String, Object...) XposedHelpers#findAndHookMethod(Class, String, Object...) XposedBridge#hookAllMethods XposedHelpers#findAndHookConstructor(String, ClassLoader, Object...) XposedHelpers#findAndHookConstructor(Class, Object...) XposedBridge#hookAllConstructors
模块编程过程中,如果不希望直接提供变参列表,可以提供Object数组,这样可以保证上述接口的“稳定”。具体操作可以类比以下示例代码片段:
Object[] args_obj = new Object[2] ;
XC_MethodHook callback_fun = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("...");
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("...);
}
};
args_obj[0] = String.class;
args_obj[1] = callback_fun;
findAndHookMethod(class_name, lpparam.classLoader, function_name,args_obj);
以上demo为debug版本,没有混淆。在实践过程中,可能遇到混淆甚至加固的产品。更有甚者,目标app即使没有混淆、加固,但是我们还是不能很快定位目标函数,难道此时只能大海捞针般静态寻找target?这时候需要通过一些辅助工具帮助我们定位api位置。
此文章可能考虑续篇,内容根据上述异常情况或者我能想到的新的出发点与思路。比如:反模拟器检测、反调试和加密库操作监控,每一点都是一个小的工程,需要考虑周全些才能有实用价值,大家一起努力。
最后,感谢非虫对文章难点提供的解决思路,以及最终文章质量的把关。
本文原创作者:L5,