對app的反爬測試之apk逆向分析-frida繞過ssl pinning檢測


前言:

 

受人所托,需要對他們的產品進行反爬測試,所以就有了以下內容。

 

不過,我知道,針對這方面的文章太多了,是真的多,而且好早就有了,但是目前為止,很多app的防護基本也還是用的ssl pinning檢測證書。

因為,目前的app要嘛不用ssl,要嘛用就是一般的ssl,基本就是在手機上裝個相關軟件 的代理即可,而且這個代理基本就是fiddler,charlels,burpsuite,mitmproxy(Python環境下的)四個抓包軟件自帶的ssl證書,然后即可抓到ssl(https)的請求

以上這些,基本可以解決大部分的app(其實很多使用ssl的網站也是這樣處理)

 

但是因為很多app為了防止數據被分析爬取,會做ssl pinning驗證

 

ssl painning

 

SSL Pinning是一種防止中間人攻擊(MITM)的技術,主要機制是在客戶端發起請求–>收到服務器發來的證書進行校驗,如果收到的證書不被客戶端信任,就直接斷開連接不繼續求情。

所以在遇到對關鍵請求開啟SSL Pinning的APP時,我們抓包就只能看到APP上提示無法連接網絡或者請求失敗之類的提示;而在抓包工具上面,要么就只能看到一排 CONNECT 請求,獲取到證書卻沒有后續了,要么就是一些無關的請求,找不到想要的接口

 

比如如下圖:

 

 

 

針對這種,如果是web網站,我們都知道,在本地裝下抓包軟件自帶的ssl證書就行了,但是app的話,如此操作之后還是不行,而且app還會提示沒網(比如:網絡連接失敗,網絡有問題等等的),反正意思就是沒網的意思,這種就是因為app自身做了ssl pinning驗證處理,驗證下當前的ssl證書是否是合法允許的,如果不是就會驗證失敗

 

其實使用ssl pinning目前已經成為了趨勢,那么我們的目前對象剛好就有這個怎么辦呢?

 

目前根據我的經驗,最有效的有三個方法:

 

  • 1.使用低版本的安卓機抓包
  • 2.使用ios端手機抓包
  • 3.使用frida繞過證書處理

 

使用低版本的安卓機抓包

 

因為app的話,目前主流的都是用的前后端分離開發,所以越到后期,app更新新版后,越會有不同版本的后端接口存在,而且新版接口和老版接口其實返回的數據差異性很小,並且有個關鍵點就是,為了兼容性,會依舊留下舊版接口,因為每個用戶使用的手機不一樣,安卓或者ios版本不同,系統版本也就會不同,且老款手機因為內存太小,不會更新新版的app包,種種情況下來,結果就是會留下舊版接口,而且這個舊版接口安全性比新版低很多,所以可以用低版本的餓安卓機來抓包,就正常的抓包流程即可,不出意外的話,可能還用的普通的http請求。

為什么高版本的安卓就抓不到包呢,因為高版本的(安卓7以上)開始,安卓自帶了一些安全機制,本質上就是只信任系統證書,不再信任用戶自己安裝的證書了,我們裝的ssl代理證書就是自己裝的,所以就會驗證不通過了

 

使用ios端手機抓包

 

這個情況真的很多,因為,蘋果端的appstore管理得很嚴,不能加些自己獨特的東西,但是加ssl是可以的,但是很多app並沒有加我就不知道了,這個情況就很簡單,需要一台iphone,其他都是正常抓包操作,然后安裝證書,把證書信任下就行了,詳細的操作就不說了,網上很多教程

 

 

使用frida繞過證書處理

 

 

這個方法就是本篇文章的重點了,這個放到后面再說

 

 

其他方法

其實也有其他的方法,這些方法並不是通用的,可能運氣好可以用,運氣不好就沒用:

 

安卓模擬器

 

用安卓模擬器,模擬低版本安卓然后抓包

 

對證書信任,修改APP設置

 

看這個app是否是自有app,如果是自有的,谷歌有debug模式,該模式下讓app默認可以信任用戶域的證書(trust-anchors),如果是非自有,用xposed+JustTrustMe即可,但是使用Xposed框架需要root,網上那些微信魔改小功能,什么自動搶紅包,防消息撤回之類的就是用的xposed框架改的,用JustTrustMe來信任用戶安裝的證書

目前市面上有VitualXposed、太極等虛擬的框架,不用root也可以操作,太極這個軟件挺好的,有太極-陰(免root)和太極-陽(需要root),兩個版本都可以用,但是針對有些app的話,太極-陰沒戲,只能太極-陽,但是既然我都已經root了,我就沒必要整這些了。

 

 

如果是 App 的開發者或者把 apk 逆向出來了,那么可以直接通過修改 AndroidManifest.xml 文件,在 apk 里面添加證書的信任規則即可,詳情可以參考 https://crifan.github.io/app_capture_package_tool_charles/website/how_capture_app/complex_https/https_ssl_pinning/,這種思路屬於第一種信任證書的解決方案。

 

 

強制信任證書

 

