zoukankan      html  css  js  c++  java
  • Xposed模块编写

    背景阐述

    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操作的场景。比如:函数体、构造函数、匿名类、匿名内部类以及类的值域。

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

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

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

  • 相关阅读:
    leetcode 141 环形链表
    [转载]Tensorflow中reduction_indices 的用法
    SIFT特征原理与理解
    numpy切片和布尔型索引
    IPython的使用
    [文献阅读]基于卷积神经网络的高光谱图像深度特征提取与分类
    验证码校验
    防止表单重复提交
    MyBatis 一对一,一对多,多对多
    MySQL基础内容
  • 原文地址:https://www.cnblogs.com/nongchaoer/p/12458789.html
Copyright © 2011-2022 走看看