首页
社区
课程
招聘
[原创]使用frida来hook加固的Android应用的java层
2018-9-11 00:26 32543

[原创]使用frida来hook加固的Android应用的java层

2018-9-11 00:26
32543

获取完整的demo

完整的代码已经上传github,https://github.com/smartdone/Frida-Scripts/tree/master/shell,如果喜欢请star哦

需求

在对一些加固的Android应用做测试的时候,脱壳二次打包是一个相当相当复杂的工作。所以一般是脱壳分析代码,然后用hook的方式来动态劫持代码。使用xposed来hook加固的应用大家可能已经很熟悉了,但是使用frida大概没有多少人尝试,今天就给大家分享下如何使用xposed来hook加固之后的Android应用。

基本原理

要hook加固的应用分为三步,第一步是拿到加载应用本事dex的classloader;第二步是通过这个classloader去找到被加固的类;第三步是通过这个类去hook需要hook的方法

 

得到第一步的classloader之后的hook操作和hook未加固的应用基本类似。

如何获取classloader

我们看Android的android.app.Application的源码http://androidxref.com/7.1.2_r36/xref/frameworks/base/core/java/android/app/Application.java#188可以发现,自己定义的ApplicationattachBaseContext方法是在Applicationattach方法里面被调用的。而基本上所有的壳都是在attachBaseContext里面完成的代码解密并且内存加载dex,在attachBaseContext执行完之后就可以去拿classloader,此时的classloader就已经是加载过加固dex的classloader了。

开始hook加固应用

i春秋app为例,此应用使用的360加固,我们的目标是hook他的flytv.run.monitor.fragment.user.AyWelcomeonCreate方法,然后弹出一个Toast

直接使用Java.use

我们直接使用Java.use来获取这个Activity,代码如下:

if(Java.available) {
    Java.perform(function(){
        var AyWelcome = Java.use("flytv.run.monitor.fragment.user.AyWelcome");
        if(AyWelcome != undefined) {
            console.log("AyWelcome: " + AyWelcome.toString());
        } else {
            console.log("AyWelcome: undefined");
        }
    });
}

使用如下命令来注入这个js:

frida -R -f com.ni.ichunqiu -l hook_java.js

运行之后会报如下的错误:

Spawned `com.ni.ichunqiu`. Use %resume to let the main thread start executing!
[Remote::com.ni.ichunqiu]-> %resume
[Remote::com.ni.ichunqiu]-> Error: java.lang.ClassNotFoundException: Didn't find class "flytv.run.monitor.fragment.user.AyWelcome" on path: DexPathList[[zip file "/data/app/com.ni.ichunqiu-1/base.apk"],nativeLibraryDirectories=[/data/app/com.ni.ichunqiu-1/lib/arm, /system/fake-libs, /data/app/com.ni.ichunqiu-1/base.apk!/lib/armeabi-v7a, /system/lib, /vendor/lib]]
    at frida/node_modules/frida-java/lib/env.js:222
    at ensureClass (frida/node_modules/frida-java/lib/class-factory.js:777)
    at frida/node_modules/frida-java/lib/class-factory.js:164
    at [anon] (repl1.js:3)
    at frida/node_modules/frida-java/lib/vm.js:39
    at v (frida/node_modules/frida-java/index.js:338)
    at frida/node_modules/frida-java/index.js:319
    at input:1
[Remote::com.ni.ichunqiu]->

也就是找不到这个类,也就是我们现在这个默认的classloader找不到flytv.run.monitor.fragment.user.AyWelcome这个类。

获取classloader

代码如下:

if(Java.available) {
    Java.perform(function(){
        var application = Java.use("android.app.Application");
        application.attach.overload('android.content.Context').implementation = function(context) {
            var result = this.attach(context); // 先执行原来的attach方法
            var classloader = context.getClassLoader();

            return result;
        }

    });
}

现在我们在attach方法执行之后拿到了Context,并且通过context获取了classloader,我们看现在的classloader是否加载了被加固的dex。我们使用classloader的loadClass方法去加载flytv.run.monitor.fragment.user.AyWelcome这个类,看是否成功:

if(Java.available) {
    Java.perform(function(){
        var application = Java.use("android.app.Application");
        var reflectClass = Java.use("java.lang.Class");

        application.attach.overload('android.content.Context').implementation = function(context) {
            var result = this.attach(context); // 先执行原来的attach方法
            var classloader = context.getClassLoader(); // 获取classloader
            var AyWelcome = classloader.loadClass("flytv.run.monitor.fragment.user.AyWelcome"); // 使用classloader加载类
            AyWelcome = Java.cast(AyWelcome, reflectClass); // 因为loadClass得到的是一个Object对象,我们需要把它强制转换成Class
            console.log("AyWelcome class name: " + AyWelcome.getName());
            return result;
        }

    });
}

