frida如何抓apk網絡包

一 . 埋頭分析踩坑路

從系統的角度去尋找hook點,而不是為了抓包而抓包。

1.okhttp調用流程

public static final MediaType JSON = MediaType.get("application/json; charset=utf-8");  OkHttpClient client = new OkHttpClient();  String post(String url, String json) throws IOException { RequestBody body = RequestBody.create(json, JSON); Request request = new Request.Builder() .url(url) .post(body) .build(); try (Response response = client.newCall(request).execute()) { return response.body().string(); } }

客戶端的重要代碼在client.newCall()上,上面是okhttp官網的一個示例。從此處接口調用開始,終會調用至okhttp框架, okhttp本是sdk,后來aosp已經集成至系統,所以可以歸類至框架層。

框架層不詳述,主要就是這幾個java類:

com.android.okhttp.internal.huc.HttpURLConnectionImpl com.android.okhttp.internal.http.HttpEngine com.android.okhttp.internal.http.RetryableSink com.android.okhttp.internal.http.CacheStrategy$Factory

其實client.newCall終會通過URL獲取一個connection

HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();

這里的urlConnection其實就是HttpURLConnectionImpl的實例,該類有getInputStream getOutputStream方法,內部分別會調用HttpEngine的getBufferedRequestBody,getResponse。剛開始我嘗試hook過這兩個接口,比如hook getResponse后,可以將response打印出來.

后來我發現,Request只能輸出header,無法輸出body。所以又埋頭繼續分析,getBufferedRequestBody這個函數剛好可以入手,獲取一個sink,最后以RetryableSink為突破點,比如hook 其write函數就可以將body打印出來。write函數對應于app層面的urlConnection.getOutputStream().write。

后來發現一個Request,調用getBufferedReuqestBody函數可能不止一次,所以會有數據重復的問題,后來我又尋找到了CacheStrategy$Factory.get點進行Hook,發現還是有數據重復。發現以上hook均有弊端

  • 數據重復

  • 非okhttp調用無法抓取

然后還打印了從native層的send,sendmsg,write,recv,read開始的調用棧。最后折騰了三天,決定放棄治療,還是采取工具吧。

okhttp流程:sdk接口->okhttp框架->native(libc)

