某視頻app的學習記錄
一、軟硬件環境
IDA 7.5
Frida 14.2.2
Gda3.86
JEB
jadx-gui
unidbg
LineageOs 17.1 (android 10)
小米8
二、坑(時間順序)
1、截包相關
這個看見有人說有個版本開始不能截了,我這邊一直都是換證書的,沒感覺有影響,估計我下的是盜版,碰到再看了。
2、之前看到so都是32位的,后來都換成64位的了。
3、網絡請求相關
之前就看到有使用Cronet模塊,想看下實現過程,下過源碼(https://github.com/hanpfei/chromium-net),一個是發現跟app使用的并不完全相同,再一個還要自己編譯模塊,就想到直接使用抖音的模塊。
先新建一個自己的測試app,接著的工作就是搬代碼了,直接導出所有的反編譯代碼,結合源碼,首先就是這個包:
com.ttnet.org.chromium.net

這里插下搬代碼中的一些問題:
首先就是反編譯代碼,幾個工具(jeb ,jadx, GDA)各有優劣,要結合使用, jadx得到的代碼可讀性更好,但是很多函數識別不出來,比如這種:

GDA擅長處理疑難雜癥,其它2個識別不了的代碼,它這個都能識別,不過可讀性不好,基本不能直接編譯通過,需要看清楚邏輯后修復。
再就是反編譯代碼中變量順序問題,也會影響編譯(初始化的變量放在后面了):

代碼修復中有些類型的轉換,比如Bundle類型變量為null的,可能被還原成了int i=0; 然后條件判斷的時候,這個也會出現編譯錯誤。

再就是Map.Entry這種,需要先用object遍歷,再轉換類型。
if (!StringsKt.contains$default((CharSequence) name, (CharSequence) "__MACOSX", false, 2, (Object) null)) {
kotlin相關也要修改:
StringsKt.contains((CharSequence) str, (CharSequence) "?", false))
還有下面這種繼承的:
public final class FrontMethodFragment$onCreateFailed$1 extends Lambda implements Function0 {
Lambda要帶上Lambda
參考這個 cannot be inherited with different type arguments_PhilsphyPrgram的博客-CSDN博客。
還有一些邏輯上的,少了break之類,就不是很好發現了,這個只有遇到后調試修復了。
最后還有麻煩的就是缺少異常處理,java編譯是強制要求處理異常的,找過資料,想編譯時候忽略異常處理,發現是JAVA規范強制的,屏蔽不了,加這個也花了不少時間。
總之,3個工具要結合使用,怎么方便怎么來。
搬過來后,直接調用測試:
// 初始化引擎 CronetEngine.Builder myBuilder = new CronetEngine.Builder(getApplicationContext()); CronetEngine cronetEngine = myBuilder.build();
// 創建請求線程 Executor executor = Executors.newSingleThreadExecutor();
// 創建UrlRequest strUrl="http://10.0.0.217/about"; UrlRequest.Builder requestBuilder = cronetEngine.newUrlRequestBuilder( strUrl, new MyUrlRequestCallback(), executor); requestBuilder.addHeader("testHeader","testValue"); UrlRequest request = requestBuilder.build();
// 發起請求 request.start();
class MyUrlRequestCallback extends UrlRequest.Callback { private static final String TAG = "ttttt MyUrlRequestCallback";
@Override public void onRedirectReceived(UrlRequest request, UrlResponseInfo info, String newLocationUrl) { android.util.Log.i(TAG, "onRedirectReceived method called."); // You should call the request.followRedirect() method to continue // processing the request. request.followRedirect(); }
@Override public void onResponseStarted(UrlRequest request, UrlResponseInfo info) { //這個函數只會調用一次 android.util.Log.i(TAG, "onResponseStarted method called."); // You should call the request.read() method before the request can be // further processed. The following instruction provides a ByteBuffer object // with a capacity of 102400 bytes to the read() method. request.read(ByteBuffer.allocateDirect(102400)); }
@Override public void onFailed(UrlRequest urlRequest, UrlResponseInfo urlResponseInfo, CronetException cronetException) {
}
@Override public void onReadCompleted(UrlRequest request, UrlResponseInfo info, ByteBuffer byteBuffer) { //這個會調用多次 android.util.Log.i(TAG, "onReadCompleted method called."); // You should keep reading the request until there's no more data. request.read(ByteBuffer.allocateDirect(102400)); }
@Override public void onSucceeded(UrlRequest request, UrlResponseInfo info, String str) { android.util.Log.i(TAG, "onSucceeded method called."); }}
測試發現本地服務器正常收到請求了,測試app也能正常收到回調:

不過請求地址改成本地https的時候,發現崩潰,根據日志定位報錯線程:


發現是throw new UnsupportedOperationException("Method not decompiled造成的,那就是jadx代碼不全的問題,這個參考GDA補全。
不過這里也看出是證書校驗問題,本地的證書不通過。
// Certificate is not trusted due to non-trusted root of the certificate// chain.static int NO_TRUSTED_ROOT = -2;
這里又熟悉了下https,查了下資料:HTTPS請求的整個過程的詳細分析_研究生生活、學習記錄-CSDN博客_https過程
最后,由于非對稱加密的公鑰可以在網絡中傳輸,如何保證公鑰傳送到給正確的一方,這個時候使用了證書來驗證。證書不是保證公鑰的安全性,而是驗證正確的交互方。

從這個流程圖來看,訪問本地網站就是驗證證書無效了。

本地web服務器的file.crt文件:

解密后:
Len:625
正好上面截圖對應測試程序調試中的certChain。
搞清楚這個過程后,后面再進行干涉就有切入點了。
到這里后,雖然可以調用Cronet了,但是發現跟抖音自己的接口調用路徑其實是不同的,比如某個接口的堆棧:

明顯不是上面用的調用方式,那接著就是根據這個補代碼了。

熟悉了上面這種注釋語法:


最后找到下面這個:

其實之前也看到過這個url,但是關聯不到怎么調用的,現在看是通過代理類方式使用的($Proxy84),這里是這個功能的類定義文件,單純靜態看確實不好看明白,通過動態調試才搞清楚整個過程。
看見有鴻蒙相關:

這里遇到個自己挖的坑,因為用了混淆的包名,導致hash這里跳過了,返回null了,會導致創建proxy類不成功:
private T getStaticServiceImplReal(Class cls) { PatchProxyResult proxy = PatchProxy.proxy(new Object[]{cls}, this, changeQuickRedirect, false, 95671); if (proxy.isSupported) { return (T) proxy.result; }
int iHashCode=cls.getName().hashCode(); switch (iHashCode) {

補完各種代碼后(中間確實各種報錯,各種補文件,最后備份的時候,發現有3000+文件,不過有的模塊,比如wx相關的基本拷過來就能用了,zfb的交叉太多了,花了不少時間修復,感覺這樣下去,可以編譯一個抖音出來了),使用下面方式調用接口:
FeedActionApi.f71204b.diggItem("7018000130007633191", "7018000130007633191", 1, 0).get();
運行調試:

看起來點贊操作的類是創建成功了,跟hook看到的堆棧類似了。
這里查了下代理類的資料:Java動態代理之InvocationHandler最簡單的入門教程 - 簡書 (jianshu.com)

創建代理類相關:






這個選擇不同的網絡模式,模擬器默認走第二種。
if (C9859c.m21219a()) { jSONObject.put("netClientType", "CronetClient"); } else { jSONObject.put("netClientType", "TTOkhttp3Client"); }
到這里后,終于在測試服務器收到請求了:

到這里的時候,發現極速版更新到18.2了。
Cront相關Native函數引入有變化:

輸出jni函數的時候,發現有SPDY相關的:

順便查了下SPDY相關資料:HTTP 的前世今生:一次性搞懂 HTTP、HTTPS、SPDY、HTT_請求 (sohu.com)


后面看來要看下這個了。
在16.6版本都測試程序上替換上18.2版本都so,測試報錯:

看起來native函數引入混淆了:

包裝了一層:

對應修改后,就可以正常編譯運行了。
看到檢查root相關代碼:

算起來這次搬代碼,最難的是開始時候,加進來的代碼牽扯其它引用,一堆編譯錯誤,加入引用,可能又會引入新的引用依賴,很容易耐心磨沒了,這個時候就需要權衡取舍,不能把分支展得太開,還好堅持下來了,慢慢框架搭起來后,很多就是正向工作了。
三、學習總結
1、學習了JAV代理類使用,大廠設計模式。
2、熟悉了Cronet模塊。
感覺主要工作都是正向的,正向逆向不分家,有了正向的工作,逆向切入點也會更多。