抓包进阶之SSL unpinning
zsk Lv4

SSL Pinning Bypass

客户端的证书绑定(pinning)。一种情况没有分析,就是客户端并不会默认信任系统根证书目录中的证书,而是在代码里再加一层校验,这就是证书绑定机制——SSL pinning,如果这段代码的校验过不了,那么客户端还是会报证书错误。
两层校验,
第一种不信任系统证书,使用自己的证书。
第二种在自己证书上再做一层判断
● Https客户端代码校验服务器证书
案例是自己配置的证书,不是ca机构签发的。这段代码放了自己的证书,一段检验机制

image

打开charles抓包,抓包失败

image
image

信任所有证书,都为空表示都信任

image
image
image

因为图片是https的,修改一下ssl

image

成功抓包
image

让客户端信任系统根证书目录中的证书

把证书校验重新开起来
image
image

hook住checkServerTrusted,将其所有重载都置空;
使用spawn模式运行

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
function main(){
Java.perform(function (){
// 检查一个类是否继承某一个接口
function checkIsImplementeInterFace(clsName, interface_){
try{
var cls = Java.use(clsName)
if(cls.class.isInterface()){
return false
}
if(cls.class != undefined)
if(interface_.class.isAssignableFrom(cls.class)){
return true
}
return false
}catch(e){
return false
}
}

var TrustManagerInterface = Java.use("javax.net.ssl.TrustManager")

Java.use("okhttp3.CertificatePinner").check.overload('java.lang.String', '[Ljava.security.cert.Certificate;').implementation = function(){
console.log("CertificatePinner check called!")

}

Java.use("okhttp3.CertificatePinner").check.overload('java.lang.String', 'java.util.List').implementation = function(){
console.log("CertificatePinner check called!")

}

Java.enumerateLoadedClasses({
onMatch: function(clsName,handle){
if(checkIsImplementeInterFace(clsName,TrustManagerInterface)){
console.log(clsName)
var targetClass = Java.use(clsName)
var len = targetClass["checkServerTrusted"].overloads.length
for (var i = 0; i < len; i++){
targetClass["checkServerTrusted"].overloads[i].implementation = function(){

console.log(clsName + i + "checkServerTrusted Called!")
//return targetClass["checkServerTrusted"].overloads[i].apply(this,arguments)

}
}
}
},
onComplete: function(){
console.log("Search Classes Completed!")
}

})

})
}
setImmediate(main)

成功抓到包

image

过检验证书

把证书检验开启

image

objection绕过检测

使用objection,直接将SSL pinning给disable掉

1
android sslpinning disable

ssl pinning+服务器检验客户端证书

注册页面抓包,证书不信任
image

用frida hook掉他的一些检测,

ssl pinning(hook掉证书验证(自定义证书))

方式一

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
function main(){
Java.perform(function (){
// 检查一个类是否继承某一个接口
function checkIsImplementeInterFace(clsName, interface_){
try{
var cls = Java.use(clsName)
if(cls.class.isInterface()){
return false
}
if(cls.class != undefined)
if(interface_.class.isAssignableFrom(cls.class)){
return true
}
return false
}catch(e){
return false
}
}

var TrustManagerInterface = Java.use("javax.net.ssl.TrustManager")

// Java.use("okhttp3.CertificatePinner").check.overload('java.lang.String', '[Ljava.security.cert.Certificate;').implementation = function(){
// console.log("CertificatePinner check called!")

// }

// Java.use("okhttp3.CertificatePinner").check.overload('java.lang.String', 'java.util.List').implementation = function(){
// console.log("CertificatePinner check called!")

// }

Java.enumerateLoadedClasses({
onMatch: function(clsName,handle){
if(checkIsImplementeInterFace(clsName,TrustManagerInterface)){
console.log(clsName)
var targetClass = Java.use(clsName)
var len = targetClass["checkServerTrusted"].overloads.length
for (var i = 0; i < len; i++){
targetClass["checkServerTrusted"].overloads[i].implementation = function(){

console.log(clsName + i + "checkServerTrusted Called!")
//return targetClass["checkServerTrusted"].overloads[i].apply(this,arguments)

}
}
}
},
onComplete: function(){
console.log("Search Classes Completed!")
}

})

})
}
setImmediate(main)