注入这个js,可以正确的打印出flytv.run.monitor.fragment.user.AyWelcome类名,说明我们拿到这个这个classloader是加载了加固过的dex的。

转换成Java.use获取到的js对象

在上一步我们虽然可以通过frida来获取到加固之后的class,但是你如果直接使用这个{class}.{fuction}依然会失败,因为class没有这个成员变量,所以我们需要来实现获取到与Java.use一样的js对象,那么如何解决呢?当然是read the fuking source code

 

我们看frida-java的use方法的实现,代码在https://github.com/frida/frida-java/blob/9becc27091576fc198dc2a719c0fedb30a270b28/lib/class-factory.js#L139代码如下:

this.use = function (className) {
    let C = classes[className];
    if (!C) {
      const env = vm.getEnv();
      if (loader !== null) {
        const usedLoader = loader;

        if (cachedLoaderMethod === null) {
          cachedLoaderInvoke = env.vaMethod('pointer', ['pointer']);
          cachedLoaderMethod = loader.loadClass.overload('java.lang.String').handle;
        }

        const getClassHandle = function (env) {
          const classNameValue = env.newStringUtf(className);
          const tid = Process.getCurrentThreadId();
          ignore(tid);
          try {
            return cachedLoaderInvoke(env.handle, usedLoader.$handle, cachedLoaderMethod, classNameValue);
          } finally {
            unignore(tid);
            env.deleteLocalRef(classNameValue);
          }
        };

        C = ensureClass(getClassHandle, className);
      } else {
        const canonicalClassName = className.replace(/\./g, '/');

        const getClassHandle = function (env) {
          const tid = Process.getCurrentThreadId();
          ignore(tid);
          try {
            return env.findClass(canonicalClassName);
          } finally {
            unignore(tid);
          }
        };

        C = ensureClass(getClassHandle, className);
      }
    }

    return new C(null);
  };

从代码中我们可以看出来,他会先到他存class的一个列表里面去找,如果找不到,就会判断loader是不是null,loader不为null,就会使用loader加载class,loader为null就会使用JNIEnv的findClass方法去找类,也就是使用默认的classloader。所以现在目标明确了,我们只需要让这个loader是我们从Applicaiton的attach方法获取到的classloader即可,那么怎么替换呢?

 

很显然直接Java.loader会说undefined,我们看最终导出的是index.js这个脚本https://github.com/frida/frida-java/blob/022bc7d95c00d627091d4edc0ff87b67de5a9739/index.js#L22,有下面几个成员变量:

  let initialized = false;
  let api = null;
  let apiError = null;
  let vm = null;
  let classFactory = null;
  let pending = [];
  let threadsInPerform = 0;
  let cachedIsAppProcess = null;

我们看到了,这个classFactory不就是我们刚刚上面看到的那个loader所在的地方吗,那么要引用这个loader就很简单了,直接Java.classFactory.loader就可以引用了,你可以使用console.log("classloader: " + Java.classFactory.loader);来获取这个loader的值,后面我们直接将这个值替换为我们获取的classloader就行了,代码如下:

if(Java.available) {
    Java.perform(function(){
        var application = Java.use("android.app.Application");
        var reflectClass = Java.use("java.lang.Class");

        console.log("application: " + application);

        application.attach.overload('android.content.Context').implementation = function(context) {
            var result = this.attach(context); // 先执行原来的attach方法
            var classloader = context.getClassLoader(); // 获取classloader
            Java.classFactory.loader = classloader;
            var AyWelcome = Java.classFactory.use("flytv.run.monitor.fragment.user.AyWelcome"); //这里能直接使用Java.use,因为java.use会检查在不在perform里面,不在就会失败
            console.log("AyWelcome: " + AyWelcome);

            return result;
        }

    });
}

写hook加固的类的代码,弹出toast

if(Java.available) {
    Java.perform(function(){
        var application = Java.use("android.app.Application");
        var Toast = Java.use('android.widget.Toast');

        application.attach.overload('android.content.Context').implementation = function(context) {
            var result = this.attach(context); // 先执行原来的attach方法
            var classloader = context.getClassLoader(); // 获取classloader
            Java.classFactory.loader = classloader;
            var AyWelcome = Java.classFactory.use("flytv.run.monitor.fragment.user.AyWelcome"); //这里不能直接使用Java.use,因为java.use会检查在不在perform里面,不在就会失败
            console.log("AyWelcome: " + AyWelcome);
            // 然后下面的代码就和写正常的hook一样啦
            AyWelcome.onCreate.overload('android.os.Bundle').implementation = function(bundle) {
                var ret = this.onCreate(bundle);
                Toast.makeText(context, "onCreate called", 1).show(); //弹出Toast
                return ret;
            }
            return result;
        }
    });
}

最后效果如下:

 

a.png


[培训]《安卓高级研修班(网课)》月薪三万计划

