Look for hook points from a system perspective, rather than capturing packets for the sake of capturing them.
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(); } }
The important code of the client is on client.newCall(). The above is an example from the okhttp official website. Starting from the interface call here, it will eventually be called to the okhttp framework. okhttp is originally an SDK. Later, AOSP has been integrated into the system, so it can be classified into the framework layer.
The framework layer is not detailed, mainly these java classes:
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
In fact, client.newCall will eventually obtain a connection through the URL
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
The urlConnection here is actually An instance of HttpURLConnectionImpl. This class has the getInputStream getOutputStream method, which internally calls HttpEngine's getBufferedRequestBody and getResponse respectively. At first, I tried hooking these two interfaces. For example, after hooking getResponse, the response can be printed out.
Later I found that Request can only output the header, not the body. So I immersed myself in the analysis and found that the function getBufferedRequestBody can be used to get a sink. Finally, RetryableSink is used as the breakthrough point. For example, hooking its write function can print out the body. The write function corresponds to urlConnection.getOutputStream().write at the app level.
Later I discovered that for a Request, the getBufferedReuqestBody function might be called more than once, so there would be a problem of data duplication. Later, I found the CacheStrategy$Factory.get point to hook, and found that there was still data duplication. It was found that all the above hooks have disadvantages
Data duplication
non-okhttp calls cannot be captured
Then the call stack starting from send, sendmsg, write, recv, and read of the native layer is also printed. Finally, after struggling for three days, I decided to give up treatment and use tools instead.
okhttp流程:sdk接口->okhttp框架->native(libc)
android.util.Log does not print
var Logd = function Logd(tag, msg) { Java.use("android.util.Log").d(tag, msg); }; Logd('http-body-', '11111111111111');//该log不打印 Logd('http-body', '11111111111111');//该log打印
Anonymous inner classes require reflection to obtain members
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"); } };
android.os.Bundle printing requires 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); };
In fact, the pitfalls mentioned above are not the only pitfalls I have encountered. At the beginning, I also tried some frida network interception solutions. I also carefully studied the Interceptor solution of okhttp. Finally, I found that the app also used an interceptor, so a conflict occurred and the solution could not be used. .
I also purely analyzed the smali of the app, looking for the call stack and network requests. In the end, there were only a few relatively small gains, which may not be useful to readers, but I recorded them so that I can recall them later.
java.net.URL interception
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调用概率比较大,但是不一定对网络进行请求
Interception where app uses json before calling http request, this is just one of them
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); } }
trace http related 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() { } }); }); };
Request$Builder interception
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; }
property_get interception
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); } });
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; };
After analyzing the adb log, the process has java.security.cert.CertPathValidatorException printed. I have also seen some posts about frida intercepting packets and bypassing certificates before. Try a brute force search first:
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; } } } });
Even if you directly force verify to return true, you still cannot log in because the same SSL problem error occurs. I found the answer after searching on Baidu. Unpack apktool, then modify
res/xml/network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<!--添加fiddle证书可信任
<certificates src="user" />
-->
</trust-anchors>
</base-config>
</network-security-config>
to repackage the signature and run it. Fiddle caught the package and the app can log in normally. I was lucky this time. The SSL verification of the app only has one-way app verification. Verification, the server did not perform verification.
I have been tossing and turning from Tuesday afternoon to Friday. Finally, it is not a good method to find hook points from HttpEngine at the system level, and the disadvantages are already clear. Therefore, on Sunday, I used packet capture tools and various methods found on Baidu to gradually solve the problems encountered.
The following are the two packages caught:
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 above is the detailed content of How to grab apk network package in frida. For more information, please follow other related articles on the PHP Chinese website!