其實就是將證書設置為系統證書,只需要將抓包軟件的證書設置為系統區域即可。但這個前提是手機必須要 ROOT,而且需要計算證書 Hash Code 並對證書進行重命名,具體可以參考 https://crifan.github.io/app_capture_package_tool_charles/website/how_capture_app/complex_https/https_ssl_pinning, 把ssl代理證書強制的放到安卓機的/system/etc/security/cacerts/目錄下,這個目錄就是安卓機系統信任的目錄。

 

具體步驟:

 

 

1.charles導出.pem證書,選擇.prm類型 保存在pc上

 

 

2.修改證書名稱


系統證書目錄:/system/etc/security/cacerts/

 

 

其中的每個證書的命名規則如下:
<Certificate_Hash>.
文件名是一個Hash值,而后綴是一個數字。

 

文件名可以用下面的命令計算出來:
openssl x509 -subject_hash_old -in <Certificate_File>

 

后綴名的數字是為了防止文件名沖突的,比如如果兩個證書算出的Hash值是一樣的話,那么一個證書的后綴名數字可以設置成0,而另一個證書的后綴名數字可以設置成1

 

 

3. 用adb命令把證書推到手機上


adb push xxxxxxx.0 /sdcard/

 

 

4.復制到系統目錄並修改權限(安卓8.1.0 Magisk Root)

 

mount -o rw,remount /system 【不修改 沒法寫入】
mount -o rw,remount /

 

mv /sdcard/xxxxxxx.0 /etc/security/cacerts/ 移動文件到系統
chown root:root /etc/security/cacerts/fc365f9d.0 修改用戶組
chmod 644 /system/etc/security/cacerts/xxxxxxx.0 修改權限

 

 

5. 重啟手機驗證即可

這時你就發現證書已經在系統級別里了

 

6.進行抓包

 

2021-11-09補充:

安卓10版本有更高的保護機制,即使root了也沒法把證書導入到系統證書目錄里,解決方法: https://blog.csdn.net/fjh1997/article/details/106756012 

 

httpcannary

這個是安卓端的抓包工具,網上吹得很火,根據我(我手機是安卓10)親自操作,發現其實沒有用,也不知道是不是我的姿勢錯誤,或者我手機安卓系統版本太高了失效

 

 

VirtualApp

 

用這個可以免root操作,然后正常抓包,但是這個方法我沒有實際操作過,網上的資料不多,自行查找

 

 

 

Xposed + JustTrustMe

 

 

 

Xposed 是一款 Android 端的 Hook 工具,利用它我們可以 Hook App 里面的關鍵方法的執行邏輯,繞過 HTTPS 的證書校驗過程。JustTrustMe 是基於 Xposed 一個插件,它可以將 HTTPS 證書校驗的部分進行 Hook,改寫其中的證書校驗邏輯,這種思路是屬於第二種繞過 HTTPS 證書校驗的解決方案。

 

 

 

當然基於 Xposed 的類似插件也有很多,如 SSLKiller、sslunpining 等等,可以自行搜索。 

 

不過 Xposed 的安裝必須要 ROOT,如果不想 ROOT 的話,可以使用后文介紹的 VirtualXposed。

 

具體可以參考 https://codeshare.frida.re/@pcipolloni/universal-android-ssl-pinning-bypass-with-frida/

 

 

VirtualXposed

 

Xposed 的使用需要 ROOT,如果不想 ROOT 的話,可以直接使用一款基於 VirtualApp 開發的 VirtualXposed 工具,它提供了一個虛擬環境,內置了 Xposed。我們只需要將想要的軟件安裝到 VirtualXposed 里面就能使用 Xposed 的功能了,然后配合 JustTrustMe 插件也能解決 SSL Pining 的問題,這種思路是屬於第二種繞過 HTTPS 證書校驗的解決方案。

 

特殊改寫

  

其實本質上是對一些關鍵的校驗方法進行了 Hook 和改寫,去除了一些校驗邏輯。但是對於一些代碼混淆后的 App 來說,其校驗 HTTPS 證書的方法名直接變了,那么 JustTrustMe 這樣的插件就無法 Hook 這些方法,因此也就無效了。

 

 

強制全局代理

 

手機root后,使用proxy Droid 實現強制全局代理,讓ssl代理證書生效,proxy Droid可以在UpToDown,ApkHere等的地方下載

 

VPN抓包

免root,在安卓機上安裝packet capture,然后抓包,我試了下,我的手機(我手機是安卓10)沒用

 

魔改JustTrustMe

在JustTrustMe插件上增加一個可以運行時根據實際情況調整ssl檢測的功能,對hook增加動態適配,這個方法我沒試過,我在看雪論壇里找到一個 JustTrustMePlus,點我下載

 

反編譯app包

用apktools修改配置文件里的ssl證書檢測部分,可利用jadx等工具分析源碼,然后重新打包,再抓包分析,這個方法是可行的,詳細的步驟自行百度吧,后續有時間的話,我單獨發一篇對app的脫殼重新打包

 

 

AndServer處理

這個工具的原理就是把一個安卓機在本地作為一台服務器,然后找到數據接口,這個方法沒有親測過,更多的適用於獲取app的sign/token時去獲取接口

  

以上的方法就是我所知道的方法,各位朋友自行操作

 

 

接下來進入正題,frida hook

 

什么是frida

 

官網:https://frida.re/

Frida是個輕量級別的hook框架, 是Python API,用JavaScript調試來邏輯

Frida的核心是用C編寫的,並將Google的V8引擎注入到目標進程中,在這些進程中,JS可以完全訪問內存,掛鈎函數甚至調用進程內的本機函數來執行。

使用Python和JS可以使用無風險的API進行快速開發。Frida可以幫助您輕松捕獲JS中的錯誤並為您提供異常而不是崩潰。

 

frida是平台原生app的Greasemonkey,說的專業一點,就是一種動態插樁工具,可以插入一些代碼到原生app的內存空間去,(動態地監視和修改其行為),這些原生平台可以是Win、Mac、Linux、Android或者iOS。而且frida還是開源的。

Greasemonkey可能大家不明白,它其實就是firefox的一套插件體系,使用它編寫的腳本可以直接改變firefox對網頁的編排方式,實現想要的任何功能。而且這套插件還是外掛的,非常靈活機動。

 

 

frida框架主要分為兩部分:
1)一部分是運行在系統上的交互工具frida CLI。
2)另一部分是運行在目標機器上的代碼注入工具 frida-server

 

 

注:以下相關操作,終端里凡是 C:\Users\Administrator 開頭的都是在pc機上操作的,需要在安卓機目錄里操作的我都有說明,不要搞混了

 

環境准備

 

安裝frida

沒有python的安裝python,然后安裝frida:

pip  install frida

pip install frida-tools

 

安裝過程很慢,這個只能耐心等待,然后如果你是macbook的話,如果你遇到安裝出錯,可以看看我這篇文章的解決方法 macos 安裝frida的坑

然后frida是mac,linux,windows都可以安裝使用的,這個根據你自己的條件選擇了

安裝adb

這個就很簡單,去 安卓開發網  然后下載這個工具:

 

 

 

  

 

 如果你下載太慢可以在我這里下載:點我

下載完畢后,解壓,然后放到你想放的路徑,然后配置下環境變量即可,此電腦(我的電腦)- 屬性-高級系統設置-環境變量-系統變量的path,新增即可

 

 

 

然后,打開終端:

 

 敲adb,回車,如果有以下提示,說明你adb安裝成功

 

 

 

 

 

以上配置是windows平台,如果是其他平台的話,自行查找,這里就不展示了

 

找一個安卓機(已root)

根據現在的行情,要找到一個已root的手機,問題不大也不小,但是很多時候沒有必要,所以我這里就選擇用安卓模擬器來輔助操作了

安裝夜神模擬器,夜神默認是安卓5,你可以自行選擇安卓版本,在夜神里設置已root即可

 

 

 

打開開發者選項里的USB調試

 

設置里面,關於本機,然后狂點系統版本號,開啟開發者模式:

 

 

 

返回,會多一個開發者選項:

 

 

打開調試

 

 

 

adb連接安卓機(模擬器)

 

在安裝了frida和adb的真機操作系統下,打開終端,用 adb connect IP 連接安卓機:

 

夜神的ip是127.0.0.1:62001,這里注意,如果你創建了多個安卓系統的話,那么你待連接的安卓機不一定是62001,可能是其他的,可以在安裝目錄里面找

 

 

 

 

 

 

進入后,找nox.vbox文件

 

 

 用文本編輯器打開,搜索5555就能看到是哪個端口了,為什么必須是5555端口呢,因為5555就是模擬器掛載在我們windows真機上的端口

 

 

 

 

 

 

 

 

 

用 adb connect 127.0.0.1:端口   連接

 

 

 

我這里已經連接上了,所以提示已連接

 

連接之后可以用 adb devices查看已連接的機器:

 

 

安裝frida-server

frida-server這個需要安裝在安卓機上,但是安卓機我們都知道有很多個版本,對應架構才行,要查看當前安卓機的架構:

adb shell getprop ro.product.cpu.abi

 

 

 

 

 

 

然后去這里下載對應架構的frida-server :  點我

 

我這里是x86,安卓,所以選下面我選中那個下載,你的安卓機是什么你就選哪個就行了

 

 

 

然后下載很慢,我這里也提供了,點我下載  

 

但是,一定注意,pip安裝的frida版本一定要跟去frida官網下載的frida-server版本對應上,不然連不上

 

解壓,然后用adb 傳到安卓機上

adb push (本機的frida-sever文件所在目錄) (安卓機目錄)

  

 

 

 

這里提示太長了,看不出來,可以用adb shell 去那個目錄下看下是否有frida-server即可:

 

 

 

 

修改frida-server的權限:

chmod 700 frida-server

  

 

 

 

 

下載一個frida hook 的js文件

 

這個文件,有好幾個版本,我選用了兩個版本,放到下面,你們自己選擇吧

 

版本1:

 