最后于 2019-5-30 10:34 被smartdon编辑 ,原因:
收藏
点赞8
打赏
分享
打赏 + 2.00雪花
打赏次数 1 雪花 + 2.00
 
赞赏  junkboy   +2.00 2018/09/11
最新回复 (32)
雪    币: 3
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
尘封万丈 2018-9-11 01:11
2
0
支持楼主。虽然看不懂
雪    币: 3543
活跃值: (708)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
kiyaa 2018-9-11 10:27
3
0
哈哈, 很棒! 
雪    币: 36
活跃值: (921)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
芃杉 2018-9-11 11:46
4
0
mark
雪    币: 3539
活跃值: (901)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
smartdon 1 2018-9-11 12:31
5
0
kiyaa 哈哈, 很棒!
感谢道友支持
雪    币: 54
活跃值: (570)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
chmlqw 2018-9-11 15:11
6
0
路过,感觉很nb
雪    币: 215
活跃值: (372)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
magicxss 2018-9-11 15:17
7
0
666666
雪    币: 909
活跃值: (831)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
La0s 2018-9-11 18:50
8
0
我记得frida一般情况下是可以直接hook加固应用的吧,而且我这边测试也通过了(APP官网下的3.7.0.67版本),不过还是很感谢楼主的分享!

雪    币: 3539
活跃值: (901)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
smartdon 1 2018-9-11 20:04
9
0
wx_La0s 我记得frida一般情况下是可以直接hook加固应用的吧,而且我这边测试也通过了(APP官网下的3.7.0.67版本),不过还是很感谢楼主的分享!
难道就我的frida hook不到,我直接java.use的时候回class not found
雪    币: 237
活跃值: (83)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
王小东 2018-9-11 20:46
10
0
难道就我的frida hook不到
雪    币: 909
活跃值: (831)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
La0s 2018-9-11 21:17
11
0
smartdon 难道就我的frida hook不到,我直接java.use的时候回class not found
我用frida测试的时候只有一种情况会报ClassNotFoundException on path: DexPathList... 就是hook动态加载的dex时候(跟壳没有关系),当然这个题目应该不是
最后于 2018-9-11 21:19 被La0s编辑 ,原因:
雪    币: 6818
活跃值: (153)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
聖blue 2018-9-11 23:53
12
0
雪    币: 41
活跃值: (78)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
simonkoh 2018-9-13 12:59
13
0
呃...
雪    币: 160
活跃值: (151)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
小青峰 2018-9-13 14:22
14
0
666,非常好,感谢楼主分享
雪    币: 182
活跃值: (475)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
glider菜鸟 1 2018-9-14 14:22
15
0
谢谢楼主分享,hook不到可能是因为在壳动态加载之前hook的? 按理讲是可以直接hook加固应用的,因为java.use使用的classloader就是加载应用的classloader,frida是通过android.app.ActivityThread.currentApplication().getClassLoader()获取的,和你这个手工获取的classloader应该同一个。
雪    币: 107
活跃值: (311)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Fido 2018-9-14 18:11
16
0
good job.
雪    币: 2
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wxfishso 2018-9-15 14:45
17
0
楼主从源码分析的过程很赞。之前测试frida、xposed都可以hook加固后的应用。“java.use的时候回class not found”应该是壳代码没执行完吧。你运行app等一会,再执行frida试试
雪    币: 122
活跃值: (1305)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
koflfy 1 2018-9-16 15:24
18
0
mark
雪    币: 3539
活跃值: (901)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
smartdon 1 2018-9-17 16:59
19
0
glider菜鸟 谢谢楼主分享,hook不到可能是因为在壳动态加载之前hook的? 按理讲是可以直接hook加固应用的,因为java.use使用的classloader就是加载应用的classloader,frida是 ...
应该就是这个原因了
雪    币: 13270
活跃值: (4588)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tDasm 2018-11-14 11:23
20
0
smartdon 应该就是这个原因了
lz开始转向frida?Xposed使用动态获取类一样可以hook
雪    币: 51
活跃值: (27)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
leonrain 2019-1-5 20:05
21
0
好文
雪    币: 109
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_kmjaeuwz 2019-4-28 20:56
22
0
您好,我想问一下,实例化一个类之后,怎么去修改他全局变量的值呢?比如一个 boolean类型的全局变量,去赋值成 true.
最后于 2019-5-5 15:06 被mb_kmjaeuwz编辑 ,原因:
雪    币: 1654
活跃值: (3947)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
小黄鸭爱学习 2019-4-30 17:16
23
0
Xposed hook全部方法 导致 OOM,楼主怎么解决。
雪    币: 2359
活跃值: (278)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
coolsnake 2019-5-1 17:17
24
0
什么是OOM?
雪    币: 632
活跃值: (1503)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
Monkeylord 2019-5-6 18:47
25
0
小黄鸭爱学习 Xposed hook全部方法 导致 OOM,楼主怎么解决。
复用
游客
登录 | 注册 方可回帖
返回