低版本x音client會驗證server的證書,所以把抓包軟件的證書安裝在android系統后就可以冒充server欺騙client抓包了;但是后來的高版本libttboringssl.so中調用了SSL_CTX_set_custom_verify來設置server對client的驗證,這下抓包軟件就抓瞎了,因為不知道client的公鑰,怎么在server面前冒充client了?(當然也可以想辦法在app中找到client的公鑰或證書,然后導入charlse這種抓包軟件,但肯定不輕松)看雪有大佬通過hook或者直接硬編碼把SSL_CTX_set_custom_verify第二個參數改為0,不讓server驗證client;同時把第三個參數回調函數的返回值也改成0來繞開驗證的邏輯,這是非常好的一種思路!我這里根據tls原理、libssl.so或libttboringssl.so源碼從其他角度抓包!
1、(1)剛打開x音時,client和server要互相驗明正身,由於抓包軟件沒有client的公鑰,是沒法在server面前冒充client的,這里只能罷了!client和server通信首要就是通過handshake協商出雙方對稱加密的key,后續一律通過對稱加密通信!所以handshake階段最重要的就是這個key了!有了這個key,后續一切通信的數據不就能解開了么?這個key怎么獲取了?參考我上一篇文章https://www.cnblogs.com/theseventhson/p/16051195.html 介紹的frida腳本如下:
function startTLSKeyLogger(SSL_CTX_new, SSL_CTX_set_keylog_callback) { function keyLogger(ssl, line) { console.log(new NativePointer(line).readCString()); } const keyLogCallback = new NativeCallback(keyLogger, 'void', ['pointer', 'pointer']); Interceptor.attach(SSL_CTX_new, { onLeave: function(retval) { const ssl = new NativePointer(retval); const SSL_CTX_set_keylog_callbackFn = new NativeFunction(SSL_CTX_set_keylog_callback, 'void', ['pointer', 'pointer']); SSL_CTX_set_keylog_callbackFn(ssl, keyLogCallback); } }); } startTLSKeyLogger( Module.findExportByName('libssl.so', 'SSL_CTX_new'), Module.findExportByName('libssl.so', 'SSL_CTX_set_keylog_callback') )
我hook的結果是抓了50多個key,部分key如下:
CLIENT_RANDOM 05c10ca9bfb225e24e5fc03fa6dfef05292ce52dab90edd39b20124fd9ce64db 8ea99a617f99e59e1676f26e7266725227d8305fbb48dd36fe91e0e9133b7de4efb28edeacd52d5a06a69d41c4c0d232
CLIENT_RANDOM 75c5363ef6dd0e8ed769bb61dd3b6440d01eb1aa61b52e57be780446281e7b5d c72ba6c9fe54f643d79e330fc83130cd24f88151ea5f2ca9462577cd2585d1a72edd3132010f0bcf675f1aaf9ac5bf94
CLIENT_RANDOM a67744ecb0afb61a70b41604e7e711f56042af51266e02d79d73f28563b2de84 1ab1a45ed5407307e545a47a88a39f2c533c3e5d4effcccdf4a72b12927442fe1b6feaa38063e7ada28a9ca2553e026c
CLIENT_RANDOM 64c75bc38d438669602690369f696df57349e66d5ee784a5e22b66b37f91bff6 f7008fac9daa766edcb636be5ebccc0c3dc3d73c119f39c38f9a7e89d54a508a9da9d96ea30d5b2745639ae275eb8d79
CLIENT_RANDOM b87890d1c9e17f6741985f7e42f96569df9a1153a19c215b107c19a088bec98d 4edec3ac50b381f67125da34014408b70b4c75973db6421fca3e0fde20c859187360e2c68b5efba493ea4efab78fe062
CLIENT_RANDOM bbebae8d1ec928eb785281d56baa50b38f41dd0e6848304af06f4227d1dd1f1c 33c9be07cfd31dbe6a77dfa2f89845f8cf72d1afa196d53bfc9c4bab3757b3b187f631d592e4ac7ec01fb29b87c2a1f5
CLIENT_RANDOM a0a78b7b2a4c100337cd6af637bf40af079ec6bd4f922a3bb1ffbf9f5f4e403b 5c9dd3e00dcac4838fefc498d29d445831e0b92af4480e0dc0815d85552c9f5452bef423a5481252d95066104ef597b1
CLIENT_RANDOM c4ec4ee28686ceb7d68c0c3c1262993848271f809ab3e0824043814e9fa49683 97f2510382981b51bdc3458bda2a0df1994039648ab5b6db9e3d131898952b0574d6772c90461161cc758453ab8e4e10
CLIENT_RANDOM 03572dfae7a9c42406b00d3a17443ed085b42f6094081b6ae951ae5f5a466019 84f2b4c3b08cb3b9335ba087e334fb92f01f0310a9547d220f18604e8e3b3d3780c62d1126ad586707f5fd5e3b6a03e2
CLIENT_RANDOM a190584887037a9deef70ee793ddc8876e379aa196d5c0298216370811c74824 91e661957726600703b2ac7580e0cdce16c135f8627fc1665fa436c4a2a7ea81a10ae55e9754fbc1896e90d4971ba050
CLIENT_RANDOM f379c78bd8efb66d6774cbafa7f7e4953c3d699c24a1cda0e2cfa72e122f9a67 22169928550d7daaf002eeb317de3aae816b2e1dc1b91839fe69f2385e367e39d8723548cfb1dada605c8e6a3d4f6fed
CLIENT_RANDOM 132fd7ae00fa066077d8d99306ee4f248391567fe0cb1edc8859308074361101 4017bee954c38afc02437b472e5c6a175043696cb9587f1680aff3a0c0d5d8d4ec178171e095d4f615a273a445020e0c
key倒是有了,client和server之間通信的數據又怎么獲取了?
(2)前面提到了普通的抓包軟件抓不了包,是因為client和server在應用層互相驗證身份,導致charles、fiddler、burpsuit等應用層的抓包軟件立馬抓瞎!換個角度想:所有的數據肯定都是要通過網卡、wifi等硬件設備發送出去的,既然在應用層又驗證無法抓包,為啥不通過“降維打擊”的思路在更底層的傳輸層、ip等、鏈路層等抓包了?這里就要用到wireshark了!由於wireshark是底層抓包,所以完全不用給操作系統裝任何證書,走的也不是“中間人攻擊”的方式抓包,而是直接簡單粗暴地從網卡拿frame數據!現在網絡通信的數據確實拿到了,但應用層是加密的,這時就用到了上面的sslkey了!在wireshark的編輯->首選項->protocols->tls這里把上面hook得到的sslkey上傳,如下:
我這里的抓包結果(我用的是雷電模擬器):這個貌似是個圖片
這里不知道又從x音的cdn拿了啥:
2、上述抓包需要先得到key,再用wireshark解析,各種操作較多,使用並不方便,還有另一種抓包方法:通信雙方不是用tls協議通信么?既然數據是用key加密的,在加密前hook總可以吧? 順着這個思路去hook SSL_write(往外發送數據的)函數和SSL_read(接收外面數據)函數,還真能得到加密前的數據,我hook SSL_write部分結果如下:
(1) 這是get方法:
(2)這是post方法:
可以看出確實都是明文的!hook的腳本如下:
function SSL_hook() { if (!Java.available) { console.error('Java API not available'); return; } Java.perform(function () { console.log('hooked'); var SSL_write, SSL_read; //可查找當前已加載的共享庫的導入與導出方法 const apiResolver = new ApiResolver('module'); apiResolver.enumerateMatches('exports:*lib*ssl*!SSL_*').forEach(function (v) { if (v.name.indexOf('SSL_write') > 0) { SSL_write = v.address; console.log('SSL_write:',SSL_write); } else if (v.name.indexOf('SSL_read') > 0) { SSL_read = v.address; console.log('SSL_read:',SSL_read); } }); if (SSL_write) { Interceptor.attach(SSL_write, { onEnter: function (args) { this.ssl = args[0].toString(); this.buf = ptr(args[1]); }, onLeave: function (retval) { const len = retval.toInt32(); if (len > 0) { console.log('SSL_write\n', this.buf.readByteArray(len), '\n', '*'.repeat(120)); //send({code: 100,ssl: this.ssl}, this.buf.readByteArray(len)); // send({ // code: 100, // ssl: this.ssl // }, Memory.readByteArray(this.buf, len)); } } }); } if (SSL_read) { Interceptor.attach(SSL_read, { onEnter: function (args) { this.ssl = args[0].toString(); this.buf = ptr(args[1]); }, onLeave: function (retval) { const len = retval.toInt32(); if (len > 0) { //console.log('SSL_read\n', this.buf.readByteArray(len), '\n', '*'.repeat(120)); //send({code: 200,ssl: this.ssl}, this.buf.readByteArray(len)); // send({ // code: 200, // ssl: this.ssl // }, Memory.readByteArray(this.buf, len)); } } }); } }); } function main() { SSL_hook(); } setImmediate(main);
注意:x音不同的版本貌似調用了libssl.so和libttboringssl.so,建議這兩個不同的so都hook一下,看看都發了啥數據!
3、附上抓包要點的整理和總結如下:
參考:
1、http://www.zhuoyue360.com/crack/73.html android硬核抓包
2、https://bbs.pediy.com/thread-268014.htm 非標准sslpinning抓包
3、https://github.com/google/ssl_logger ssl_logger
4、https://github.com/BigFaceCat2017/frida_ssl_logger frida_ssl_logger
5、https://github.com/r0ysue/r0capture