setTimeout(function(){
    Java.perform(function (){
    	console.log("");
	    console.log("[.] Cert Pinning Bypass/Re-Pinning");

	    var CertificateFactory = Java.use("java.security.cert.CertificateFactory");
	    var FileInputStream = Java.use("java.io.FileInputStream");
	    var BufferedInputStream = Java.use("java.io.BufferedInputStream");
	    var X509Certificate = Java.use("java.security.cert.X509Certificate");
	    var KeyStore = Java.use("java.security.KeyStore");
	    var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory");
	    var SSLContext = Java.use("javax.net.ssl.SSLContext");

	    // Load CAs from an InputStream
	    console.log("[+] Loading our CA...")
	    cf = CertificateFactory.getInstance("X.509");
	    
	    try {
	    	var fileInputStream = FileInputStream.$new("/data/local/tmp/cert-der.crt");
	    }
	    catch(err) {
	    	console.log("[o] " + err);
	    }
	    var bufferedInputStream = BufferedInputStream.$new(fileInputStream);
	  	var ca = cf.generateCertificate(bufferedInputStream);
	    bufferedInputStream.close();

		var certInfo = Java.cast(ca, X509Certificate);
	    console.log("[o] Our CA Info: " + certInfo.getSubjectDN());

	    // Create a KeyStore containing our trusted CAs
	    console.log("[+] Creating a KeyStore for our CA...");
	    var keyStoreType = KeyStore.getDefaultType();
	    var keyStore = KeyStore.getInstance(keyStoreType);
	    keyStore.load(null, null);
	    keyStore.setCertificateEntry("ca", ca);
	    
	    // Create a TrustManager that trusts the CAs in our KeyStore
	    console.log("[+] Creating a TrustManager that trusts the CA in our KeyStore...");
	    var tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
	    var tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
	    tmf.init(keyStore);
	    console.log("[+] Our TrustManager is ready...");

	    console.log("[+] Hijacking SSLContext methods now...")
	    console.log("[-] Waiting for the app to invoke SSLContext.init()...")

	   	SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").implementation = function(a,b,c) {
	   		console.log("[o] App invoked javax.net.ssl.SSLContext.init...");
	   		SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").call(this, a, tmf.getTrustManagers(), c);
	   		console.log("[+] SSLContext initialized with our custom TrustManager!");
	   	}
    });
},0);

  

版本2:

Java.perform(function() {
    var array_list = Java.use("java.util.ArrayList");
    var ApiClient = Java.use('com.android.org.conscrypt.TrustManagerImpl');

    ApiClient.checkTrustedRecursive.implementation = function(a1, a2, a3, a4, a5, a6) {
        // console.log('Bypassing SSL Pinning');
        var k = array_list.$new();
        return k; 
        }
}, 0);

  

然后你自己復制以上的任何一個版本的代碼,然后在本地新建一個js文件,粘貼進去就行了

 

 

 

 

 

 

或者這個:

 

這里補充一個完全版:

 

