先看两段代码:
这两段代码大概意思就是本地随机生成一个AES的密钥a,然后使用硬编码在代码中的字符串来生成RSA公钥,最后再使用生成的公钥对这个AES密钥a进行加密,这样对称加密的密钥a就可以安全传输了。
当客户端和服务端进行通讯时,客户端使用这个密钥a对明文进行加密,同时再使用公钥加密密钥a进行安全传输,服务器收到密文和加密后的密钥a之后,先使用私钥解密得到密钥a,再使用密钥a解密密文,这样即可正确解密服务端传送的数据。
看似是这么回事,我们使用Frida来Hook一下这个随机产生AES密钥的方法,得到随机生成的密钥,之后抓包,把Hook出来的密文拿来解密,验证一下我们的方法~
Hook代码如下,这里依旧添加了SSL pinning绕过的脚本代码,免得抓包失败:
Java.perform(function () { var SecurityKeys = Java.use("com.platform.usercenter.common.security.SecurityProtocolManager$SecurityKeys") SecurityKeys.$init.implementation = function () { this.$init(); console.log("=================") console.log(this.mAES.value) console.log("=================") } SecurityKeys.decrypt.implementation = function (str) { console.log("-----------") console.log(str) console.log("-----------") } console.log('') console.log('===') console.log('* Injecting hooks into common certificate pinning methods *') console.log('===') var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager'); var SSLContext = Java.use('javax.net.ssl.SSLContext'); // build fake trust manager var TrustManager = Java.registerClass({ name: 'com.sensepost.test.TrustManager', implements: [X509TrustManager], methods: { checkClientTrusted: function (chain, authType) { }, checkServerTrusted: function (chain, authType) { }, getAcceptedIssuers: function () { return []; } } }); // pass our own custom trust manager through when requested var TrustManagers = [TrustManager.$new()]; var SSLContext_init = SSLContext.init.overload( '[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom' ); SSLContext_init.implementation = function (keyManager, trustManager, secureRandom) { console.log('! Intercepted trustmanager request'); SSLContext_init.call(this, keyManager, TrustManagers, secureRandom); }; console.log('* Setup custom trust manager'); // okhttp3 try { var CertificatePinner = Java.use('okhttp3.CertificatePinner'); CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function (str) { console.log('! Intercepted okhttp3: ' + str); return; }; console.log('* Setup okhttp3 pinning') } catch(err) { console.log('* Unable to hook into okhttp3 pinner') } // trustkit try { var Activity = Java.use("com.datatheorem.android.trustkit.pinning.OkHostnameVerifier"); Activity.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function (str) { console.log('! Intercepted trustkit{1}: ' + str); return true; }; Activity.verify.overload('java.lang.String', 'java.security.cert.X509Certificate').implementation = function (str) { console.log('! Intercepted trustkit{2}: ' + str); return true; }; console.log('* Setup trustkit pinning') } catch(err) { console.log('* Unable to hook into trustkit pinner') } // TrustManagerImpl try { var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl'); TrustManagerImpl.verifyChain.implementation = function (untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) { console.log('! Intercepted TrustManagerImp: ' + host); return untrustedChain; } console.log('* Setup TrustManagerImpl pinning') } catch (err) { console.log('* Unable to hook into TrustManagerImpl') } // Appcelerator try { var PinningTrustManager = Java.use('appcelerator.https.PinningTrustManager'); PinningTrustManager.checkServerTrusted.implementation = function () { console.log('! Intercepted Appcelerator'); } console.log('* Setup Appcelerator pinning') } catch (err) { console.log('* Unable to hook into Appcelerator pinning') }});
Hook结果:
密钥如上图第一列,下面那一大坨就是Hook到的密文,接下来用代码解密试试看(解密方法是我直接从反编译的源码里面抠出来的~):
成了......
值得一说的是上面Hook构造函数的地方,我想Hook的是AES密钥,这个密钥是类内静态类的成员变量,如下:
Java的内部类名字一般就是OuterClass$InnerClass,所以代码应该这样写:
var SecurityKeys = Java.use("com.platform.usercenter.common.security.SecurityProtocolManager$SecurityKeys")
如果是匿名内部类,那类名一般就是OuterClass$1、$2、$3一直往下走,而我选择在构造方法执行时Hook这个成员变量的值,这时候就需要注意一点,我们在编写Hook脚本的时候,一定要调用一下它本身的构造方法,我是这样写的:
SecurityKeys.$init.implementation = function () { this.$init(); console.log("=================") console.log(this.mAES.value) console.log("=================") }
如果构造方法有参数,我们想看传入参数是啥,那就先输出参数,最后return this.$init(参数),也就是说对于构造方法类的Hook行为,我们最终一定要执行原本的init方法,否则会导致原程序无法正常运行,这时候Frida不一定会报错,但是你的目标Apk肯定没法正常执行了,我的就会显示这个:
同时,Hook到的东西是null:
觉得好看的铁铁们可以关注一哈: