混淆后OKHTTP框架的通用抓包方案探索
目標:
okhttp因其穩定、開源、簡單易用被眾多的app開發人員所使用。okhttp中的execute和enqueue分別為發出同步和異步請求,通過對okhttp發起請求的整個流程進行簡單分析就可以快速得到合適的hook點完成對app網絡請求的抓包和溯源。但是,為了防止被快速逆向分析,部分app針對okhttp的代碼進行了混淆,請以課程中的被混淆了的okhttp框架的apk為例,提出針對混淆了的okhttp框架,進行抓包和溯源的通用解決方案。
針對okhttp框架的抓包,暫時知道兩種使用frida的hook方式。
第一種是自定義一個攔截器,并添加到okhttp框架的攔截器鏈中。如Yang神的實現(https://bbs.pediy.com/thread-252129.htm)。

還有一種是hook RealCall類的enqueue和execute方法,獲取到返回的response對象。然后逐步解析出請求和響應的內容。(如https://github.com/siyujie/OkHttpLogger-Frida)
OkHttpLogger-Frida的作者還考慮到了okhttp框架的代碼被混淆的情況。它專門打包了一個okhttpfind.dex文件來從Java層尋找okhttp的特征,并記錄所有后續要用到的類名,方法名和變量名。
通過查閱其代碼(https://github.com/siyujie/okhttp_find),發現它尋找特征的方法也很簡單粗暴。就是先去所有可能屬于okhttp框架層的類中找到OkHttpClient$Builder內部類。該類的特征為有四個List類型的成員變量,其中兩個有final修飾符,兩個的列表類型為接口類型。

一旦找到滿足這個條件的類,就可以通過getEnclosingClass方法獲取該類的外部類,也就定位到了關鍵類OkHttpClient。關鍵實現代碼如下:

接著就以OkHttpClient和OkHttpClient$Builder為起點,根據成員變量的類型或是成員方法的參數和返回值類型不斷定位到后續要用到的類、方法和變量。OkHttpLogger中定位到的主要內容如下:

如果是混淆了的話,具體值會在定位之后更新。對于作業的目標app,更新后的情況為:

可以發現,該app對okhttp的混淆還是比較嚴重的。很多方法名和變量名顯示出來的名字都是一樣的。這就對后續frida hook造成一定影響,因為在OkHttpLogger中不管是對方法hook還是直接調用,又或者是獲取某個對象的成員變量,都是直接通過名字進行的。當一個類中以非自然(重載方法不算)的形式出現了同名的方法或變量時,frida運行起來的結果就不能達到預期了。
比如,假設知道RealCall混淆后的類名為okhttp3.x。打印出該類所有的方法:

得到結果為(可以看到很多方法名相同):

即使通過特征識別,確定那個返回類型為okhttp3.aa,沒有參數的public方法為execute,也無法通過方法名hook該方法。

因為沒有參數的overload也匹配到了多個,除了一個以外都不是execute方法。通過.overload() hook到的那個方法是哪個是不確定的。(可能是第一個?)

所以現在的問題為如何做到不依賴函數名實現混淆okhttp的抓包。
暫時想到的方法是hook一個只有一個方法的類,這樣只要hook那個方法就不會錯。而這個方法又是okhttp請求鏈中的關鍵一環。Okhttp中正好有多個這樣的待選類,都是Interceptor接口的實現類。它只有一個intercept方法:

Okhttp的所有請求和響應肯定都要流經多個Interceptor實現類的intercept()方法,正常只要hook鏈中的一個類就行了,比如hook鏈中最后一個Interceptor。
課上有提到過,即使被混淆,還是可以通過打印出的調用堆棧與正常的堆棧對比,定位到關鍵類和方法。
通過hook NativeCrypto類的SSL_write方法,得到一下調用棧:

可以看到最后一個Interceptor為CallServerInterceptor,混淆后的類名為okhttp3.internal.b.b。用frida hook一下:

發現已經可以得到響應了:

那接下來就可以參照OkHttpLogger中尋找特征的方式,定位出解析請求和響應時用到的類,方法和成員變量。不同的是,對于方法和成員變量,不要只記錄其名字,而是記錄對應的整個對象。如一個方法就記錄一個對應的Method對象,一個成員變量就記錄一個對應的Field對象。
然后在需要調用方法的地方,用Method對象的invoke方法調用:

在需要獲取成員變量內容的時候,用Field對象的get方法:

中間步驟比較繁雜,所以直接附上了代碼。
最后hook結果為:


還有不少缺陷:
- 定位CallServerInterceptor的過程應該也可以用frida完成,就是先hook出調用堆棧后,從上至下逐個類判讀是否滿足Interceptor接口實現類的特征。將找到的第一個滿足條件的類作為攔截器鏈中的最后一個攔截器。
- 解析過程中對RequestBody的處理沒處理好。
- ResponseBody只處理了UTF-8編碼的內容,對于其他編碼的內容(如Gzip),則暫時未處理。
- 不太穩定,時不時會掛掉。但可能是app自帶的反hook措施。
暫時未來得及處理,慢慢完善。