Java.perform(function() {

/*
hook list:
1.SSLcontext
2.okhttp
3.webview
4.XUtils
5.httpclientandroidlib
6.JSSE
7.network\_security\_config (android 7.0+)
8.Apache Http client (support partly)
9.OpenSSLSocketImpl
10.TrustKit
11.Cronet
*/

	// Attempts to bypass SSL pinning implementations in a number of
	// ways. These include implementing a new TrustManager that will
	// accept any SSL certificate, overriding OkHTTP v3 check()
	// method etc.
	var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
	var HostnameVerifier = Java.use('javax.net.ssl.HostnameVerifier');
	var SSLContext = Java.use('javax.net.ssl.SSLContext');
	var quiet_output = false;

	// Helper method to honor the quiet flag.

	function quiet_send(data) {

		if (quiet_output) {

			return;
		}

		send(data)
	}


	// Implement a new TrustManager
	// ref: https://gist.github.com/oleavr/3ca67a173ff7d207c6b8c3b0ca65a9d8
	// Java.registerClass() is only supported on ART for now(201803). 所以android 4.4以下不兼容,4.4要切換成ART使用.
	/*
06-07 16:15:38.541 27021-27073/mi.sslpinningdemo W/System.err: java.lang.IllegalArgumentException: Required method checkServerTrusted(X509Certificate[], String, String, String) missing
06-07 16:15:38.542 27021-27073/mi.sslpinningdemo W/System.err:     at android.net.http.X509TrustManagerExtensions.<init>(X509TrustManagerExtensions.java:73)
        at mi.ssl.MiPinningTrustManger.<init>(MiPinningTrustManger.java:61)
06-07 16:15:38.543 27021-27073/mi.sslpinningdemo W/System.err:     at mi.sslpinningdemo.OkHttpUtil.getSecPinningClient(OkHttpUtil.java:112)
        at mi.sslpinningdemo.OkHttpUtil.get(OkHttpUtil.java:62)
        at mi.sslpinningdemo.MainActivity$1$1.run(MainActivity.java:36)
*/
	var X509Certificate = Java.use("java.security.cert.X509Certificate");
	var TrustManager;
	try {
		TrustManager = Java.registerClass({
			name: 'org.wooyun.TrustManager',
			implements: [X509TrustManager],
			methods: {
				checkClientTrusted: function(chain, authType) {},
				checkServerTrusted: function(chain, authType) {},
				getAcceptedIssuers: function() {
					// var certs = [X509Certificate.$new()];
					// return certs;
					return [];
				}
			}
		});
	} catch (e) {
		quiet_send("registerClass from X509TrustManager >>>>>>>> " + e.message);
	}





	// Prepare the TrustManagers array to pass to SSLContext.init()
	var TrustManagers = [TrustManager.$new()];

	try {
		// Prepare a Empty SSLFactory
		var TLS_SSLContext = SSLContext.getInstance("TLS");
		TLS_SSLContext.init(null, TrustManagers, null);
		var EmptySSLFactory = TLS_SSLContext.getSocketFactory();
	} catch (e) {
		quiet_send(e.message);
	}

	send('Custom, Empty TrustManager ready');

	// Get a handle on the init() on the SSLContext class
	var SSLContext_init = SSLContext.init.overload(
		'[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom');

	// Override the init method, specifying our new TrustManager
	SSLContext_init.implementation = function(keyManager, trustManager, secureRandom) {

		quiet_send('Overriding SSLContext.init() with the custom TrustManager');

		SSLContext_init.call(this, null, TrustManagers, null);
	};

	/*** okhttp3.x unpinning ***/


	// Wrap the logic in a try/catch as not all applications will have
	// okhttp as part of the app.
	try {

		var CertificatePinner = Java.use('okhttp3.CertificatePinner');

		quiet_send('OkHTTP 3.x Found');

		CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function() {

			quiet_send('OkHTTP 3.x check() called. Not throwing an exception.');
		}

	} catch (err) {

		// If we dont have a ClassNotFoundException exception, raise the
		// problem encountered.
		if (err.message.indexOf('ClassNotFoundException') === 0) {

			throw new Error(err);
		}
	}

	// Appcelerator Titanium PinningTrustManager

	// Wrap the logic in a try/catch as not all applications will have
	// appcelerator as part of the app.
	try {

		var PinningTrustManager = Java.use('appcelerator.https.PinningTrustManager');

		send('Appcelerator Titanium Found');

		PinningTrustManager.checkServerTrusted.implementation = function() {

			quiet_send('Appcelerator checkServerTrusted() called. Not throwing an exception.');
		}

	} catch (err) {

		// If we dont have a ClassNotFoundException exception, raise the
		// problem encountered.
		if (err.message.indexOf('ClassNotFoundException') === 0) {

			throw new Error(err);
		}
	}

	/*** okhttp unpinning ***/


	try {
		var OkHttpClient = Java.use("com.squareup.okhttp.OkHttpClient");
		OkHttpClient.setCertificatePinner.implementation = function(certificatePinner) {
			// do nothing
			quiet_send("OkHttpClient.setCertificatePinner Called!");
			return this;
		};

		// Invalidate the certificate pinnet checks (if "setCertificatePinner" was called before the previous invalidation)
		var CertificatePinner = Java.use("com.squareup.okhttp.CertificatePinner");
		CertificatePinner.check.overload('java.lang.String', '[Ljava.security.cert.Certificate;').implementation = function(p0, p1) {
			// do nothing
			quiet_send("okhttp Called! [Certificate]");
			return;
		};
		CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function(p0, p1) {
			// do nothing
			quiet_send("okhttp Called! [List]");
			return;
		};
	} catch (e) {
		quiet_send("com.squareup.okhttp not found");
	}

	/*** WebView Hooks ***/

	/* frameworks/base/core/java/android/webkit/WebViewClient.java */
	/* public void onReceivedSslError(Webview, SslErrorHandler, SslError) */
	var WebViewClient = Java.use("android.webkit.WebViewClient");

	WebViewClient.onReceivedSslError.implementation = function(webView, sslErrorHandler, sslError) {
		quiet_send("WebViewClient onReceivedSslError invoke");
		//執行proceed方法
		sslErrorHandler.proceed();
		return;
	};

	WebViewClient.onReceivedError.overload('android.webkit.WebView', 'int', 'java.lang.String', 'java.lang.String').implementation = function(a, b, c, d) {
		quiet_send("WebViewClient onReceivedError invoked");
		return;
	};

	WebViewClient.onReceivedError.overload('android.webkit.WebView', 'android.webkit.WebResourceRequest', 'android.webkit.WebResourceError').implementation = function() {
		quiet_send("WebViewClient onReceivedError invoked");
		return;
	};

	/*** JSSE Hooks ***/

	/* libcore/luni/src/main/java/javax/net/ssl/TrustManagerFactory.java */
	/* public final TrustManager[] getTrustManager() */
	/* TrustManagerFactory.getTrustManagers maybe cause X509TrustManagerExtensions error  */
	// var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory");
	// TrustManagerFactory.getTrustManagers.implementation = function(){
	//     quiet_send("TrustManagerFactory getTrustManagers invoked");
	//     return TrustManagers;
	// }

	var HttpsURLConnection = Java.use("javax.net.ssl.HttpsURLConnection");
	/* libcore/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java */
	/* public void setDefaultHostnameVerifier(HostnameVerifier) */
	HttpsURLConnection.setDefaultHostnameVerifier.implementation = function(hostnameVerifier) {
		quiet_send("HttpsURLConnection.setDefaultHostnameVerifier invoked");
		return null;
	};
	/* libcore/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java */
	/* public void setSSLSocketFactory(SSLSocketFactory) */
	HttpsURLConnection.setSSLSocketFactory.implementation = function(SSLSocketFactory) {
		quiet_send("HttpsURLConnection.setSSLSocketFactory invoked");
		return null;
	};
	/* libcore/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java */
	/* public void setHostnameVerifier(HostnameVerifier) */
	HttpsURLConnection.setHostnameVerifier.implementation = function(hostnameVerifier) {
		quiet_send("HttpsURLConnection.setHostnameVerifier invoked");
		return null;
	};

	/*** Xutils3.x hooks ***/
	//Implement a new HostnameVerifier
	var TrustHostnameVerifier;
	try {
		TrustHostnameVerifier = Java.registerClass({
			name: 'org.wooyun.TrustHostnameVerifier',
			implements: [HostnameVerifier],
			method: {
				verify: function(hostname, session) {
					return true;
				}
			}
		});

	} catch (e) {
		//java.lang.ClassNotFoundException: Didn't find class "org.wooyun.TrustHostnameVerifier"
		quiet_send("registerClass from hostnameVerifier >>>>>>>> " + e.message);
	}

	try {
		var RequestParams = Java.use('org.xutils.http.RequestParams');
		RequestParams.setSslSocketFactory.implementation = function(sslSocketFactory) {
			sslSocketFactory = EmptySSLFactory;
			return null;
		}

		RequestParams.setHostnameVerifier.implementation = function(hostnameVerifier) {
			hostnameVerifier = TrustHostnameVerifier.$new();
			return null;
		}

	} catch (e) {
		quiet_send("Xutils hooks not Found");
	}

	/*** httpclientandroidlib Hooks ***/
	try {
		var AbstractVerifier = Java.use("ch.boye.httpclientandroidlib.conn.ssl.AbstractVerifier");
		AbstractVerifier.verify.overload('java.lang.String', '[Ljava.lang.String', '[Ljava.lang.String', 'boolean').implementation = function() {
			quiet_send("httpclientandroidlib Hooks");
			return null;
		}
	} catch (e) {
		quiet_send("httpclientandroidlib Hooks not found");
	}

	/***
android 7.0+ network_security_config TrustManagerImpl hook
apache httpclient partly
***/
	var TrustManagerImpl = Java.use("com.android.org.conscrypt.TrustManagerImpl");
	// try {
	//     var Arrays = Java.use("java.util.Arrays");
	//     //apache http client pinning maybe baypass
	//     //https://github.com/google/conscrypt/blob/c88f9f55a523f128f0e4dace76a34724bfa1e88c/platform/src/main/java/org/conscrypt/TrustManagerImpl.java#471
	//     TrustManagerImpl.checkTrusted.implementation = function (chain, authType, session, parameters, authType) {
	//         quiet_send("TrustManagerImpl checkTrusted called");
	//         //Generics currently result in java.lang.Object
	//         return Arrays.asList(chain);
	//     }
	//
	// } catch (e) {
	//     quiet_send("TrustManagerImpl checkTrusted nout found");
	// }

	try {
		// Android 7+ TrustManagerImpl
		TrustManagerImpl.verifyChain.implementation = function(untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) {
			quiet_send("TrustManagerImpl verifyChain called");
			// Skip all the logic and just return the chain again :P
			//https://www.nccgroup.trust/uk/about-us/newsroom-and-events/blogs/2017/november/bypassing-androids-network-security-configuration/
			// https://github.com/google/conscrypt/blob/c88f9f55a523f128f0e4dace76a34724bfa1e88c/platform/src/main/java/org/conscrypt/TrustManagerImpl.java#L650
			return untrustedChain;
		}
	} catch (e) {
		quiet_send("TrustManagerImpl verifyChain nout found below 7.0");
	}
	// OpenSSLSocketImpl
	try {
		var OpenSSLSocketImpl = Java.use('com.android.org.conscrypt.OpenSSLSocketImpl');
		OpenSSLSocketImpl.verifyCertificateChain.implementation = function(certRefs, authMethod) {
			quiet_send('OpenSSLSocketImpl.verifyCertificateChain');
		}

		quiet_send('OpenSSLSocketImpl pinning')
	} catch (err) {
		quiet_send('OpenSSLSocketImpl pinner not found');
	}
	// 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) {
			quiet_send('Trustkit.verify1: ' + str);
			return true;
		};
		Activity.verify.overload('java.lang.String', 'java.security.cert.X509Certificate').implementation = function(str) {
			quiet_send('Trustkit.verify2: ' + str);
			return true;
		};

		quiet_send('Trustkit pinning')
	} catch (err) {
		quiet_send('Trustkit pinner not found')
	}

	try {
		//cronet pinner hook
		//weibo don't invoke

		var netBuilder = Java.use("org.chromium.net.CronetEngine$Builder");

		//https://developer.android.com/guide/topics/connectivity/cronet/reference/org/chromium/net/CronetEngine.Builder.html#enablePublicKeyPinningBypassForLocalTrustAnchors(boolean)
		netBuilder.enablePublicKeyPinningBypassForLocalTrustAnchors.implementation = function(arg) {

			//weibo not invoke
			console.log("Enables or disables public key pinning bypass for local trust anchors = " + arg);

			//true to enable the bypass, false to disable.
			var ret = netBuilder.enablePublicKeyPinningBypassForLocalTrustAnchors.call(this, true);
			return ret;
		};

		netBuilder.addPublicKeyPins.implementation = function(hostName, pinsSha256, includeSubdomains, expirationDate) {
			console.log("cronet addPublicKeyPins hostName = " + hostName);

			//var ret = netBuilder.addPublicKeyPins.call(this,hostName, pinsSha256,includeSubdomains, expirationDate);
			//this 是調用 addPublicKeyPins 前的對象嗎? Yes,CronetEngine.Builder
			return this;
		};

	} catch (err) {
		console.log('[-] Cronet pinner not found')
	}
});

 