方式二

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
function main(){
Java.perform(function (){
function checkIsImplementeInterFace(clsName, interface_){
try{
var cls = Java.use(clsName)
if(cls.class.isInterface()){
return false
}
if(cls.class != undefined)
if(interface_.class.isAssignableFrom(cls.class)){
return true
}
return false
}catch(e){
return false
}
}

var TrustManagerInterface = Java.use("javax.net.ssl.TrustManager")

// Java.use("okhttp3.CertificatePinner").check.overload('java.lang.String', '[Ljava.security.cert.Certificate;').implementation = function(){
// console.log("CertificatePinner check called!")
// }

// Java.use("okhttp3.CertificatePinner").check.overload('java.lang.String', 'java.util.List').implementation = function(){
// console.log("CertificatePinner check called!")
// }

// Java.enumerateClassLoaders({
// onMatch: function(loader){
// try{
// if(loader.findClass("okhttp3.OkHttpClient$Builder")){
// console.log("Found the really Classloader")
// Java.classFactory.loader = loader
// }
// }catch(e){

// }
// },
// onComplete: function(){
// console.log("Search Loaders Completed!")
// }
// })
// Java.use("okhttp3.OkHttpClient$Builder").certificatePinner.implementation = function(certificatePinner){
// return this.certificatePinner(Java.use("okhttp3.CertificatePinner").DEFAULT.value) //CertificatePinner.DEFAULT
// }



Java.enumerateLoadedClasses({
onMatch: function(clsName,handle){
if(checkIsImplementeInterFace(clsName,TrustManagerInterface)){
console.log(clsName)
var targetClass = Java.use(clsName)
var len = targetClass["checkServerTrusted"].overloads.length
for (var i = 0; i < len; i++){
//console.log(targetClass["checkServerTrusted"].overloads[i].returnType.name)
if(targetClass["checkServerTrusted"].overloads[i].returnType.name == 'V'){
targetClass["checkServerTrusted"].overloads[i].implementation = function(){
console.log(clsName + i + "checkServerTrusted Called!")
//return targetClass["checkServerTrusted"].overloads[i].apply(this,arguments)
}
}else{
targetClass["checkServerTrusted"].overloads[i].implementation = function(){
console.log(clsName + i + "checkServerTrusted Called!")
//return targetClass["checkServerTrusted"].overloads[i].apply(this,arguments)
return null
}
}
}
}
},
onComplete: function(){
console.log("Search Classes Completed!")
}

})

})
}
setImmediate(main)

现在可以抓到包,证明请求发出去,那就是证书不对
image

dump 证书

先关掉vpn,让它正常发请求,并用脚本把证书导出

方法一

把证书密码dump出来,手动解压apk搜索证书导入charles即可

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
function main(){
Java.perform(function (){
function print_stack(p) {
console.log("=====>", p)
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
}

var StringClass = Java.use("java.lang.String");
var KeyStore = Java.use("java.security.KeyStore");
KeyStore.load.overload('java.security.KeyStore$LoadStoreParameter').implementation = function (arg0) {
// print_stack("KeyStore.load1");
console.log("KeyStore.load1:", arg0);
this.load(arg0);
};
KeyStore.load.overload('java.io.InputStream', '[C').implementation = function (arg0, arg1) {
// print_stack("KeyStore.load2");
console.log("KeyStore.load2:", arg0, arg1 ? StringClass.$new(arg1) : null);
this.load(arg0, arg1);
};

console.log("hook_KeyStore_load...");
})
}


setImmediate(main)

image

方法二

dump出证书到手机,并修改证书密码

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
function main(){
Java.perform(function (){
console.log("Enter!")
function storeP12(pri, p7, p12Path, p12Password) {
var X509Certificate = Java.use("java.security.cert.X509Certificate")
var p7X509 = Java.cast(p7, X509Certificate);
var chain = Java.array("java.security.cert.X509Certificate", [p7X509])
var ks = Java.use("java.security.KeyStore").getInstance("PKCS12", "BC");
ks.load(null, null);
ks.setKeyEntry("client", pri, Java.use('java.lang.String').$new(p12Password).toCharArray(), chain);
try {
var out = Java.use("java.io.FileOutputStream").$new(p12Path);
ks.store(out, Java.use('java.lang.String').$new(p12Password).toCharArray())
} catch (exp) {
console.log(exp)
}
}
//在服务器校验客户端的情形下,帮助dump客户端证书,并保存为p12的格式,证书密码为r0ysue
Java.use("java.security.KeyStore$PrivateKeyEntry").getPrivateKey.implementation = function () {
var result = this.getPrivateKey()
var packageName = Java.use("android.app.ActivityThread").currentApplication().getApplicationContext().getPackageName();
storeP12(this.getPrivateKey(), this.getCertificate(), '/sdcard/Download/' + packageName + '.p12', 'r0ysue');
console.log("dumpClinetCertificate=>" + '/sdcard/Download/' + packageName + '.p12' + ' pwd: r0ysue');
return result;
}
Java.use("java.security.KeyStore$PrivateKeyEntry").getCertificateChain.implementation = function () {
var result = this.getCertificateChain()
var packageName = Java.use("android.app.ActivityThread").currentApplication().getApplicationContext().getPackageName();
storeP12(this.getPrivateKey(), this.getCertificate(), '/sdcard/Download/' + packageName + '.p12', 'r0ysue');
console.log("dumpClinetCertificate=>" + '/sdcard/Download/' + packageName + '.p12' + ' pwd: r0ysue');
return result;
}
})
}


setImmediate(main)

image

charles导入证书

1
adb pull /sdcard/Download/com.ninemax.ncsearchnew.p12 /root/Desktop/

image
再运行绕过本地校验脚本,成功抓包
image

混淆的okhhtp3抓包

image

通过它的官网知道是https,用的ca机构证书。不需要hook证书校验这一步
jadx打开搜索 CertificatePinner

image

通过查看的CertificatePinner源码,和该app的代码对比,基本一样的逻辑,证书校验不通过就会抛异常

image
image

使用frida对其hook,置空

1
2
3
4
5
6
7
8
function main(){
Java.perform(function (){
Java.use("q.g").a.implementation = function(){
console.log("check called!")
}
})
}
setImmediate(main)

charles设置443端口
image

成功抓包

image

 评论