抓大厂app,发现各种抓不到,最后找到个脚本 hook 它在 java 层的收发包函数,也算是一种抓包方式。
更新:经过测试,透明代理方式也可以抓到包 -> 安卓上基于透明代理对特定APP抓包
找到hook点
r0capture 抓不到明文包,中间人抓包则会导致 app 请求不了网络数据,应该是做了证书绑定,试了几个 bypass ssl pinning 脚本也不管用,反编译发现它不是一般的对 okhttp 的混淆,而是自己封装实现了 okhttp ,用 adb logact 看到如下报错
1 2 3 4 5 6 7 8
| 02-18 20:36:09.758 30350 31893 W System.err: com.bytedance.frameworks.baselib.network.http.cronet.impl.CronetIOException: :0|Exception in CronetUrlRequest: net::ERR_TTNET_APP_TIMED_OUT, ip=:0, details={...}, attempts= , dns=2003, connect=-1, ssl=-1, send=-1, proxy=-1, wait=-1, receive=-1, total=-1, reused=0, cached=0, response_content_len=0, total_bytes=0, is_proxy=0, QuicStreamError=0, QuicCloseS 02-08 18:04:01.543 3477 4560 W System.err: at X.796.execute(SourceFile:524816) 02-08 18:04:01.543 3477 4560 W System.err: at com.bytedance.retrofit2.CallServerInterceptor.executeCall(SourceFile:33816611) 02-08 18:04:01.543 3477 4560 W System.err: at com.bytedance.retrofit2.CallServerInterceptor.intercept(SourceFile:17236173) 02-08 18:04:01.543 3477 4560 W System.err: at com.bytedance.retrofit2.intercept.RealInterceptorChain.proceed(SourceFile:17170567) 02-08 18:04:01.543 3477 4560 W System.err: at com.bytedance.frameworks.baselib.network.http.retrofit.RequestVertifyInterceptor.intercept(SourceFile:17104962) 02-08 18:04:01.543 3477 4560 W System.err: at com.bytedance.retrofit2.intercept.RealInterceptorChain.proceed(SourceFile:17170567) 02-08 18:04:01.543 3477 4560 W System.err: at X.0fr.intercept(SourceFile:17104974)
|
看看类 X.796
的 execute
方法,可以确定在这里发送请求并且拿到数据,那么 hook 这里同时打印 request 和 response
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public Response execute() throws IOException { ... String url = this.request.getUrl(); if (!this.f82341g) { ... try { int code = getCode(SsCronetHttpClient.processExecute(this.request, this.httpURLConnection)); this.baseHttpRequestInfo.responseBack = System.currentTimeMillis(); this.baseHttpRequestInfo.recycleCount = -1; this.f82339e = SsCronetHttpClient.processAfterExecute(this.httpURLConnection, this.baseHttpRequestInfo, code); this.f82345k = SsCronetHttpClient.getHeaderValueIgnoreCase(this.httpURLConnection, "Content-Type"); if (this.request.isResponseStreaming()) { ... } else { int maxLength2 = this.request.getMaxLength(); this.baseHttpRequestInfo.contentType = SsCronetHttpClient.getContentBaseType(this.f82345k); typedByteArray = new TypedByteArray(this.f82345k, SsCronetHttpClient.processResponse(url, maxLength2, this.httpURLConnection, this.f82338d, this.baseHttpRequestInfo, this.f82339e, code, this.f82343i), new String[0]); } Response response = new Response(url, code, this.httpURLConnection.getResponseMessage(), SsCronetHttpClient.createHeaders(this.httpURLConnection, f82333l), typedByteArray); response.setExtraInfo(this.baseHttpRequestInfo); ... return response; } catch (Exception e2) { ... } throw new IOException("request canceled"); }
|
脚本hook
刚好找到一位大佬的代码:aweme_httpcat.js
拿来改成对 X.796.execute
的 hook
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
| function getObjClassName(obj) { var jclazz = Java.use("java.lang.Class"); var jobj = Java.use("java.lang.Object");
return jclazz.getName.call(jobj.getClass.call(obj)); }
var fileFilterArray = [".webp",".png",".jpg",".jpeg",".image"]
function isBlackListUrl(url) { for (var i = 0; i < fileFilterArray.length; i++) { if (url.indexOf(fileFilterArray[i]) != -1) { console.log(url + " ?? " + fileFilterArray[i]) return true; } } return false; }
function splitLine(string, tag) { var lineLength = 150; var newSB = Java.use("java.lang.StringBuilder").$new() var newString = Java.use("java.lang.String").$new(string) var lineNum = Math.ceil(newString.length() / lineLength) for (var i = 0; i < lineNum; i++) { var start = i * lineLength; var end = (i + 1) * lineLength newSB.append(tag) if (end > newString.length()) { newSB.append(newString.substring(start, newString.length())) } else { newSB.append(newString.substring(start, end)) } newSB.append("\n") } return newSB.deleteCharAt(newSB.length() - 1).toString() }
function main() { Java.perform(function () { Java.openClassFile("/data/local/tmp/dexfile/okhttpfind.dex").load(); var class_string = Java.use("java.lang.String"); var class_integer = Java.use("java.lang.Integer"); var class_stringBuffer = Java.use("java.lang.StringBuffer"); var class_jsonObject = Java.use("org.json.JSONObject"); var class_JavaLinkedTreeMap = Java.use("com.singleman.gson.internal.LinkedTreeMap"); var class_byteString = Java.use("com.singleman.okio.ByteString"); var class_byteArrayOutputStream = Java.use("java.io.ByteArrayOutputStream"); var class_javaMap = Java.use("java.util.Map"); var gson = Java.use("com.singleman.gson.Gson").$new(); var X796 = Java.use("X.796"); var fields = X796.class.getDeclaredFields(); var requestField = null; for (var field of fields) { field.setAccessible(true); if (class_string.$new("f").equals(field.getName())) { requestField = field; } } X796["execute"].implementation = function () { var response = this.execute(); if (response == null) { console.log('can not find response field'); return response; } var request = Java.cast(requestField.get(this), Java.use("com.bytedance.retrofit2.client.Request")); if (request == null) { console.log('can not find request field'); return response; } var logString = class_stringBuffer.$new(); logString.append("").append("\n"); logString.append("┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────").append("\n").append("\n"); var url = request.getUrl(); if (url == null) { url = "null"; } logString.append("| URL").append("\n"); logString.append(splitLine(url, "| ")).append("\n"); if (isBlackListUrl(url)) { logString.append("└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────").append("\n").append("\n"); logString.append("").append("\n").append("\n"); console.log(logString); return response; } var extraInfo = response.getExtraInfo(); if (extraInfo != null) { var requestLogString = extraInfo.getClass().getField("requestLog").get(extraInfo); if (null != requestLogString) { var jsonObject = class_jsonObject.$new(requestLogString); var baseJsonObj = jsonObject.getJSONObject("base"); var httpMethod = baseJsonObj.getString("method"); var originUrl = baseJsonObj.getString("origin_url"); logString.append("| ").append("\n"); logString.append("| " + "Method : " + httpMethod + " originUrl : " + originUrl).append("\n"); } var requestHeadersString = extraInfo.getClass().getField("requestHeaders").get(extraInfo); logString.append("| ").append("\n"); logString.append("| " + "Request Headers").append("\n"); var requestHeaderMapObj = gson.fromJson(requestHeadersString, class_javaMap.class); var requestHeaderMap = Java.cast(requestHeaderMapObj, class_JavaLinkedTreeMap); var keyArray = requestHeaderMap.keySet().toArray(); for (var i = 0; i < keyArray.length; i++) { var key = keyArray[i]; var value = requestHeaderMap.get(key); logString.append("| " + key + ":" + value).append("\n"); } }
var requestBody = request.getBody(); if (null != requestBody) { logString.append("| ").append("\n"); logString.append("| " + "Request Body").append("\n"); var byteArrayOutputStream = class_byteArrayOutputStream.$new(); requestBody["writeTo"](byteArrayOutputStream); var bodyString = ""; try { var bodyByteString = class_byteString.of(byteArrayOutputStream.toByteArray()); bodyString = bodyByteString.utf8(); } catch (error) { bodyString = "Base64[" + bodyByteString.base64() + "]"; } logString.append(splitLine(bodyString, "| ")).append("\n"); } var code = response.getStatus(); logString.append("| ").append("\n"); logString.append("| ").append("\n"); logString.append("| " + "code : " + code).append("\n");
if (extraInfo != null) { var responseHeadersString = extraInfo.getClass().getField("responseHeaders").get(extraInfo); logString.append("| ").append("\n"); logString.append("| " + "Response Headers").append("\n"); var responseHeaderMapObj = gson.fromJson(responseHeadersString, class_javaMap.class); var responseHeaderMap = Java.cast(responseHeaderMapObj, class_JavaLinkedTreeMap); var keyArray = responseHeaderMap.keySet().toArray(); for (var i = 0; i < keyArray.length; i++) { var key = keyArray[i]; var value = responseHeaderMap.get(key); logString.append("| " + key + ":" + value).append("\n"); } } var responseBody = response["getBody"](); logString.append("| ").append("\n"); logString.append("| " + "mimeType : " + responseBody["mimeType"]()).append("\n"); logString.append("| ").append("\n"); logString.append("| " + "Body").append("\n"); var inputSteam = responseBody.in(); var length = responseBody.length();
var lengthInt = class_integer.valueOf(class_string.valueOf(length)); if (responseBody.$className.indexOf("TypedByteArray") != -1) { var byteString = class_byteString.read(inputSteam, lengthInt["intValue"]()); var bodyString = ""; try { bodyString = byteString.utf8(); } catch (error) { bodyString = "Base64[" + byteString.base64() + "]"; } logString.append(splitLine(bodyString, "| ")).append("\n"); } else { var toString = responseBody.toString(); logString.append("| " + "respone body [Unknow Type] : " + responseBody.$className + " toString:" + toString).append("\n"); } console.log(logString); return response; }; }); }
if (Java.available) { console.log("available"); main(); }
|
效果不错
结束
像这种有强大开发能力的大厂会实现自己的 http 请求库,常规的针对 okhttp 的 hook 就不管用了,如果想抓到明文包可以考虑找到 app 在 java 层收发包函数,然后 hook 打印。