1.关于混淆

  • 在安卓开发中,对于第三方的包是可以进行混淆的,例如:OKHttp3.Http.Cert.check 被混淆后可以是a.f.c.b 形式。
  • 在安卓开发中,系统包是无法混淆的,例如:java.security.KeyStore不会被混淆。

由于这种的情况的存在,再次审示我们之前的通用脚本,就会发现他是不通用的,例如:

  • 客户端证书校验的frida脚本【不通用,被混淆后无法用】

    1
    2
    Java.use('okhttp3.CertificatePinner');  
    Java.use('com.squareup.okhttp.internal.tls.OkHostnameVerifier');
  • 服务端证书校验的frida脚本【通用】

    1
    Java.use("java.security.KeyStore");

那如果遇到有存在混淆的app,内部又加入了客户端证书校验,原来的绕过的方式就都不能用来,怎么解决呢?

  • Hook系统底层必走的核心方法,获取调用栈
  • 根据调用栈向上寻找 客户端证书校验 代码位置,找他其被混淆后的 类名和方法名。
  • 用frida Hook脚本的方式绕过。

如下图:未混淆时,客户端证书校验的三个位置:

image-20221013202819400

如下图:app混淆后,代码会变成这样:

image-20221013215844907

2.客户端校验和系统方法

客户端校验的顺序分别是:

  • 第1步:调用证书校验
    image-20221007160251055
  • 第2步:主机校验
    image-20221007160311071
  • 第3步:pinner公钥校验,这个校验过程本质上是调用CertificatePinner类中的check方法。
    image-20221007160348955

注意:内部按照顺序对这个3个过程进行校验,只要有一个无法通过,后续的校验就不会再触发执行。

上述三个校验的触发位置是在:okhttp3.internal.connection.RealConnection类中的connectTls方法,例如:

image-20221007160903732

2.1 调用栈

我是怎么确定触发校验的位置就是这里呢?

最简单的方式就是对我们已开发完成的安卓程序(NetDemo)案例进行Hook,根据他们的调用栈来证明,例如:

1
Log.e("调用栈", Log.getStackTraceString(new Throwable()));

image-20221007161218948

image-20221007161358236

image-20221007161430339

2.2 Hook系统位置

在上述调用栈中发现在证书校验时,底层会走 com.android.org.conscrypt.NativeSsldoHandshake方法。

所以,可以Hook他,根据调用栈向上找到证书校验的位置(其他验证也在旁边)。

1
2
3
4
5
6
7
8
9
10
Java.perform(function () {
var NativeSsl = Java.use('com.android.org.conscrypt.NativeSsl');
NativeSsl.doHandshake.overload('java.io.FileDescriptor', 'int').implementation = function (a, b) {
console.log("参数:", a, b);
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
return this.doHandshake(a, b);
};
});

// frida -UF -l 1.hook_check.js

image-20221007173854351

对比未混淆和混淆的代码,可以找到相应的证书监测的位置:

image-20230207153315334

3.滴答清单(案例)

以滴答清单(v6.3.3.0)为例,来展开代码混淆处理的过程。

3.1 现象

打开app,进入注册界面,点击【发送验证码】

  • 未设置代理,可以发送成功
  • 已设置代理,请求发送失败

image-20221007182107873

那么,内部应该可能是做了客户端校验。

但是,使用justrustme 和 frida 脚本依然不好使,哪就有可能是内部进行了代码混淆。

3.2 寻找connectTls

因为触发客户端校验的位置是在:okhttp3.internal.connection.RealConnection类中的connectTls方法中,所以,我们可以先寻找到这个位置,看他被混淆成了啥?

image-20221007173635216

可以通过hook系统方法 + 输入调用栈,定位 RealConnection类的connectTls方法

1
2
3
4
5
6
7
8
9
10
Java.perform(function () {
var NativeSsl = Java.use('com.android.org.conscrypt.NativeSsl');
NativeSsl.doHandshake.overload('java.io.FileDescriptor', 'int').implementation = function (a, b) {
console.log("参数:", a, b);
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
return this.doHandshake(a, b);
};
});

// frida -UF -l 1.hook_check.js

image-20221007173854351

image-20221007174206524

3.3 pinner校验

3.3.1 寻找位置

pinner校验比较常见,在这里也比较简单。

1
2
3
4
aVar2.f28365k.a(aVar2.f28355a.f28521d, a11.f28513c);

- 在安卓开发中,这个a方法其实就是 CertificatePinner类中的check方法
- 此处的a就是check方法 并且 aVar2.f28365k返回的就是CertificatePinner类