2.分析過程中frida踩到的坑(重點都在注釋中)

  1. android.util.Log不打印

    var Logd = function Logd(tag, msg) { Java.use("android.util.Log").d(tag, msg); };   Logd('http-body-', '11111111111111');//該log不打印 Logd('http-body', '11111111111111');//該log打印
  2. 匿名內部類獲取成員需要反射

    var printRequest = function(request) { var Buffer = Java.use("com.android.okhttp.okio.Buffer"); var bodyField = request.getClass().getDeclaredField('body'); bodyField.setAccessible(true);  if (request == null) return; Logd('http', 'printRequest: request' + request); //var requestBody = request.body();//gadget直接報錯 var requestBody = bodyField.get(request);  var requestBodyClass = requestBody.getClass(); var ClassInstanceArray = Java.array('java.lang.Class', []);  //var contentLengthMethod = requestBodyClass.getMethod("contentLength");//gadget直接報錯 var contentLengthMethod = requestBodyClass.getMethod("contentLength", ClassInstanceArray);  contentLengthMethod.setAccessible(true); var ObjectInstanceArray = Java.array('java.lang.Object', []); var contentLength = requestBody ? contentLengthMethod.invoke(requestBody, ObjectInstanceArray) : 0; //if (contentLength == 0) contentLength = contentLen; Logd('http', 'printRequest contentLength: ' + contentLength); if (contentLength > 0) { var BufferObj = Buffer.$new(); requestBody.writeTo(BufferObj); Logd(TAG, "nrequest body :n" + BufferObj.readString() + "n"); } };
  3. android.os.Bundle打印,需要將Bundle unparcel

    var printIntentAndExtras = function printIntentAndExtras(intentObj) { if (intentObj == null) return; var Intent = Java.use("android.content.Intent"); var Bundle = Java.use("android.os.Bundle"); var bundleObj = Intent.getExtras.call(intentObj);  if (bundleObj != null) { Bundle.getSize.call(bundleObj, null);//調用getSize即可反序列化 }  Logd(TAG, ‘printIntentAndExtras ’ + bundleObj); };

踩到的坑其實不只上面的,剛開始也百度過一些frida網絡攔截的方案,還仔細的研究了okhttp的Interceptor方案,最后發現app也是用了攔截器,所以就發生沖突,導致無法使用該方案。

也純粹的分析過app的smali,尋找調用棧以及網絡請求,最后,只有幾個比較小的收獲,可能對讀者沒有用處,不過記錄一下,方便自己以后回憶。

  1. java.net.URL攔截

    var URLHook = function() { var URL = Java.use('java.net.URL'); URL.openConnection.overload().implementation = function() { var retval = this.openConnection(); Logd('URL', openConnection' + retval); return retval; }; };//URL.openConnection調用概率比較大,但是不一定對網絡進行請求
  2. 攔截app調用http請求前使用json的地方,這只是其中之一

    var jsonHook = function() { var xx = Java.use('e.h.a.a');//app smali var xxa_method = xx.a.overload('org.json.JSONObject', 'java.lang.String', 'java.lang.String'); xxa_method.implementation = function(jsonObj, str1, str2) { Logd("json", jsonObj + " str1: " + str1 + " str2" + str2); xxa_method.call(this, jsonObj, str1, str2); } }
  3. trace http相關class

    var traceAllHttpClass = function() { Java.perform(function() { Java.enumerateLoadedClasses({ onMatch: function(name, handle) { /*"e.h.a.a$a",起初也攔截過app的該混淆類*/ if (name.indexOf("com.android.okhttp.Http") != -1 || name.indexOf("com.android.okhttp.Request") != -1 || name.indexOf("com.android.okhttp.internal") != -1) { traceClass(name);//對這三個class進行trace } }, onComplete: function() { } }); }); };
  4. Request$Builder攔截

    var BuilderClass = Java.use('com.android.okhttp.Request$Builder')  BuilderClass.build.implementation = function () { //LOG('com.android.okhttp.HttpUrl$Builder.build overload', { c: Color.Light.Cyan }); //printBacktrace(); var retval = this.build(); Logd(TAG, "retval:" + retval); printRequest(retval); return retval; }
  5. property_get攔截

    var nativePropertyGetAddr = Module.findExportByName(null, '__system_property_get'); Interceptor.attach(nativePropertyGetAddr, { onEnter: function onEnter(args) { this._name = args[0].readCString(); this._value = args[1]; }, onLeave: function onLeave(retval) { if (this._name.indexOf("ro.build.id") != -1) { var virtualDevice = getVirtualDevice(); if (DEBUG_PROP) Logd(TAG, "__system_property_get fake " + this._name + "=>to " + virtualDevice.build_id);  this._value.writeUtf8String(virtualDevice.build_id); }  var strFilter = /^ro./g; if (DEBUG_PROP && this._name.match(strFilter) != null) Logd(TAG, "__system_property_get " + this._name); } });

二 . 設備android_id導致用戶過期的處理

var DEBUG_PROP = false; var DEVICE_CONFIG = "/sdcard/.device";  function getVirtualDevice() { var nativeOpen = new NativeFunction(Module.findExportByName(‘libc.so’, 'open'), 'int', ['pointer', 'int']); var nativeRead = new NativeFunction(Module.findExportByName('libc.so', 'read'), 'int', ['int', 'pointer', 'int']); var fd = nativeOpen(Memory.allocUtf8String(DEVICE_CONFIG), 0); var mem = Memory.alloc(1024); var readLen = nativeRead(fd, mem, 1024); var json = JSON.parse(mem.readCString(readLen)); return json; }  Secure.getString.implementation = function () { var retval = this.getString(arguments[0], arguments[1]); if (DEBUG_PROP) Logd(TAG, "Settings.Secure get " + arguments[1] + " val " + retval);  if (arguments[1].indexOf("android_id") != -1) { var virtualDevice = getVirtualDevice(); return virtualDevice.android_id; }  return retval; };

三 . 使用抓包工具fiddle抓包脫坑

1.fiddle代理設置OK,app卻無法登陸

分析adb log,進程有 java.security.cert.CertPathValidatorException的打印,之前也看過一些frida攔截抓包繞過證書的帖子。先試一把暴力搜索:

Java.perform(function(){ const groups = Java.enumerateMethods('*!verify/u'); var classes = null; for(var i in groups){ var classes = groups[i]['classes'];  for(var i in classes){ Java.use(classes[i]['name']) .verify .overload('java.lang.String', 'javax.net.ssl.SSLSession') .implementation = function() { printBacktrace(); LOG("[+] invoke verify", { c: Color.Red }); return true; } } } });

即使直接強制verify返回true,仍然無法登錄,因為出現了相同的ssl問題錯誤。百度搜索后找到了答案。apktool解包,然后修改

res/xml/network_security_config.xml <?xml  version="1.0" encoding="utf-8"?><network-security-config><base-config><trust-anchors><certificates></certificates><!--添加fiddle證書可信任 <certificates src="user" /> --></trust-anchors></base-config></network-security-config>

重打包簽名后運行一把,fiddle抓到了包,app也能正常登陸了,這次也是運氣好吧,app的ssl校驗只有單向app校驗,服務器并沒有進行校驗。

四.結束

從周二下午一直折騰到周五,最后從系統層面的HttpEngine尋找hook點并不是很好的方法,弊端也已明了。因此,在周日利用抓包工具和各種從百度上找到的方法,逐步解決遇到的問題。

下面是抓到的兩個包:

HTTP/1.1?200?OK Date:?Sun,?16?Aug?2020?06:27:34?GMT Content-Type:?application/json Content-Length:?101 Connection:?keep-alive Grpc-Metadata-Content-Type:?application/grpc Vary:?Origin Vary:?Accept-Encoding  {"result":{"errno":"OK","errmsg":"成功"},"data":{"version":"xxxxxxxx-351e-40cf-aaa9-3177d6df9b7f"}} ----------------------------------- HTTP/1.1?200?OK Date:?Sun,?16?Aug?2020?06:27:34?GMT Content-Type:?application/json Content-Length:?99 Connection:?keep-alive Grpc-Metadata-Content-Type:?application/grpc Vary:?Origin Vary:?Accept-Encoding  {"result":{"errno":"OK","errmsg":"成功"},"data":{"nodeToken":"xxxxxxxc24d79f55c0b07beaf50cb566"}}
POST?https://tap-xxxxxxx.xxxxxx.com/api/v2/Android/analytics/basic?HTTP/1.1 Authorization:?Bearer?eyJhbGciOiJIUzI1NiIsInR5cjbcjdsabcjvbXVCJ9.eyJ1aWQiOjE4ODMzMDEsInNlY3JldCI6IjAzNzE0M2Y3LTExMTUtNGY2Yi1iNzQxLWUyMjc5ZDM3MGY3MCIsImV4cCI6MTU5NzgxNjQ0MiwiaXNzIjoiZ3Vlc3QgbG9naW4ifQ.W3SiO0-afbhxPITjRinnhyWhZLy1bzZhYexm5VCWklI X-Device-ID:?9xxxxxxx84d4542e X-Loc:?["China","Shanghai","Shanghai","","ChinaUnicom","31.224349","121.4767528","Asia/Shanghai","UTC+8","310000","86","CN","AP","xxx.166.xxx.xxx"] X-App-Version:?2.2.0 Content-Type:?application/json;?charset=utf-8 Content-Length:?208 Host:?xx-xxxx.xxxxxx.com Connection:?Keep-Alive Accept-Encoding:?gzip User-Agent:?okhttp/4.7.2  {"deviceID":"9xxxxxxx84d4542e","model":"V1813BA","systemVersion":"9","version":"2.2.0","location":{"latitude":xx.x99x990990991,"longitude":xxx.26689769073256},"network":{"g2":0,"g3":0,"g4":4,"g5":0,"wifi":4}}  ----------------------------------- HTTP/1.1?200?OK Date:?Sun,?16?Aug?2020?06:27:35?GMT Content-Type:?application/json Content-Length:?43 Connection:?keep-alive Grpc-Metadata-Content-Type:?application/grpc Vary:?Origin Vary:?Accept-Encoding  {"result":{"errno":"OK","errmsg":"成功"}}

? 版權聲明
THE END
喜歡就支持一下吧
點贊13 分享