更高更妙的抓包——從Chrome源碼學習使用Cronet 通訊組件的app的通用抓包方法
最近看論壇里關于抓包的問題聊的火熱,小弟我也借此機會獻丑來分享一下我的抓包的研究成果。
毫無疑問,app逆向的第一步是抓包。通過抓包可以獲取很多有用的線索,比如url,參數名等。再根據url,參數名等,可以逐步抽絲剝繭找到app收發數據的地方,然后就能找到最關鍵的簽名所在的位置。研究清楚簽名,接下來就可以做一些不可告人之事了~
安卓app常用的網絡框架是okhttp,對于okhttp的抓包研究已經非常成熟了,無非是certificate pin或者hostname verify那一套。但是最近幾年,出現了一些基于Google Cronet通訊框架的app,比如某音,某小破站等等。Cronet是從Chrome中抽出的給移動端使用的網絡組件,目前針對Cronet抓包,證書校驗的研究比較少,大多是奇技淫巧,沒能從根本上挖掘。本文從Chrome源碼出發,結合實例某音,研究一下使用Cronet安卓程序的正確抓包姿勢。
1.從報錯入手
打開手機代理,將https證書塞入手機的根目錄,啟動目標應用,結果:

請求全部失敗,app界面也顯示無網絡,無所謂,我們打開日志先看看:

看到了:ERR_CERT_AUTHORITY_INVALID,很明顯是檢測到了證書無效。
經常使用Chrome瀏覽器的朋友應該對這種ERR_開頭的錯誤有印象:

沒錯,這正是Chrome的報錯通用的格式。
那么我們就可以從報錯入手,去Chrome源碼里看看這個錯誤的來源,以此為根據尋求bypass的方式。
2.擼源碼
打開Chrome Code Search(在線的Chrome源碼瀏覽網頁,提供了強大的交叉引用和全文搜索功能)

全局搜索ERR_CERT_AUTHORITY_INVALID:

出現了很多結果,觀察后發現 net/cert/cert_status_flags.cc這個文件比較可疑,點進去看看:

發現 一個叫做MapCertStatusToNetError 的函數里,返回了這個ERR_CERT_AUTHORITY_INVALID。
(MapCertStatusToNetError )映射證書狀態至網絡錯誤類型,查看這個函數的被誰引用:

在一個cert_verify_proc_android.cc的文件里被引用。這個文件名已經暗示了在檢查/校驗證書,我們點進去看看:

在VerifyInternal這個函數里,先用TrustManager校驗了一番,如果發現校驗結果verify_result的cert_status出現了error,則直接將錯誤映射后返回。如果沒有錯誤則檢查了證書的發布者是否為known_root,然后返回OK。
我們再往上回溯VerifyInternal這個函數:

看到一個更抽象的名字:cert_verify_proc.cc(證書校驗程序),點進去看看:

在這個函數中,rv保存了VerifyInternal的結果,如果結果是OK,則再繼續進行了更多的檢查:

比如證書是否使用了較弱的hash算法,是否過期,是否有效期過長(ssl證書有效期通常為1年)等等。

最后將rv返回。
我們看到無論是哪一步返回,rv的值都是MapCertStatusToNetError 這個函數賦予的。所以如果我們直接將這個的返回值改為OK,那么無論怎么校驗,最終Verify的結果都是通過。換句話說,就是證書校驗的過程被bypass了。所以我們考慮在app的so中找到這個函數的位置,然后修改即可。
3.實戰
我們觀察MapCertStatusToNetError這個函數的特征:

很明顯的,每次通過一個flag與cert_status作與運算,如果通過則返回。
我們打開某音的lib目錄,找到目標so:

打開之后發現并不好定位MapCertStatusToNetError函數的位置,因為函數名被抹去了。
我們考慮從其他的地方入手,尤其是一些有字符串常量,或者打log的地方入手。
我們看到Verify函數調用了很多次MapCertStatusToNetError函數,但是Verify函數里并沒有可提供搜索的字符串信息。
結合編譯原理的知識,我們知道源碼中相鄰的函數通常在編譯結果中也是相鄰的。
我們到Verify函數下面的一個函數里有一個字符串信息:

我們全局搜索這個字符串,果然有:

通過交叉引用定位到引用這個字符串的函數,查看與他相鄰的上一個函數:

發現了與Verify函數相似的結構:判斷rv是否ok,ok則進一步檢查,并通過MapCertStatusToNetError函數給賦值。
我們點進去這個sub_31ff58函數:

看到了與MapCertStatusToNetError函數一樣的結構:通過flag與判斷,然后返回。
所以我們找到了MapCertStatusToNetError函數,通過修改匯編指令,直接讓他在入口處直接返回OK(0):
修改前:

修改后:

修改完后把so扔回手機替換app包里的so,再次打開app,發現已經可以成功抓包了:

隨便打開一個視頻的評論看看抓包是否正確:



4.后記
其實小破站用的也是cronet,之前也有類似的證書校驗,用同樣的方法也能抓包。但是今天試了一下發現小破站把證書校驗取消了,可能是覺得這些校驗容易被繞過所以直接放棄治療了吧哈哈。
總結起來,本文基于chrome源碼,提出了一種更為通用的cronet組件證書校驗的抓包方式,也更加直擊cronet組件證書校驗的核心。cronet的證書校驗繞過可能比okhttp的還要簡單,只要改8個字節就行,或者用frida hook MapCertStatusToNetError這個函數讓他返回0就行。難點在于MapCertStatusToNetError函數的定位,這個可以通過各種方式去定位,小弟我采用的是字符串+編譯相鄰性原理定位的。
希望本文能對大家在https抓包的研究上提供一種新的思路。