image-20221007174721215

image-20221007174751419

所以,我们参考之前hook的check方法,来编写hook脚本,对此方法进行hook,例如:

image-20221007175344175

1
2
3
4
5
6
7
8
9
Java.perform(function () {
var pinner = Java.use('rk.f');
pinner.a.overload('java.lang.String', 'java.util.List').implementation = function(a, b) {
console.log('[+] pinner check ' + a);
return;
};
});

// frida -UF -l hook_check.js

3.3.2 测试

由于客户端的验证流程先后顺序:【证书】、【主机】、【pinner】。

接下来,执行如下2个动作:

  • 手机挂上代理
  • 用frida脚本去hook刚才找到的pinner的check,绕过check校验

执行上述动作后,再次对【发送验证码】请求,对于其结果分析如下:

  • 可以抓到包,则表示 滴答清单app只做了pinner的校验。

  • 无法抓到包,则表示滴答清单app内部还有【证书】、【主机】的校验,这些校验是在【pinner】之前触发,所以,压根就没走到绕过【pinner】校验的步骤,就校验失败了。

    提示:如果是这种情况,就需要继续寻找其他校验的位置,来解决。

事实上,这个滴答清单app 只做了pinner的校验。

image-20221007183830582

3.3.3 其他

如果只是为了绕过 pinner 校验,其实没必要再继续往下看了。。

后续的分析是为了寻找并定位到秘钥的位置。

image-20221007222421636

image-20221007222525310

在代码中添加pin校验规则时,会将写入的规则每个都创建Pin对象,然后放在Set中,如下图:

image-20221007222627326

image-20221007222727728

所以,可以hook这个Pin的构造方法,来获取代码中添加的域名和加密值,以及代码位置。

image-20221007222856167

1
2
3
4
5
6
7
8
9
10
11
Java.perform(function () {
var fa = Java.use('rk.f$a');
fa.$init.implementation = function(a, b) {
console.log('[+] pin ',a,b);
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
return this.$init(a,b);
};
});

// frida -U -f cn.ticktick.task -l 5.hook_pin.js

image-20221007223626336

image-20221007223937491

很显然就是去 Assets目录下寻找证书,然后在进行hash256加密。

image-20221007225538875

  • 将dida365文件转换成pem格式

    1
    openssl x509 -inform der -in dida365 -out x.pem
  • 将pem格式处理并获取hash256值

    1
    openssl x509 -in x.pem -pubkey  -noout  | openssl rsa  -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64

image-20221007225242520

另外一个ticktick

1
*.ticktick.com sha256/I7TYVHP5UJmUvGnTiAQOuUSuTgRuE5fLkn9UwFd1mlc=

image-20221007225452929

3.4 证书校验

3.4.1 关键字搜索(方式1)

如果继续寻找证书校验的相关代码,可以搜索:

  • checkServerTrusted方法 或 X509Certificate[] chain参数
  • SSLContext.getInstancenew TrustManager[] 或 hook sslContext.init
    image-20221008151316865

这些都是属于系统包中的提供的类和方法,可以直接去搜索。

3.4.2 源码流程和Hook(方式2)

证书校验的checkServerTrusted方法其实是系统的代码触发的,例如:

image-20221008165050827

image-20221008165421397

1
https://github.com/google/conscrypt/blob/86ff4e3fd4b6b3bb76a7ec0e91290384401ccbf3/android/src/main/java/org/conscrypt/Platform.java#L396

所以,当我们hook这个方法时,可以实现:

  • 绕过校验
  • 输出tm到底是在哪里定义(tm其实就是业务代码中进行证书校验的位置)

image-20221008165729762

1
2
3
4
5
6
7
8
9
10
11
Java.perform(function () {
var Platform = Java.use('com.android.org.conscrypt.Platform');
Platform.checkServerTrusted.overload('javax.net.ssl.X509TrustManager', '[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'com.android.org.conscrypt.AbstractConscryptSocket').implementation = function (x509tm, chain, authType, socket) {
console.log('\n[+] checkServer ',x509tm,JSON.stringify(x509tm) );

//return this.checkServerTrusted(x509tm, chain, authType, socket);
};
});

// frida -U -f 包名 -l 6.hook_check.js --no-pause
// frida -UF -l 6.hook_check.js

3.4.3 滴答清单

应用在滴答清单app中,就会发现他其实使用的是RootTrustManager,即:系统证书校验(其实就是https证书校验)。

image-20221008170015783

扩展:如果安卓中想要做 系统证书校验的开发,可以这样:

image-20221008170729393

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
private void doRequest() {

X509TrustManager x509TrustManager = null;
SSLSocketFactory factory = null;

try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
x509TrustManager = (X509TrustManager) trustManagers[0];

SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new TrustManager[]{x509TrustManager}, new SecureRandom());

factory = sslContext.getSocketFactory();
} catch (Exception e) { }

SSLSocketFactory finalFactory = factory;
X509TrustManager finalX509TrustManager = x509TrustManager;
new Thread() {
@Override
public void run() {
try {

OkHttpClient client = new OkHttpClient.Builder().sslSocketFactory(finalFactory, finalX509TrustManager).build();
Request req = new Request.Builder().url("https://www.baidu.com/?q=defaultCerts").build();
Call call = client.newCall(req);

Response res = call.execute();
Log.e("请求发送成功", "状态码:" + res.code());

} catch (IOException ex) {
Log.e("Main", "网络请求异常" + ex);


}
}
}.start();
}

3.5 主机校验

image-20221008171101495

3.5.1 关键字搜索(方式1)

HostnameVerifier属于是javax.net.ssl 中的接口不会被混淆。

所以,可以直接搜索 HostnameVerifiernew HostnameVerifier实现HostnameVerifier接口的类verify 关键字等。

3.5.2 源码流程和Hook(方式2)

主机校验是在:okhttp3.internal.connection.RealConnection类中的connectTls方法中触发的。

image-20221008172449606

所以,可以hook这个connectTls方法,去内部读取hostNameVerifier()的值。

image-20221008181240804

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
Java.perform(function () {

function getFieldValue(obj, fieldName) {
var cls = obj.getClass();
var field = cls.getDeclaredField(fieldName);
field.setAccessible(true);
var name = field.getName();
var value = field.get(obj);
return value;
}

function getMethodValue(obj, methodName) {
var res;
var cls = obj.getClass();
var methods = cls.getDeclaredMethods();

methods.forEach(function (method) {
var method_name = method.getName();
console.log(method_name, method);
if (method_name === methodName) {
method.setAccessible(true);
res = method;
return;
}
})
return res;
}

var RealConnection = Java.use('okhttp3.internal.connection.RealConnection');
RealConnection.connectTls.implementation = function (connectionSpecSelector) {

var route = getFieldValue(this, "route"); // this.route
var address = getFieldValue(route, 'address'); // address
var hostnameVerifier = getFieldValue(address, 'hostnameVerifier');
console.log('\n[+] hostnameVerifier', hostnameVerifier);
/*
try {
var route = getFieldValue(this, "route");
var address = getFieldValue(route, 'address');
var func = getMethodValue(address, "hostnameVerifier");
console.log('\n[+] addhostnameVerifierress', func.invoke(address, null));
} catch (e) {
console.log(e);
}
*/
return this.connectTls(connectionSpecSelector);
};
});

// frida -U -f 包名 -l 7.hook_verify.js
// frida -UF -l 7.hook_verify.js
// frida -U -f cn.ticktick.task -l 7.hook_verify.js

3.5.3 滴答清单

image-20221007174206524

image-20221008183108353

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
Java.perform(function () {

function getFieldValue(obj, fieldName) {
var cls = obj.getClass();
var field = cls.getDeclaredField(fieldName);
field.setAccessible(true);
var name = field.getName();
var value = field.get(obj);
return value;
}

function getMethodValue(obj, methodName) {
var res;
var cls = obj.getClass();
var methods = cls.getDeclaredMethods();

methods.forEach(function (method) {
var method_name = method.getName();
console.log(method_name, method);
if (method_name === methodName) {
method.setAccessible(true);
res = method;
return;
}
})
return res;
}

var RealConnection = Java.use('uk.c');
RealConnection.f.implementation = function (a, b, c, d) {
try {
console.log("===============");
var route = getFieldValue(this, "c");
console.log('route=', route);
var address = getFieldValue(route, 'a');
console.log('address=', address);
var hostnameVerifier = getFieldValue(address, 'j');
console.log('hostnameVerifier=', hostnameVerifier);
console.log('\n[+] hostnameVerifier', hostnameVerifier);
} catch (e) {
console.log(e);
}
return this.f(a, b, c, d);
};
});

// frida -U -f 包名 -l 7.hook_verify.js --no-pause
// frida -UF -l 7.hook_verify.js

// frida -U -f cn.ticktick.task -l 7.hook_verify.js --no-pause

image-20221008183050577

这说明压根就没设置hostnameVerifier。

4.总结

