Recherchez les points d'accroche du point de vue du système, plutôt que de capturer des paquets pour le plaisir de les capturer.
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(); } }
Le code important du client se trouve sur client.newCall(). Ce qui précède est un exemple tiré du site officiel d'okhttp. À partir de l'appel d'interface ici, il sera éventuellement appelé au framework okhttp. okhttp est à l'origine un SDK. Plus tard, AOSP a été intégré au système, il peut donc être classé dans la couche framework.
La couche framework n'est pas détaillée, principalement ces classes 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
En fait, client.newCall finira par obtenir une connexion via l'URL
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
L'urlConnection ici est en fait une instance de HttpURLConnectionImpl. Cette classe a le getInputStream getOutputStream. méthode, en interne, getBufferedRequestBody et getResponse de HttpEngine seront appelés respectivement. Au début, j'ai essayé de connecter ces deux interfaces. Par exemple, après avoir connecté getResponse, la réponse peut être imprimée.
Plus tard, j'ai découvert que Request ne peut afficher que les en-têtes, mais pas le corps. Je me suis donc plongé dans l'analyse et j'ai découvert que la fonction getBufferedRequestBody peut être utilisée pour obtenir un récepteur. Enfin, RetryableSink est utilisé comme point de percée. Par exemple, l'accrochage de sa fonction d'écriture peut imprimer le corps. La fonction d'écriture correspond à urlConnection.getOutputStream().write au niveau de l'application.
Plus tard, j'ai découvert que pour une requête, la fonction getBufferedReuqestBody pouvait être appelée plus d'une fois, donc il y aurait un problème de duplication de données. Plus tard, j'ai trouvé le point d'accroche CacheStrategy$Factory.get, et j'ai constaté qu'il y en avait toujours. duplication des données. Il a été constaté que tous les hooks ci-dessus présentent des inconvénients
Duplication de données
les appels http non ok ne peuvent pas être capturés
Ensuite, la pile d'appels à partir de send, sendmsg, write, recv, read de la couche native a également été imprimé. Finalement, après trois jours de lutte, j’ai décidé d’abandonner le traitement et d’utiliser des outils à la place.
okhttp流程:sdk接口->okhttp框架->native(libc)
android.util.Log ne s'imprime pas
var Logd = function Logd(tag, msg) { Java.use("android.util.Log").d(tag, msg); }; Logd('http-body-', '11111111111111');//该log不打印 Logd('http-body', '11111111111111');//该log打印
Les classes internes anonymes nécessitent une réflexion pour obtenir des membres
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. Lors de l'impression de os.Bundle, vous devez déballer le Bundle
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); };
En fait, les pièges mentionnés ci-dessus ne sont pas les seuls. Au début, j'ai essayé quelques solutions d'interception de réseau Frida. a également étudié attentivement la solution Interceptor d'okhttp et a finalement découvert que l'application utilise également l'intercepteur est désactivé, donc un conflit se produit et la solution ne peut pas être utilisée.
J'ai aussi purement analysé le smali de l'application, à la recherche de la pile d'appels et des requêtes réseau. Au final, il n'y a eu que quelques gains relativement faibles, qui peuvent ne pas être utiles aux lecteurs, mais je les ai enregistrés pour pouvoir. rappelez-les plus tard.
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 où json est utilisé avant que l'application n'appelle la requête http, ce n'est que l'un d'entre eux
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 la classe associée à http
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$ Interception du constructeur
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; };
Analyse du journal adb, le processus a java.security.cert.CertPathValidatorException imprimé, et j'ai déjà vu des articles sur Frida interceptant des paquets et contournant des certificats. Essayez d'abord une recherche par force brute :
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; } } } });
Même si vous forcez directement la vérification à renvoyer true, vous ne pouvez toujours pas vous connecter car la même erreur de problème SSL se produit. J'ai trouvé la réponse après avoir cherché sur Baidu. Décompressez apktool, puis modifiez
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>
pour reconditionner la signature et exécutez-la. Fiddle a détecté le package et l'application peut se connecter normalement. Cette fois, la vérification SSL de l'application n'a qu'une vérification unidirectionnelle de l'application. le serveur ne fonctionne pas. Aucune vérification n'est effectuée.
J'ai eu du mal du mardi après-midi au vendredi. Enfin, ce n'est pas une bonne méthode pour trouver des points d'accroche depuis HttpEngine au niveau du système, et les inconvénients sont déjà clairs. J'ai donc utilisé dimanche les outils de capture de paquets et diverses méthodes trouvées sur Baidu pour résoudre progressivement les problèmes rencontrés.
Voici les deux sacs attrapés :
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":"成功"}}
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!