패킷을 캡처하기보다는 시스템 관점에서 후크 포인트를 찾으세요.
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을 통해 연결을 얻습니다
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
여기의 urlConnection은 실제로 HttpURLConnectionImpl의 인스턴스입니다. 이 클래스에는 getInputStream getOutputStream이 있습니다. 메서드 내부적으로 HttpEngine의 getBufferedRequestBody 및 getResponse가 각각 호출됩니다. 처음에는 이 두 인터페이스를 연결하려고 했습니다. 예를 들어 getResponse를 연결한 후 응답을 인쇄할 수 있습니다.
나중에 Request는 본문이 아닌 헤더만 출력할 수 있다는 것을 발견했습니다. 그래서 분석에 푹 빠져서 getBufferedRequestBody 함수를 사용하여 싱크를 얻을 수 있다는 것을 발견했습니다. 마지막으로 RetryableSink가 획기적인 지점으로 사용됩니다. 예를 들어 쓰기 함수를 연결하면 본문이 인쇄될 수 있습니다. 쓰기 함수는 앱 수준에서 urlConnection.getOutputStream().write에 해당합니다.
나중에 요청에 대해 getBufferedReuqestBody 함수가 두 번 이상 호출될 수 있으므로 데이터 중복 문제가 있을 수 있다는 것을 발견했습니다. 나중에 CacheStrategy$Factory.get 지점이 후크에 있음을 발견했습니다. 데이터 복제. 위의 모든 후크에는 단점이 있는 것으로 나타났습니다
데이터 복제
non-okhttp 호출을 캡처할 수 없습니다
다음 호출 스택은 기본 레이어의 send, sendmsg, write, recv, read부터 시작됩니다. 인쇄되기도 했습니다. 결국 3일 동안 고생한 끝에 치료를 포기하고 대신 도구를 사용하기로 결정했습니다.
okhttp流程:sdk接口->okhttp框架->native(libc)
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打印
익명 내부 클래스는 멤버를 얻기 위해 반사가 필요합니다
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을 인쇄할 때 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); };
을 풀어야 합니다. 사실, 위에서 언급한 함정은 처음에는 몇 가지 frida 네트워크 차단 솔루션을 시도했습니다. 또한 okhttp의 인터셉터 솔루션을 주의 깊게 연구한 결과 마침내 앱도 사용하고 있음을 발견했습니다. 인터셉터가 비활성화되어 충돌이 발생하여 솔루션을 사용할 수 없습니다.
앱의 스말리도 순수하게 분석해 콜 스택과 네트워크 요청을 찾아보았지만 결국 상대적으로 작은 이득이 몇 개밖에 없어 독자들에게는 유용하지 않을 수도 있지만, 참고할 수 있도록 기록해 두었습니다. 나중에 기억해 보세요.
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调用概率比较大,但是不一定对网络进行请求
앱이 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); } }
trace 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 $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; }
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); } });
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; };
Analytic adb 로그, 프로세스에 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; } } } });
직접 강제로 true를 반환하도록 강제 검증하더라도 동일한 SSL 문제 오류가 발생하기 때문에 여전히 로그인할 수 없습니다. 바이두에서 검색해서 답을 찾았습니다. apktool의 압축을 푼 다음
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>
를 수정하여 서명을 다시 패키지하고 실행합니다. Fiddle이 패키지를 포착하면 앱이 정상적으로 로그인할 수 있습니다. 이번에는 앱의 SSL 확인이 단방향 앱 확인만 가능합니다. 서버가 확인을 수행하지 않습니다.
화요일 오후부터 금요일까지 고생했습니다. 결국 시스템 수준에서 HttpEngine에서 Hook Point를 찾는 것은 좋은 방법이 아니며 단점은 이미 명확합니다. 그래서 일요일에는 Baidu에서 찾은 패킷 캡처 도구와 다양한 방법을 사용하여 발생한 문제를 점차적으로 해결했습니다.
여기에 잡힌 가방 두 개가 있습니다:
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":"成功"}}
위 내용은 Frida에서 APK 네트워크 패키지를 가져오는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!