以后遇到 混淆版的 客户端校验时,本质上的需要做2件事:

  • 定位混淆后的代码位置
  • Hook绕过

如果定位混淆后的代码呢?

  • hook系统方法,根据调用栈寻找到 doHandshake 被混淆的位置。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Java.perform(function () {
    var NativeSsl = Java.use('com.android.org.conscrypt.NativeSsl');
    NativeSsl.doHandshake.overload('java.io.FileDescriptor', 'int').implementation = function (a, b) {
    console.log("参数:", a, b);
    console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
    return this.doHandshake(a, b);
    };
    });

    // frida -UF -l 1.hook_check.js
  • 对比分析,先找到 connectTls 位置,由此展开后续分析。
    image-20221013215844907

如果绕过?

  • 绕过pinner证书,最简单

    1
    2
    直接在connectTls中就能找到他的类和方法被混淆后的名称,可以直接Hook。
    例如:上面的第52行
  • 绕过证书校验

    1
    2
    方式1:关键字搜索定位代码(费劲)
    方式2:Hook底层校验的方法,直接绕过
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Java.perform(function () {
    var Platform = Java.use('com.android.org.conscrypt.Platform');
    Platform.checkServerTrusted.overload('javax.net.ssl.X509TrustManager', '[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'com.android.org.conscrypt.AbstractConscryptSocket').implementation = function (x509tm, chain, authType, socket) {
    console.log('\n[+] checkServer ',x509tm,JSON.stringify(x509tm) );
    // 这里会去调用客户端证书校验的方法,不执行,就是不去校验(直接通过)。
    //return this.checkServerTrusted(x509tm, chain, authType, socket);
    };
    });

    // frida -U -f 包名 -l 6.hook_check.js --no-pause
    // frida -UF -l 6.hook_check.js
  • Host校验

    1
    2
    3
    方式1:关键字搜索(费劲)
    方式2:对connectTls进行Hook,通过反射寻找Host的类,然后再定位+Hook
    注意:需要先找到connectTls的位置
    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
    Java.perform(function () {

    function getFieldValue(obj, fieldName) {
    var cls = obj.getClass();
    var field = cls.getDeclaredField(fieldName);
    field.setAccessible(true);
    var name = field.getName();
    var value = field.get(obj);
    return value;
    }

    function getMethodValue(obj, methodName) {
    var res;
    var cls = obj.getClass();
    var methods = cls.getDeclaredMethods();

    methods.forEach(function (method) {
    var method_name = method.getName();
    console.log(method_name, method);
    if (method_name === methodName) {
    method.setAccessible(true);
    res = method;
    return;
    }
    })
    return res;
    }

    var RealConnection = Java.use('okhttp3.internal.connection.RealConnection');
    RealConnection.connectTls.implementation = function (connectionSpecSelector) {
    var route = getFieldValue(this, "route");
    var address = getFieldValue(route, 'address');
    var hostnameVerifier = getFieldValue(address, 'hostnameVerifier');
    console.log('\n[+] hostnameVerifier', hostnameVerifier);
    /*
    try {
    var route = getFieldValue(this, "route");
    var address = getFieldValue(route, 'address');
    var func = getMethodValue(address, "hostnameVerifier");
    console.log('\n[+] addhostnameVerifierress', func.invoke(address, null));
    } catch (e) {
    console.log(e);
    }
    */
    return this.connectTls(connectionSpecSelector);
    };
    });

    // frida -UF -l 7.hook_verify.js
    // frida -U -f cn.ticktick.task -l 7.hook_verify.js

5 笔记

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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
抓包-代码混淆

1.关于混淆
在基于安卓开发的APP中:自定义功能代码 + 第三方模块 + 系统包
- 自定义功能代码 + 第三方模块 可以被混淆
com.nb.demo.UserInfo -> a.b.e
- 系统包
android.util.Base64 -> android.util.Base64

2.客户端证书校验 and 服务端证书校验
- 客户端证书校验
Java.use('okhttp3.CertificatePinner');
Java.use('com.squareup.okhttp.internal.tls.OkHostnameVerifier');

方案:找系统包 + 调用栈 + 对比分析 -> 被混淆的模块名称

- 服务端证书校验
Java.use("java.security.KeyStore");