上面這個完全版本包含了如下功能,如果你想一步到位的話,就可以用這個完全版

 

  • SSLcontext(ART only)
  • okhttp
  • webview
  • XUtils(ART only)
  • httpclientandroidlib
  • JSSE
  • network_security_config (android 7.0+)
  • Apache Http client (support partly)
  • OpenSSLSocketImpl
  • TrustKit
  • Cronet 

 

任意一個都可以,不要三個都用,都用也沒用,根據實際情況選用

 

安卓機配置代理

 

配置代理到開啟了抓包工具的IP上:

 

 

 

長按wiredssid

 

 

 

 

 

 

 

 

 

 

 

 補充一句,當配置完代理后,pc端電腦上一定要打開對應的抓包軟件,不然安卓機會沒網

 

 

 

安卓機上安裝ssl證書

 

 

根據你選用的抓包工具,fiddler,charles,burpsuite,安裝證書即可,你可以訪問局域網下帶的ip來下載,然后安裝:

 

配置了代理再執行此步驟,不然打不開下載證書的局域網址

 

 

 

 

也可以用adb 像傳frida-server一樣,用adb push把證書push到安卓機上,然后在安卓機的設置-安全里本地導入證書:

 

 

 

 

 用adb push 之后,還是把代理配置上,不然后面操作也無法繼續,不管怎么操作,反正必須要ssl證書安裝上即可

 

 

 

