SSL Pinning Bypass
客户端的证书绑定(pinning)。一种情况没有分析,就是客户端并不会默认信任系统根证书目录中的证书,而是在代码里再加一层校验,这就是证书绑定机制——SSL pinning,如果这段代码的校验过不了,那么客户端还是会报证书错误。
两层校验,
第一种不信任系统证书,使用自己的证书。
第二种在自己证书上再做一层判断
● Https客户端代码校验服务器证书
案例是自己配置的证书,不是ca机构签发的。这段代码放了自己的证书,一段检验机制
打开charles抓包,抓包失败
信任所有证书,都为空表示都信任
因为图片是https的,修改一下ssl
成功抓包
让客户端信任系统根证书目录中的证书
把证书校验重新开起来
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!") } } } }, onComplete: function(){ console.log("Search Classes Completed!") }
}) }) } setImmediate(main)
|
成功抓到包
过检验证书
把证书检验开启
objection绕过检测
使用objection,直接将SSL pinning给disable掉
1
| android sslpinning disable
|
ssl pinning+服务器检验客户端证书
注册页面抓包,证书不信任
用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.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!") } } } }, 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.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++){ if(targetClass["checkServerTrusted"].overloads[i].returnType.name == 'V'){ targetClass["checkServerTrusted"].overloads[i].implementation = function(){ console.log(clsName + i + "checkServerTrusted Called!") } }else{ targetClass["checkServerTrusted"].overloads[i].implementation = function(){ console.log(clsName + i + "checkServerTrusted Called!") return null } } } } }, onComplete: function(){ console.log("Search Classes Completed!") }
}) }) } setImmediate(main)
|
现在可以抓到包,证明请求发出去,那就是证书不对
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) { console.log("KeyStore.load1:", arg0); this.load(arg0); }; KeyStore.load.overload('java.io.InputStream', '[C').implementation = function (arg0, arg1) { console.log("KeyStore.load2:", arg0, arg1 ? StringClass.$new(arg1) : null); this.load(arg0, arg1); };
console.log("hook_KeyStore_load..."); }) }
setImmediate(main)
|
方法二
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) } } 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)
|
charles导入证书
1
| adb pull /sdcard/Download/com.ninemax.ncsearchnew.p12 /root/Desktop/
|
再运行绕过本地校验脚本,成功抓包
混淆的okhhtp3抓包
通过它的官网知道是https,用的ca机构证书。不需要hook证书校验这一步
jadx打开搜索 CertificatePinner
通过查看的CertificatePinner源码,和该app的代码对比,基本一样的逻辑,证书校验不通过就会抛异常
使用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端口
成功抓包