首页
论坛
课程
招聘
[基础理论] [逆向分析] [脱壳反混淆] [程序开发] [系统相关] [源码分析] [工具脚本] [其他] [原创]实现frida版的JustTrustMe(三)frida版JustTrustMe升级和混淆对抗
2021-5-7 21:30 5266

[基础理论] [逆向分析] [脱壳反混淆] [程序开发] [系统相关] [源码分析] [工具脚本] [其他] [原创]实现frida版的JustTrustMe(三)frida版JustTrustMe升级和混淆对抗

2021-5-7 21:30
5266

前言

上一篇《实现frida版的JustTrustMe(二)用frida实现JustTrustMe》,我们已经使用frida实现了JustTrustMe所有的hook点。但是由于不知明的原因JustTrustMe项目不更新和apk混淆常态化,使JustTrustMe有效hook点位越来越少。本篇,我们将在上篇just_trust_me.js的基础上来做升级,使它比官方的JustTrustMe更强。哦!不!是比现在所有的翻版JustTrustMe更强。

新增hook点位

我找到3处新的hook点位,实现如下

1. com.android.org.conscrypt.Platform

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function processConscryptPlatform() {
    if (!classExists("com.android.org.conscrypt.Platform")) {
        return;
    }
    var com_android_org_conscrypt_Platform_clz = Java.use('com.android.org.conscrypt.Platform');
    var com_android_org_conscrypt_Platform_clz_method_checkServerTrusted_9565 = com_android_org_conscrypt_Platform_clz.checkServerTrusted.overload('javax.net.ssl.X509TrustManager', '[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'com.android.org.conscrypt.OpenSSLEngineImpl');
    com_android_org_conscrypt_Platform_clz_method_checkServerTrusted_9565.implementation = function(v0, v1, v2, v3) {
        //什么都不做
    };
    var com_android_org_conscrypt_Platform_clz_method_checkServerTrusted_6928 = com_android_org_conscrypt_Platform_clz.checkServerTrusted.overload('javax.net.ssl.X509TrustManager', '[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'com.android.org.conscrypt.OpenSSLSocketImpl');
    com_android_org_conscrypt_Platform_clz_method_checkServerTrusted_6928.implementation = function(v0, v1, v2, v3) {
        //什么都不做
    };
}

2. appcelerator.https.PinningTrustManager

1
2
3
4
5
6
7
8
9
10
function processPinningTrustManager() {
    if (!classExists("appcelerator.https.PinningTrustManager")) {
        return;
    }
    var pinningTrustManagerClass = Java.use('appcelerator.https.PinningTrustManager');
    var pinningTrustManagerClass_checkServerTrusted = pinningTrustManagerClass.checkServerTrusted.overload();
    pinningTrustManagerClass_checkServerTrusted.implementation = function() {
        //什么都不做
    };
}

3. okhttp3.OkHttpClient$Builder

在JustTrustMe官方源码中没有这个hook点,我觉得还有有必要加一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if (classExists("okhttp3.OkHttpClient$Builder")) {
        try{
            var okhttp3_OkHttpClient_Builder_clz = Java.use('okhttp3.OkHttpClient$Builder');
            var okhttp3_OkHttpClient_Builder_clz_sslSocketFactory_one = okhttp3_OkHttpClient_Builder_clz.sslSocketFactory.overload('javax.net.ssl.SSLSocketFactory');
            okhttp3_OkHttpClient_Builder_clz_sslSocketFactory_one.implementation = function(sSLSocketFactory) {
                //把参数替换成EmptySSLFactory
                var ret = okhttp3_OkHttpClient_Builder_clz_sslSocketFactory_one.call(this, Java.use("gz.justtrustme.Helper").getEmptySSLFactory());
                return ret;
            };
            var okhttp3_OkHttpClient_Builder_clz_sslSocketFactory_two = okhttp3_OkHttpClient_Builder_clz.sslSocketFactory.overload('javax.net.ssl.SSLSocketFactory', 'javax.net.ssl.X509TrustManager');
            okhttp3_OkHttpClient_Builder_clz_sslSocketFactory_two.implementation = function(sSLSocketFactory, x509TrustManager) {
                //把参数替换成EmptySSLFactory
                var ret = okhttp3_OkHttpClient_Builder_clz_sslSocketFactory_two.call(this, Java.use("gz.justtrustme.Helper").getEmptySSLFactory(), x509TrustManager);
                return ret;
            };
        } catch(error) {
            console.error("okhttp3.OkHttpClient$Builder的sslSocketFactory方法可能被混淆了。你可以jadx反编译下还原回来!");
        }
    }else{
        console.error("没找到okhttp3.OkHttpClient$Builder类,可能被混淆了。你可以jadx反编译下还原回来!");
    }

just_trust_me.js调用如下" class="anchor" href="#在just_trust_me.js调用如下">just_trust_me.js调用如下

1
2
3
4
5
6
processOkHttp();
processXutils();
processHttpClientAndroidLib();
//hooker添加的hook点
processConscryptPlatform();
processPinningTrustManager();

对抗混淆

对抗混淆我们主要针对okhttp进行对抗,因为okhttp真的太火爆了。现在的商业app几乎没有不用ok库做网络请求的,当然QUIC、MMTLS这些超级大厂app的私有协议除外。对付非超一线app,我们把okhttp的混淆对抗做好就足够!!!

 

okhttp一共有3个混淆类需要我们定位。okhttp3.CertificatePinner、okhttp3.internal.tls.OkHostnameVerifier、okhttp3.OkHttpClient$Builder。这三个混淆类我们可以通过反射遍历内部方法特征和类的继承接口关系确定哪个时混淆类。而不管是okhttp3还是okhttp4这些类内部方法特征和继承关系不会变化太多。而且我们检测混淆的时候也不会去用类名,所以基本上可以达到通用的效果。

如何实现混淆检测

okhttp3.OkHttpClient.Builder为例,首先是okhttp3.OkHttpClient.Builder类的特征分析,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//public static final 内部类 且无继承、无任何接口实现
public static final class Builder {
        //有javax.net.ssl.HostnameVerifier属性
        HostnameVerifier hostnameVerifier;
 
        //有java.net.Proxy属性
        Proxy proxy;
 
        //有java.net.ProxySelector属性
        ProxySelector proxySelector;
 
        //javax.net.SocketFactory
        SocketFactory socketFactory;
 
        //javax.net.ssl.SSLSocketFactory
        SSLSocketFactory sslSocketFactory;
 
        //有一个参数为SSLSocketFactory的方法返回值是类自己
        public Builder sslSocketFactory(SSLSocketFactory sSLSocketFactory);
 
        //有一个参数为SSLSocketFactory、X509TrustManager的方法返回值是类自己
        public Builder sslSocketFactory(SSLSocketFactory sSLSocketFactory, X509TrustManager x509TrustManager);
}

java检测是否为okhttp3.OkHttpClient.Builder混淆类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// public static final class Builder
    public static Okhttp3FakeClass okHttpClientBuilderCheck(Class clz) {
        // java.net.Proxy proxy;
        boolean hasProxyField = false;
 
        // java.net.ProxySelector proxySelector;
        boolean hasProxySelectorField = false;
 
        // javax.net.SocketFactory socketFactory;
        boolean hasSocketFactoryField = false;
 
        // javax.net.ssl.SSLSocketFactory sslSocketFactory;
        boolean hasSSLSocketFactoryField = false;
 
        // javax.net.ssl.HostnameVerifier
        boolean hasHostnameVerifierField = false;
 
        // public Builder sslSocketFactory(javax.net.ssl.SSLSocketFactory
        // sSLSocketFactory)
        boolean hasSslSocketFactoryMethod = false;
        String sslSocketFactoryMethodName = null;
 
        // public Builder sslSocketFactory(javax.net.ssl.SSLSocketFactory
        // sSLSocketFactory, javax.net.ssl.X509TrustManager x509TrustManager)
        boolean hasSslSocketFactory2Method = false;
        String sslSocketFactoryMethod2Name = null;
 
        try {
            String className = clz.getName();
            if (clz.isInterface() || clz.isArray() || clz.isAnnotation() || clz.isEnum()
                    || className.startsWith("java.") || className.startsWith("android") || className.startsWith("com")
                    || !className.contains("$")) {
                // XLog.appendText("okHttpClientBuilderCheck 111");
                return null;
            }
            // 校验 public static final class Builder 类声明
            if (!Modifier.isFinal(clz.getModifiers()) || !Modifier.isPublic(clz.getModifiers())
                    || !Modifier.isStatic(clz.getModifiers())) {
                // XLog.appendText("okHttpClientBuilderCheck 222");
                return null;
            }
            Field[] fields = clz.getDeclaredFields();
            for (Field field : fields) {
                String fieldClassName = field.getType().getName();
                if ("java.net.Proxy".equals(fieldClassName)) {
                    hasProxyField = true;
                } else if ("java.net.ProxySelector".equals(fieldClassName)) {
                    hasProxySelectorField = true;
                } else if ("javax.net.SocketFactory".equals(fieldClassName)) {
                    hasSocketFactoryField = true;
                } else if ("javax.net.ssl.SSLSocketFactory".equals(fieldClassName)) {
                    hasSSLSocketFactoryField = true;
                } else if ("javax.net.ssl.HostnameVerifier".equals(fieldClassName)) {
                    hasHostnameVerifierField = true;
                }
            }
            Method[] methods = clz.getDeclaredMethods();
            for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];
                // 如果方法非public或static方法直接continue
                if (!Modifier.isPublic(method.getModifiers()) || Modifier.isStatic(method.getModifiers())) {
                    continue;
                }
                // 校验方法返回值为Builder自己
                if (!clz.equals(method.getReturnType())) {
                    continue;
                }
                Class<?>[] parameterTypes = method.getParameterTypes();
                int parameterCount = parameterTypes.length;
                if (parameterCount == 1) {
                    if ("javax.net.ssl.SSLSocketFactory".equals(parameterTypes[0].getName())) {
                        hasSslSocketFactoryMethod = true;
                        sslSocketFactoryMethodName = method.toString();
                    }
                } else if (parameterCount == 2) {
                    Class parameter0Clz = parameterTypes[0];
                    Class parameter1Clz = parameterTypes[1];
                    if ("javax.net.ssl.SSLSocketFactory".equals(parameter0Clz.getName())
                            && "javax.net.ssl.X509TrustManager".equals(parameter1Clz.getName())) {
                        hasSslSocketFactory2Method = true;
                        sslSocketFactoryMethod2Name = method.toString();
                    }
                }
            }
        } catch (Throwable e) {
            // XLog.appendText(new Exception(e));
        }
        // 7项核心指标全部达标,我们可以判断这个类就是okhttp3.OkHttpClient$Builder的混淆类了
        if (hasProxyField && hasProxySelectorField && hasSocketFactoryField && hasSSLSocketFactoryField
                && hasHostnameVerifierField) {
            // 返回混淆的类名和方法名
            Okhttp3FakeClass okhttp3FakeClass = new Okhttp3FakeClass("okhttp3.OkHttpClient$Builder", clz.getName());
            if (hasSslSocketFactoryMethod && hasSslSocketFactory2Method) {
                okhttp3FakeClass.addFakeMethod(new FakeMethod(
                        "public okhttp3.OkHttpClient$Builder okhttp3.OkHttpClient$Builder.sslSocketFactory(javax.net.ssl.SSLSocketFactory)",
                        sslSocketFactoryMethodName));
                okhttp3FakeClass.addFakeMethod(new FakeMethod(
                        "public okhttp3.OkHttpClient$Builder okhttp3.OkHttpClient$Builder.sslSocketFactory(javax.net.ssl.SSLSocketFactory,javax.net.ssl.X509TrustManager)",
                        sslSocketFactoryMethod2Name));
            }
            return okhttp3FakeClass;
        }
        return null;
    }

