Frida應(yīng)用基礎(chǔ)及APP https證書驗(yàn)證破解
一、Frida簡介
1. 基礎(chǔ)介紹
Frida是適用于開發(fā)人員、逆向工程師和安全研究人員的輕量級Hook工具,它允許將JavaScript代碼或庫注入Windows、macOS、GNU / Linux、iOS、Android和QNX上的本機(jī)應(yīng)用程序。此外, Frida還提供了一些基于Frida API構(gòu)建的簡單工具。
Frida的四大特點(diǎn):
- 可編寫腳本:將腳本注入黑盒進(jìn)程,無須源代碼即可劫持任何功能函數(shù),監(jiān)視加密API或跟蹤私有應(yīng)用程序代碼;然后編輯,點(diǎn)擊保存,即可查看結(jié)果,不需要進(jìn)行編譯或重新啟動(dòng)程序。
- 可移植的:適用于Window、macOS、GNU / Linux、iOS、Android和QNX。從npm安裝綁定的Node.js,從PyPI獲取Python包,或通過其Swift綁定、.NET綁定、Qt / Qml綁定或C API使用Frida。
- 免費(fèi)的:Frida是并且將永遠(yuǎn)是免費(fèi)軟件(免費(fèi)自由)。
- 經(jīng)過實(shí)戰(zhàn)檢驗(yàn):現(xiàn)在安全人員正在使用Frida對大規(guī)模移動(dòng)應(yīng)用進(jìn)行快速、深入的分析。 Frida擁有全面的測試套件,并經(jīng)過多年的嚴(yán)格測試,涵蓋了廣泛的用例。
Frida訪問地址:https://github.com/frida/frida。
2. 與其他Hook工具的對比
(1) Xposed的優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):在編寫Java層hook插件的時(shí)候非常好用,這一點(diǎn)完全優(yōu)越于FridaSubstrateCydia,因?yàn)樗彩茿ndroid項(xiàng)目,可以直接編寫Java代碼調(diào)用各類api進(jìn)行操作,而且可以安裝到手機(jī)上直接使用。
- 缺點(diǎn):配置安裝環(huán)境繁瑣,兼容性差,在Hook底層的時(shí)候就很無助了。
(2) Frida的優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):配置環(huán)境簡單,操作也很便捷,對于破解者來說開發(fā)階段非常好用。支持Java層和Native層的hook操作,只是在Native層hook如果是非基本類型的話操作有點(diǎn)麻煩。
- 缺點(diǎn):因?yàn)樗贿m合破解者在開發(fā)階段使用,因此它無法像Xposed那樣用于實(shí)踐。比如寫一個(gè)微信外掛,用Frida寫肯定不行,因?yàn)樗鼰o法在手機(jī)端運(yùn)行。
(3) SubstrateCydia的優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):和Xposed類似可以運(yùn)行在手機(jī)端。支持Java層和Native層的hook操作,但是Java層hook不怎么常用,用得比較多的是Native層的hook操作,因?yàn)樗彩茿ndroid工程,可以調(diào)用系統(tǒng)api,操作更為方便。
- 缺點(diǎn):和Xposed一樣安裝配置環(huán)境煩瑣,兼容性差。
以上3個(gè)工具可以說是現(xiàn)在用得最多的hook工具了,總結(jié)一句話就是,寫Java層hook還是Xposed方便,寫Native層hook適合用SubstrateCydia,而對于處在開發(fā)階段的破解者來說,則還是Frida最靠譜。
二、Frida Hook示例
1. 連接Android設(shè)備
(1)需要一臺已經(jīng)root的Android設(shè)備(可以使用Android模擬器),支持4.2~6.0版本。
(2)需要使用來自Android SDK中的adb工具連接Android設(shè)備。
(3)在https://github.com/frida/frida/releases中下載相應(yīng)的frida-server應(yīng)用,示例使用夜神Android模擬器4.4版本,下載的frida服務(wù)端是frida-server-12.2.16-android-x86.xz。
(4)啟動(dòng)夜神Android模擬器4.4版本,運(yùn)行adb devices命令,即可成功連接Android設(shè)備,如圖2.1所示。
圖2.1連接 Android 設(shè)備
(5)運(yùn)行如下命令,將frida-server復(fù)制到Android設(shè)備上,提升權(quán)限并運(yùn)行。
- $ adb push frida-server /data/local/tmp/
- $ adb shell "chmod 777 /data/local/tmp/frida-server"
- $ adb shell "/data/local/tmp/frida-server &"
(6)運(yùn)行adb shell ps命令,查看frida-server已成功運(yùn)行,如圖2.2所示。
圖2.2 Frida-Server 成功運(yùn)行
(7)運(yùn)行adb shell netstat命令,檢查frida-server監(jiān)聽端口,默認(rèn)為27042,如圖2.3所示。
圖2.3 端口監(jiān)聽成功
(8)運(yùn)行adb forward tcp:27042 tcp:27042命令,把Android設(shè)備端口轉(zhuǎn)發(fā)到PC端。
(9)運(yùn)行frida-ps –R命令,即可查詢Android設(shè)備當(dāng)前運(yùn)行進(jìn)程,至此連接Android設(shè)備成功(見圖2.4)。另外,也可以直接通過USB將Android設(shè)備與PC端連接,運(yùn)行frida-ps –U即可測試是否連接成功。
圖2.4 Android 設(shè)備連接成功檢測
2. HTTPS單向認(rèn)證強(qiáng)校驗(yàn)Hook示例
本次示例通過在本地搭建Tomcat + SSL自簽名證書環(huán)境,使用一個(gè)開源的Android HTTPS雙向認(rèn)證項(xiàng)目:
https://github.com/Frank-Zhu/AndroidHttpsDemo ,對其中部分代碼進(jìn)行修改后重新編譯,構(gòu)建本次測試的 APP應(yīng)用。
(1) 基本原理
想要繞過證書鎖定抓明文包就需要先知道APP是如何進(jìn)行鎖定操作的,然后再針對其操作進(jìn)行注入解鎖。
Android客戶端關(guān)于證書處理的邏輯按照安全等級分類,如表2.1所示。
表2.1 Android客戶端證書處理的安全等級分類
Apache http client 因?yàn)閺腶pi23起被Android拋棄,因此使用率較低。目前更多使用的是HttpURLConnection類或第三方庫OKhttp3.0進(jìn)行HTTPS通信。其中OKhttp3.0的部分運(yùn)用與HttpURLConnection相同,客戶端都可以通過實(shí)現(xiàn)X509TrustManager接口的checkServerTrusted方法,將服務(wù)器證書與APP預(yù)埋證書做對比,來完成強(qiáng)校驗(yàn)。此外,也可以再通過實(shí)現(xiàn)HostnameVerifier接口的verify方法,校驗(yàn)服務(wù)器證書中的一般名稱(CN)是否與域名相符。通過使用上述方法,完成客戶端對服務(wù)端的證書強(qiáng)校驗(yàn)。
(2) 應(yīng)用分析
由于這次是自編譯的文件,就不通過APK反編譯等方法查看代碼了,直接看主要通信類HttpClientSslHelper的源碼。
- public class HttpClientSslHelper {
- public static boolean isServerTrusted1 = true; //強(qiáng)校驗(yàn)關(guān)鍵參數(shù)
- private static SSLContext sslContext = null;
- public static OkHttpClient getSslOkHttpClient(Context context) {
- OkHttpClient.Builder builder = new OkHttpClient.Builder();
- builder.sslSocketFactory(getSslContextByCustomTrustManager(context).getSocketFactory())
- .hostnameVerifier(new HostnameVerifier() {
- @Override //實(shí)現(xiàn)自定義的HostnameVerifier 對象的verify校驗(yàn)方法
- public boolean verify(String hostname, SSLSession session) {
- Log.d("HttpClientSslHelper", "hostname = " + hostname);
- if ("192.168.96.1".equals(hostname)) {
- //根據(jù)isServerTrusted1的值進(jìn)行校驗(yàn),若為True,則校驗(yàn)成功,執(zhí)行后續(xù)連接
- return isServerTrusted1;
- } else {
- //由于是實(shí)驗(yàn)環(huán)境,if語句總為真,不會(huì)執(zhí)行else語句
- HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
- return hv.verify("localhost", session);
- }
- }
- });
- return builder.build();
- }
- public static SSLContext getSslContextByCustomTrustManager(Context context) {
- if (sslContext == null) {
- try {
- CertificateFactory cf = CertificateFactory.getInstance("X.509","BC");
- InputStream caInput = new BufferedInputStream(context.getResources().getAssets().open("server.cer"));
- final Certificate ca;
- try {
- ca = cf.generateCertificate(caInput);
- } finally {
- caInput.close();
- }
- sslContext = SSLContext.getInstance("TLS");
- //自定義X509TrustManager接口的checkClientTrusted、checkServerTrusted和getAcceptedIssuers方法
- sslContext.init(null, new X509TrustManager[]{new X509TrustManager() {
- public void checkClientTrusted(X509Certificate[] chain,
- String authType) throws CertificateException {
- Log.d("HttpClientSslHelper", "checkClientTrusted --> authType = " + authType);
- //校驗(yàn)客戶端證書
- }
- public void checkServerTrusted(X509Certificate[] chain,
- String authType) throws CertificateException {
- Log.d("HttpClientSslHelper", "checkServerTrusted --> authType = " + authType);
- //校驗(yàn)服務(wù)器證書
- for (X509Certificate cert : chain) {
- cert.checkValidity();
- try {
- //關(guān)鍵點(diǎn),用APP中內(nèi)置的服務(wù)端證書server.cer來校驗(yàn)網(wǎng)絡(luò)連接中服務(wù)器頒發(fā)的證書
- cert.verify(ca.getPublicKey());
- } catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchProviderException | SignatureException e) {
- e.printStackTrace();
- //校驗(yàn)失敗,則更改isServerTrusted1值為false
- isServerTrusted1 = false;
- }
- }
- }
- public X509Certificate[] getAcceptedIssuers() {
- return new X509Certificate[0];
- }
- }}, null);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- return sslContext;
- }
- }
由上可知,將HttpClientSslHelper類的getSslContextByCustomTrustManager方法重寫,重新實(shí)現(xiàn)checkServerTrusted方法,讓其不修改isServerTrusted1的值,即可繞過證書強(qiáng)校驗(yàn)。
(3) 開始Hook
重新實(shí)現(xiàn)checkServerTrusted方法
構(gòu)造完整腳本如下:
- import frida, sys
- def on_message(message, data):
- if message['type'] == 'send':
- print("[*] {0}".format(message['payload']))
- else:
- print(message)
- jscode = """
- Java.perform(function () {
- var HttpClientSslHelper = Java.use('com.frankzhu.androidhttpsdemo.HttpClientSslHelper');
- var Log = Java.use('android.util.Log');
- HttpClientSslHelper.getSslContextByCustomTrustManager.implementation = function () {
- var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
- var SSLContext = Java.use('javax.net.ssl.SSLContext');
- var TrustManager = Java.registerClass({
- name: 'com.frankzhu.androidhttpsdemo.test',
- implements: [X509TrustManager],
- methods: {
- checkClientTrusted: function (chain, authType) {
- },
- checkServerTrusted: function (chain, authType) {
- Log.d("Frida Hook checkServerTrusted()", "Success!!!");
- send("Frida Hook checkServerTrusted() Success!!!");
- },
- getAcceptedIssuers: function () {
- return [];
- }
- }
- });
- // Prepare the TrustManagers array to pass to HttpClientSslHelper.sslContext.init()
- var TrustManagers = [TrustManager.$new()];
- send("Custom, Empty TrustManager ready");
- // Override the init method, specifying our new TrustManager
- var sslContext = SSLContext.getInstance("TLS");
- sslContext.init(null, TrustManagers, null);
- //return的值類型必須與原來的相同,否則會(huì)出現(xiàn)Error: Implementation for getSslContextByCustomTrustManager expected return value compatible with 'javax.net.ssl.SSLContext',同時(shí)導(dǎo)致應(yīng)用崩潰
- //源碼里有private static SSLContext sslContext = null;如果想通過this.sslContext使用該變量,一定要注意Hook的時(shí)機(jī),要在sslContext變?yōu)閷ο蠛笤貶ook,這樣就不會(huì)出現(xiàn)應(yīng)用異常崩潰
- return sslContext;
- }
- });
- """
- process = frida.get_remote_device().attach('com.frankzhu.androidhttpsdemo')
- script = process.create_script(jscode)
- script.on('message', on_message)
- script.load()
- sys.stdin.read()
測試步驟:
- 使用Burpsuite,設(shè)置PC端和Android端的代理;
- 打開測試APP,運(yùn)行上述Python腳本;
- 點(diǎn)擊APP網(wǎng)絡(luò)測試按鈕,發(fā)送網(wǎng)絡(luò)請求(一定要在第一次點(diǎn)擊按鈕前,運(yùn)行Python腳本)。
未用Frida Hook的結(jié)果如圖2.5、圖2.6所示。
圖2.5 連接報(bào)錯(cuò)
圖2.6 HTTPS 抓包失敗
使用Frida Hook后的結(jié)果如圖2.7~圖2.9所示。
圖2.7 腳本運(yùn)行成功
圖2.8 運(yùn)行日志
圖2.9 HTTPS 證書驗(yàn)證破解成功
【本文為51CTO專欄作者“安全加”原創(chuàng)稿件,轉(zhuǎn)載請聯(lián)系原作者】