Xposed模块编写的那些事

*本文原创做者:L5,本文属FreeBuf原创奖励计划,未经许可禁止转载php

背景阐述

Android是一种基于Linux的自由及开放源代码的操做系统,由Google公司和开放手机联盟领导及开发。因为其开放的特质,吸引了一大批硬件厂商和软件开发者。第三方的统计数据显示,2016年Android占有的市场份额高达76.4%,远远超过其余智能手机厂商。java

大量的Android os装机量,在丰富安卓系统使用场景的同时,也催生出了许多安全问题。xposed框架提供了不须要修改系统源码就能灵活定制系统功能的能力,极大的方便了安全研究人员的工做。且xposed利用了JNI机制修改Java framework的功能实现,没有涉及arm本地指令的适配工做,因此不多出现兼容性问题。本文是为了带领你们了解下此框架的能力以及实现方式,最终打造一款属于本身的“神器”。android

本文总计5个章节,以xposed的使用需求做为切入点,由浅入深地介绍了模块的编写实战、模块编写进阶篇和经常使用模块编写以及异常状况&后续展望。本文来源于平时的实践,用做你们互相交流与学习。git

xposed 使用需求

咱们在选择使用xposed功能模块的时候,可能基于如下需求之一:github

[1]监控app行为:查看关键api 的调用日志,用于特定目标的行为分析。apache

[2]定制系统功能:改变原先函数的处理逻辑,自定义api行为。编程

[3]沙箱功能定制:主要关注反环境检测(上述两点关注app自己),如:恶意样本分析,模拟器须要尽量的“真实”以便触发样本行为。api

固然,实际的需求并不会仅仅局限于此,可能会更多。这里列出的需求点也只是我的的总结,若有遗漏,敬请告知。毕竟需求驱动学习,文章的出发点也是但愿汇集有着共同需求点的小伙伴,你们有个讨论地方,共同窗习和进步。好,其它话很少说,接下来进入xposed模块的编写实战。数组

xposed 模块编写实战

xposed 模块的能力包括如下几个方面:安全

[1] 对普通函数或者构造函数有做用(针对具体实现类,不包括接口,抽象类的实现函数也能够hook)。

[2] 对目标函数进行 before、after 代码插桩,多用于操做(查看或修改)api的入参以及返回值。

[3] 目标函数替换,多用于功能变动、版本升级。

接下来,咱们列举下 xposed 模块编写可能遇到的实际场景(假设阅读本文以前,读者拥有基本的模块编写经验)。在这个demo中,我将尽量全面的再现须要hook操做的场景。好比:函数体、构造函数、匿名类、匿名内部类以及类的值域。

1.png

上述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。

图片5.png

经常使用模块编写

在这一章节,我将列出一些常见的功能模块。但愿发散下你们的思路、节约模块开发的时间成本(毕竟重复劳动会消耗些时间、精力,聚沙成塔嘛)。

第一部分

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位置。

此文章可能考虑续篇,内容根据上述异常状况或者我能想到的新的出发点与思路。好比:反模拟器检测、反调试和加密库操做监控,每一点都是一个小的工程,须要考虑周全些才能有实用价值,你们一块儿努力。

最后,感谢非虫对文章难点提供的解决思路,以及最终文章质量的把关。

致谢非虫(非虫看雪ID

相关文章
相关标签/搜索