其实还可以更严谨,但是有这几项检测基本上够了。因为很少出现误判情况,至少我到现在还没遇到过。

frida脚本封装

在java层封装好了,我们打包成dex,然后就可以用frida调用了。利用frida的enumerateLoadedClasses方法遍历每个类交给OkHttp3FakeFinder做混淆检测。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Java.perform(function() {
    var OkHttp3FakeFinder = Java.use("gz.justtrustme.OkHttp3FakeFinder");
    Java.enumerateLoadedClasses({
        onMatch: function(className) {
            if (className.match(matchClassNameRegExp)) {
                try {
                    let tempClassHandle = Java.use(className).class;
                    let matchOkHttpClientBuilderCheckResult = OkHttp3FakeFinder.okHttpClientBuilderCheck(tempClassHandle);
                    if (matchOkHttpClientBuilderCheckResult) {
                        printOkhttp3FakeClass(matchOkHttpClientBuilderCheckResult);
                        continue;
                    }
 
                    let matchOk3CertificatePinnerCheckResult = OkHttp3FakeFinder.okHttpCertificatePinnerCheck(tempClassHandle);
                    if (matchOk3CertificatePinnerCheckResult) {
                        printOkhttp3FakeClass(matchOk3CertificatePinnerCheckResult);
                        continue;
                    }
 
                    let matchOk3OkHostnameVerifierVerifyResult = OkHttp3FakeFinder.okHttpOkHostnameVerifierVerify(tempClassHandle);
                    if (matchOk3OkHostnameVerifierVerifyResult) {
                        printOkhttp3FakeClass(matchOk3OkHostnameVerifierVerifyResult);
                        continue;
                    }
 
                } catch(error) {
                    //console.error(error);
                }
            }
        },
        onComplete: function() {}
    });
});

某头条新闻资讯app混淆检测

apk版本android:versionName="3.10.39.000.0427.1536"
jadx打开apk发现okhttp3如下
qutoutiao.png-47kB

 

执行hooker/com.xxxx.qukan$ ./hooking just_trust_me_okhttp_hook_finder.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
-----------------------------------------------------------------------
原类名:okhttp3.CertificatePinner
混淆类名:okhttp3.g
 
 
混淆方法0:
原方法签名:public void okhttp3.CertificatePinner.check(java.lang.String,java.util.List)
混淆方法签名:public void okhttp3.g.a(java.lang.String,java.util.List) throws javax.net.ssl.SSLPeerUnverifiedException
 
 
-----------------------------------------------------------------------
原类名:okhttp3.OkHttpClient$Builder
混淆类名:okhttp3.OkHttpClient$Builder
 
 
自动定位混淆方法失败,请去jadx打开okhttp3.OkHttpClient$Builder手动分析混淆方法
-----------------------------------------------------------------------
原类名:okhttp3.internal.tls.OkHostnameVerifier
混淆类名:okhttp3.internal.i.d
 
 
混淆方法0:
原方法签名:public boolean okhttp3.internal.tls.OkHostnameVerifier.verify(java.lang.String,javax.net.ssl.SSLSession)
混淆方法签名:public boolean okhttp3.internal.i.d.verify(java.lang.String,javax.net.ssl.SSLSession)
 
 
混淆方法1:
原方法签名:public boolean okhttp3.internal.tls.OkHostnameVerifier.verify(java.lang.String,java.security.cert.X509Certificate)
混淆方法签名:public boolean okhttp3.internal.i.d.a(java.lang.String,java.security.cert.X509Certificate)
 
 
-----------------------------------------------------------------------

由于某些还不知的原因,Builder类在遍历方法的时候会出现异常导致分析失败。所以只能还原到类层面,但是已经帮助非常大了。只要自己去看下jadx的代码就可以知道混淆方法名。

修复just_trust_me.js

根据上面just_trust_me_okhttp_hook_finder.js跑的结果
把okhttp3的hook点改成混淆的类:
okhttp_justhook.png-155.7kB
修改提交记录:https://github.com/CreditTone/hooker/commit/f47d2068320a58306735a623f12bd955cbd20632

总结

不容易!把技术突破完再讲给大家听,真难呐!还怕你们看不懂,想多写点。又怕你们觉得烦!啥也不说,就是干!!!


[注意] 招人!base上海,课程运营、市场多个坑位等你投递!

收藏
点赞3
打赏
分享
最新回复 (6)
雪    币: 8343
活跃值: 活跃值 (2402)
能力值: (RANK:200 )
在线值:
发帖
回帖
粉丝
LowRebSwrd 活跃值 4 2021-5-7 22:20
2
0
图片重新贴一下
雪    币: 527
活跃值: 活跃值 (375)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wooyunking 活跃值 2021-5-8 09:42
3
0
支持下
雪    币: 212
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
菱志漪 活跃值 2021-5-9 14:45
4
0
大佬私发下我这里app的名字以及具体版本,还有哪里下载吧,我想测试下,谢谢
雪    币: 210
活跃值: 活跃值 (69)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
小青峰 活跃值 2021-5-13 16:15
5
0
mark
雪    币: 188
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
朝梦 活跃值 2021-5-14 16:11
6
0
大佬还在什么地方发过文章  除了b站
雪    币: 149
活跃值: 活跃值 (178)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
你瞒我瞒 活跃值 2021-5-14 16:48
7
0
这个和xp的hook有什么不同吗
游客
登录 | 注册 方可回帖
返回