開始hook

 

hook的本質意思就是鈎子,在開發里面通俗的說就是可以在任意流程里插一手,然后做些手腳,比如打開一個app,在啟動到完全打開app,顯示app的首頁,這個過程就可以hook一下,比如把本來要打開首頁的,改成打開第二頁數據,當然這只是舉個例子

 

 

啟動frida-server:

/data/local/tmp/frida-server

 

補充下,有的高級點的app會檢測本地是否啟動了frida-server的程序,以及監聽是否開啟了27042端口,所以,如果有反調試的話,建議將frida-server改個自定義的名字,比如fsx86之類的,反正就是別讓app檢測到,然后啟動:

 

/data/local/tmp/fsx86 -l 0.0.0.0:6666  (6666就是自定義端口)

  

 

這里要用絕對路徑來啟動,我也不知道為啥,啟動,如下,warning是個警告,無所謂,說明啟動成功了,只要沒報錯就行了

 

 

映射端口

 

在pc端電腦,裝adb的機器上使用如下命令映射端口

 

 

adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043

 

  

 

 

找到需要hook的app包名

 

 

這個包名不是app的名字,是安裝之后存在目錄里的文件夾名,一般是com.xxxx.xxx之類的,但是有少部分奇葩的報名並不是com開頭

查看當前所有的包名:

frida-ps -U

 

 

 

 注意要安卓機里先啟動了firda-server,然后adb連上了安卓機,才可以調用frida命令, 如果不啟動的話,運行frida這樣,Failed,失敗的意思

 

 

 

 

 

 

 

以上查看app包,顯示出來太多了,你根本不知道哪個才是我們需要的包名,可以使用下面的命令查看

adb shell pm list packages:打印設備上的所有軟件包

adb shell pm list packages -f:輸出包和包相關聯的文件

adb shell pm list packages -d:只輸出禁用的包由於本機禁用沒有,輸出為空

adb shell pm list packages -e:只輸出啟用的包

adb shell pm list packages -s:只輸出系統的包

adb shell pm list packages -3:只輸出第三方的包

adb shell pm list packages -i:只輸出包和安裝信息(安裝來源)

adb shell pm list packages -u:只輸出包和未安裝包信息(安裝來源)

adb shell pm list packages --user <USER_ID>:根據用戶id查詢用戶的空間的所有包,USER_ID代表當前連接設備的順序,從零開始

  

如果還找不到,可以先在安卓機上啟動了目標app后,再用命令查看:

adb shell "dumpsys window | grep mCurrentFocus"

  

 

 

 

hook操作

frida -U -f (app包名) -l  (js目錄)  --no-pause

  

 

 

 

 

 

注意了,這段js是放在安裝了frida和adb的電腦上,不是放在安卓機上

 

運行完這條命令,安卓機會自動打開目標app,

 

