【实战】某交友app的双向认证crack

0x0 前言

前几天刚总结了Android平台HTTPS的证书绑定和绕过方法([Android HTTPS认证的N种方式和对抗方法总结](Android HTTPS认证的N种方式和对抗方法总结 (ch3nye.top))),这就遇到了一个双向认证的APP,crack完了才发现2018年就有人搞过它了,不过当前版本和之前的代码逻辑有点变化,这里就简要记录一下思路。

环境

RedmiK305G Android10 root with magisk

BurpSuite v2.0.11

frida 14.2.16

jadx v1.2.0

APP 版本v3.80.0

0x1 检查证书校验方式

手机添加代理证书到系统证书目录,抓包,抓到请求报文,响应总是400:

image-20210507172445520

说明可能是做了双向认证。

查一下assets目录,发现client.p12证书,和我写的双向认证demo app中用的证书名字都一样,赶紧到反编译代码里搜一波:

image-20210507170100115

到这里看一下代码逻辑就实锤了,就是双向认证。

要想抓包必须将客户端证书client.p12导入BurpSuite,所以要获取该客户端证书的密码。

0x2 获取客户端证书密码

从理论上来说,APP要使用客户端证书client.p12,就必须读取该证书文件,这个过程中需要输入证书密码,所以必然能在本地逆向出证书密码。

a.分析代码

去看看上述搜索到的代码逻辑,jadx对此处代码反编译不全,需要看smali,证书载入的逻辑在构造函数init中,代码片段:

const-string v5, "client.p12"

invoke-virtual {v0, v5}, Landroid/content/res/AssetManager;->open(Ljava/lang/String;)Ljava/io/InputStream;

move-result-object v0
:try_end_50
.catch Ljava/lang/Exception; {:try_start_3c .. :try_end_50} :catch_ee

.line 10
:try_start_50
invoke-virtual {v1}, Ljava/lang/String;->toCharArray()[C

move-result-object v5

invoke-virtual {v4, v0, v5}, Ljava/security/KeyStore;->load(Ljava/io/InputStream;[C)V // 此处参数v5字符数组就是密钥
:try_end_57
.catch Ljava/lang/Exception; {:try_start_50 .. :try_end_57} :catch_57
.catchall {:try_start_50 .. :try_end_57} :catchall_5b

.line 11
:catch_57
:try_start_57
invoke-virtual {v0}, Ljava/io/InputStream;->close()V
:try_end_5a
.catch Ljava/lang/Exception; {:try_start_57 .. :try_end_5a} :catch_60

分析一下上述代码,密钥v5是从v1字符串转CharArray来的,向上翻找v1:

invoke-virtual {v1, v2}, Lcn/xxxxapp/android/net/XxxxNetworkSDK;->b(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1

发现v1是从cn.xxxxapp.android.net.xxxxNetworkSDK中的方法b(String str)得到的。

再去翻看cn.xxxxapp.android.net.xxxxNetworkSDK.b(String str):

public String b(String str) {
	return XxxxPowerful.a(str);
}

再去翻看cn.xxxxapp.android.xxxxpower.xxxxPowerful.a(String str):

public class xxxxPowerful {
    static {
        System.loadLibrary("xxxxpower");
    }

    public static String a(String str) {
        return c(str);
    }

    public static String b() {
        return i();
    }

    public static native String c(String str);
	...
}

现在了然了,从libxxxxpower.so中获取client.p12证书密码。

b.hook获取

frida -U -f cn.xxxxapp.android -l .\hook_Key.js --no-pause

hook_Key.js

setTimeout(function(){
    Java.perform(function (){
    	var KeyStore = Java.use("java.security.KeyStore");
        KeyStore.load.overload('java.io.InputStream', '[C').implementation = function(a,pass) {
            console.log("\n============keystore.load()============");
            console.log(pass);
            return this.load(a,pass);
        };
        var XxxxNetworkSDK = Java.use("cn.xxxxapp.android.net.XxxxNetworkSDK");
        XxxxNetworkSDK.b.overload('java.lang.String').implementation = function(str) {
            var res = this.b(str);
            console.log("\n============XxxxNetworkSDK.b()============");
            console.log(str);
            console.log(res);
            return str;
        }

    });
},0);

我一开始想直接hook java.security.KeyStore.load方法获取key但是结果总为空,后来通过hook XxxxNetworkSDK中b方法成功获取密钥:

frida logcat:

[Redmi K30 5G::cn.xxxxapp.android]->
============keystore.load()============
null

============keystore.load()============
null

============XxxxNetworkSDK.b()============
10000003
}%3R-\0SsjpP1w%X

============keystore.load()============
[object Object]

c.导入证书

image-20210507172104297

d.成功抓包

image-20210507173421905

而且奇怪的是这个app似乎对请求没有限速,5线程疯狂访问某个用户主页1000次,居然没ban我😁🙏。