3.源码调用栈和底层执行过程
测试运行:Net04
手机环境:系统代理去掉
客户端证书:
CertificatePinner.check
X509TrustManager.checkServerTrusted
输出调用栈
Log.e("调用栈", Log.getStackTraceString(new Throwable()));
at com.nb.net04.MainActivity$1.checkServerTrusted(MainActivity.java:50)
at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:228)
at com.android.org.conscrypt.ConscryptFileDescriptorSocket.verifyCertificateChain(ConscryptFileDescriptorSocket.java:407)
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
at com.android.org.conscrypt.NativeSsl.doHandshake(NativeSsl.java:387)
at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:226)
at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.java:336)
HostnameVerifier.verify

3.1 connectTls
okhttp3.internal.connection.RealConnection.connectTls

3.2 系统包doHandshake
at com.android.org.conscrypt.NativeSsl.doHandshake(NativeSsl.java:387)
at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:226)
at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.java:336)

4.案例:滴答清单v6.3.3.0

4.1 抓包测试
- 手机 -> 直接上网 -> 正常发送请求
- 手机 -> charles -> 发送失败
- Frida脚本 【无效】
- justtrustme【无效】


4.2 Hook系统方法doHandshake,寻找connectTls
com.android.org.conscrypt.NativeSsl
doHandshake
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));

正常:
at com.android.org.conscrypt.NativeSsl.doHandshake(NativeSsl.java:387)
at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:226)
at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.java:336)
at okhttp3.internal.connection.RealConnection.establishProtocol(RealConnection.java:300)

滴答清单:
at com.android.org.conscrypt.NativeSsl.doHandshake(Native Method)
at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:226)
at uk.c.f(RealConnection.java:27)
at uk.c.c(RealConnection.java:22)
at uk.f.d(StreamAllocation.java:95)

反编译APK:
uk.c.f


4.3 分析绕过CertificatePinner

- 以往的Hook脚本:
var okhttp3_Activity_1 = Java.use('okhttp3.CertificatePinner');
okhttp3_Activity_1.check.overload('java.lang.String', 'java.util.List').implementation = function(a, b) {
console.log('[+] Bypassing OkHTTPv3 {1}: ' + a);
return;
};

- 混淆后的脚本:
Java.perform(function () {
var pinner = Java.use('rk.f');
pinner.a.overload('java.lang.String', 'java.util.List').implementation = function(a, b) {
console.log('[+] pinner check ' + a);
return;
};
});


4.4 分析绕过证书校验

定义在自定义类中:
X509TrustManager trustManager = new X509TrustManager() {
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
...
}

}

调用栈:
at com.nb.net04.MainActivity$1.checkServerTrusted(MainActivity.java:50)
at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:228)

默认看不到checkServerTrusted具体内容:
去安卓源码中寻找
https://github.com/google/conscrypt/blob/86ff4e3fd4b6b3bb76a7ec0e91290384401ccbf3/android/src/main/java/org/conscrypt/Platform.java#L396

public static void checkServerTrusted(X509TrustManager tm, X509Certificate[] chain,String authType, AbstractConscryptSocket socket) throws CertificateException {
if (!checkTrusted("checkServerTrusted", tm, chain, authType, Socket.class, socket)&& !checkTrusted("checkServerTrusted", tm, chain, authType, String.class,socket.getHandshakeSession().getPeerHost())) {
tm.checkServerTrusted(chain, authType);
}
}

方式1:找到在哪里?然后再进行Hook绕过。
a.去Hook -> com.android.org.conscrypt.Platform.checkServerTrusted
b.输出第1个参数 tm -> JSON.stringify(tm)

方法2:直接Hook系统checkServerTrusted方法,直接什么都不干。
..


4.5 分析绕过主机校验

- 定义在自定义模块中

- 被混淆过的代码
HostnameVerifier aVar2.j = ....
if(!aVar2.j.verify(..)){
...
}
console.log( JSON.Stringify(aVar2.j)) -> 包.类

- 如果要进行Hook
uk.c.f
真实的hostnameVerifier = this.c.a.j
if(!aVar2.j.verify(..)){}

okhttp3.internal.connection.RealConnection.connectTls
Address address = this.route.address();
if (!address.hostnameVerifier().verify(address.url().host(), sslSocketSession))

真实的hostnameVerifier对象 = this.route.address().hostnameVerifier()

如果Hook获取到这个【真实的hostnameVerifier = this.c.a.j】,基于反射的形式读取

- Hook被混淆后的类的包和方法名称

hostnameVerifier真实类定义的路径.verify


其他寻找和定位的方式【不推荐】,关键字寻找。
搜索:X509Certificate[]
搜索:new HostnameVerifier 或 class xxxxx implemented HostnameVerifier{}
...

5.总结

5.1 客户端证书 + 服务端证书

5.2 混淆版(客户端证书)

__END__