app打開界面我就不展示了

 

如果打開的就是我們預期的那個app,那就是對的,如果打開錯了,請重新獲取app包名,打開之后就可以用抓包工具進行抓包了,ssl的一樣的可以抓:

 

 

 

 

上面看到的https的還是會隧道,但是緊接着就有數據出現,說明還是抓到了數據包了

 

 

ok,繞過ssl  pinning成功!!!

 

其實如果你覺得需要做些改動的話,可以寫個python腳本來調用,js代碼就作為文件內容讀取就行了,然后進行hook操作 

 

 

最后得出的結論就是,我朋友他們的產品,其實反爬做得挺好,上面的截圖也可以看到,其實還是有些數據拿不到的

 

 

補充:

如果你用的模擬器在安裝了app之后打不開,說明app有檢測是否是模擬器或者對安卓版本做了檢測,版本太低直接不給使用,那么你就只能用真機操作了,adb連接真機操作區別不大,詳細的自行百度

 

檢測模擬器的辦法:

 

  • 1.檢測模擬器上特有的文件
  • 2.檢測qemu pipes驅動程序
  • 3.檢測手機號是否是155552155開頭的
  • 4.檢測設備ID是否是15個0
  • 5.檢測IMSI ID是否是31026+10個0
  • 6.檢測運營商是否是“Android”
  • 7.代碼里用getInstance()方法調用任意一個方法,返回true就是模擬器
  • 8.檢測IMEI或者入網許可證

 

以上都是我以前搜集的數據,但是,根據現在的時代發展,可能模擬器也早就更新迭代了,把一些特征給抹除或者改的跟真機一樣了,所以有些方法並不是有用了,這個就只有自行選擇了

 

免root使用frida

 

 

其實不root也可以使用frida,這里我就不展開了,給一個大神寫的鏈接,里面還有其他方法的hook,感興趣自己看吧,點我

 

 

 

針對很安全性很強的app——逆向

 

 

 

JEB 

JEB 是一款適用於 Android 應用程序和本機機器代碼的反匯編器和反編譯器軟件。利用它我們可以直接將安卓的 apk 反編譯得到 Smali 代碼、jar 文件,獲取到 Java 代碼。有了 Java 代碼,我們就能分析其中的加密邏輯了。

 

 

JEB:https://www.pnfsoftware.com/

 

 

JADX

 

與 JEB 類似,JADX 也是一款安卓反編譯軟件,可以將 apk 反編譯得到 jar 文件,得到 Java 代碼,從而進一步分析邏輯。
JADX:https://github.com/skylot/jadx

  

dex2jar、jd-gui

  

這兩者通常會配合使用來進行反編譯,同樣也可以實現 apk 文件的反編譯,但其用起來個人感覺不如 JEB、JADX 方便。

 

脫殼

 

一些 apk 可能進行了加固處理,所以在反編譯之前需要進行脫殼處理。一般來說可以先借助於一些查殼工具查殼,如果有殼的話可以借助於 Dumpdex、FRIDA-DEXDump 等工具進行脫殼。

 

 

FRIDA-DEXDump:https://github.com/hluwa/FRIDA-DEXDump
Dumpdex:https://github.com/WrBug/dumpDex

 

 

反匯編

 

一些 apk 里面的加密可能直接寫入 so 格式的動態鏈接庫里面,要想破解其中的邏輯,就需要用到反匯編的一些知識了,這里可以借助於 IDA 這個軟件來進行分析。
IDA:https://www.hex-rays.com/

 

 

以上的一些逆向操作需要較深的功底和安全知識,在很多情況下,如果逆向成功了,一些加密算法還是能夠被找出來的,找出來了加密邏輯之后,我們用程序模擬就方便了。

 

模擬

 

逆向對於多數有保護 App 是有一定作用的,但有的時候 App 還增加了風控檢測,一旦 App 檢測到運行環境或訪問頻率等信息出現異常,那么 App 或服務器就可能產生防護,直接停止執行或者服務器返回假數據等都是有可能的。

 

對於這種情形,有時候我們就需要回歸本源,真實模擬一些 App 的手工操作了。

 

adb

 

最常規的 adb 命令可以實現一些手機自動化操作,但功能有限。

 

觸動精靈、按鍵精靈

 

有很多商家提供了手機 App 的一些自動化腳本和驅動,如觸動精靈、按鍵精靈等,利用它們的一些服務我們可以自動化地完成一些 App 的操作。

 

觸動精靈:https://www.touchsprite.com/

 

Appium

 

類似 Selenium,Appium 是手機上的一款移動端的自動化測試工具,也能做到可見即可爬的操作。

 Appium:http://appium.io/

 

AirTest

 

同樣是一款移動端的自動化測試工具,是網易公司開發的,相比 Appium 來說使用更方便。

 

AirTest:http://airtest.netease.com/

 

Appium/AirTest + mitmdump

 

mitmdump 其實是一款抓包軟件,與 mitmproxy 是一套工具。這款軟件配合自動化的一些操作就可以用 Python 實現實時抓包處理了。

 

mitmdump:https://mitmproxy.readthedocs.io/

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM