├── README.md ├── aweme_httpcat.js ├── okhttp_cat.js ├── okhttp_poker.js ├── okhttpfind.dex └── screen.gif /README.md: -------------------------------------------------------------------------------- 1 | # OkHttpLogger-Frida 2 | - Frida 实现拦截okhttp的脚本 3 | 4 | 5 | ### 使用说明 6 | 7 | > ① 首先将 `okhttpfind.dex` 拷贝到 `/data/local/tmp/` 目录下。 8 | > [okhttpfind.dex源码链接](https://github.com/siyujie/okhttp_find) 9 | 10 | 执行命令启动`frida -U -l okhttp_poker.js -f com.example.demo --no-pause` 可追加 `-o [output filepath]`保存到文件 11 | 12 | > ② 调用函数开始执行 13 | - **find() 要等完全启动并执行过网络请求后再进行调用** 14 | - **hold() 要等完全启动再进行调用** 15 | - **history() & resend() 只有可以重新发送的请求** 16 | 17 | #### 函数: 18 | ``` 19 | `find()` 检查是否使用了Okhttp & 是否可能被混淆 & 寻找okhttp3关键类及函数 20 | `switchLoader(\"okhttp3.OkHttpClient\")` 参数:静态分析到的okhttpclient类名 21 | `hold()` 开启HOOK拦截 22 | `history()` 打印可重新发送的请求 23 | `resend(index)` 重新发送请求 24 | ``` 25 | 26 | #### 原理: 27 | 由于所有使用的`okhttp`框架的App发出的请求都是通过`RealCall.java`发出的,那么我们可以hook此类拿到`request`和`response`, 28 | 也可以缓存下来每一个请求的`call`对象,进行再次请求,所以选择了此处进行hook。 29 | `find`前新增`check`,根据特征类寻找是否使用了`okhttp3`库,如果没有特征类,则说明没有使用`okhttp`; 30 | 找到特征类,说明使用了`okhttp`的库,并打印出是否被混淆。 31 | 32 | #### 抓取打印的样例 33 | 34 | ###### 例子1 35 | ``` 36 | ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 37 | | URL: https://lng.***.com/api/collect 38 | | 39 | | Method: POST 40 | | 41 | | Headers: 42 | | ┌─Content-Type: application/octet-stream; charset=utf-8 43 | | ┌─Content-Length: 3971 44 | | └─User-Agent: Dalvik/2.1.0 (Linux; U; Android 8.1.0; AOSP on msm8996 Build/OPM1.171019.011) Resolution/1080*1920 Version/6.59.0 Build/6590119 Device/(google;AOSP on msm8996) discover/6.59.0 45 | | 46 | | Body: 47 | | hex[........]//省略了,太长了 48 | | 49 | |--> END (binary body omitted -> isPlaintext) 50 | | 51 | | URL: https://lng.***.com/api/collect 52 | | 53 | | Status Code: 200 / 54 | | 55 | | Headers: 56 | | ┌─date: Sat, 29 Aug 2020 10:09:28 GMT 57 | | ┌─content-type: text/json; charset=utf-8 58 | | ┌─content-length: 41 59 | | ┌─access-control-allow-origin: * 60 | | ┌─access-control-allow-credentials: true 61 | | ┌─access-control-allow-methods: GET,POST,OPTIONS,HEAD 62 | | └─access-control-allow-headers: Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Client-Build,X-Client-Platform,X-Client-Version,X-Mx-ReqToken,X-Requested-With,X-Sign 63 | | 64 | | Body: 65 | | {"code":0,"msg":"Success","success":true} 66 | | 67 | |<-- END HTTP 68 | └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 69 | 70 | ``` 71 | ###### 例子2 72 | ``` 73 | ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 74 | | URL: http://****/searchByKeywork 75 | | 76 | | Method: POST 77 | | 78 | | Headers: 79 | | ┌─Content-Type: application/x-www-form-urlencoded 80 | | └─Content-Length: 20 81 | | 82 | | Body: 83 | | userId=*****&keyword=run 84 | | 85 | |--> END 86 | | 87 | | URL: http://****/searchByKeywork 88 | | 89 | | Status Code: 200 / 90 | | 91 | | Headers: 92 | | ┌─Content-Type: application/json;charset=UTF-8 93 | | ┌─Transfer-Encoding: chunked 94 | | └─Date: Sat, 29 Aug 2020 10:18:50 GMT 95 | | 96 | | Body: 97 | | {"code":1000,"message":"成功","result":[{"id":"jqjcRQFO2","name":"RUN","remark":"","shareKey":"dRbkPjn 98 | | J2sjVJTP0G","cover":null,"list":null,"index":0,"note":"更新至20200123期"}]} 99 | | 100 | |<-- END HTTP 101 | └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 102 | 103 | ``` 104 | 105 | #### okhttp_find结果样例 106 | 107 | ``` 108 | 109 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 被 混 淆 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 110 | 111 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Start Find~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 112 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Find Result~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 113 | var Cls_Call = "فمضﺝ.ثيغه"; 114 | var Cls_CallBack = "فمضﺝ.ﺙلﺩج"; 115 | var Cls_OkHttpClient = "فمضﺝ.ﻙﺫتك"; 116 | var Cls_Request = "فمضﺝ.ﺵكـﻅ"; 117 | var Cls_Response = "فمضﺝ.صرفج"; 118 | var Cls_ResponseBody = "فمضﺝ.ضتﻭذ"; 119 | var Cls_okio_Buffer = "ﻭﻍﺫﻉ.ﺵﺱﻭع"; 120 | var F_header_namesAndValues = "ﻝبـق"; 121 | var F_req_body = "ﺵﺱﻭع"; 122 | var F_req_headers = "بﺙذن"; 123 | var F_req_method = "ﺯﺵتﻝ"; 124 | var F_req_url = "ﻝبـق"; 125 | var F_rsp$builder_body = "ﻝجﻭق"; 126 | var F_rsp_body = "ﺹﻅﻍز"; 127 | var F_rsp_code = "ﻝجﻭق"; 128 | var F_rsp_headers = "غﻝزث"; 129 | var F_rsp_message = "فمضﺝ"; 130 | var F_rsp_request = "ثيغه"; 131 | var M_CallBack_onResponse = "onResponse"; 132 | var M_Call_enqueue = "ﻝبـق"; 133 | var M_Call_execute = "wait"; 134 | var M_Call_request = ""; 135 | var M_Client_newCall = "ﻝبـق"; 136 | var M_buffer_readByteArray = "ﺹﻅﻍز"; 137 | var M_contentType_charset = "ﻝبـق"; 138 | var M_reqbody_contentLength = "contentLength"; 139 | var M_reqbody_contentType = "contentType"; 140 | var M_reqbody_writeTo = "writeTo"; 141 | var M_rsp$builder_build = "ﻝبـق"; 142 | var M_rspBody_contentLength = "contentLength"; 143 | var M_rspBody_contentType = "contentType"; 144 | var M_rspBody_create = "create"; 145 | var M_rspBody_source = "source"; 146 | var M_rsp_newBuilder = "بﺙذن"; 147 | 148 | 149 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Find Complete!~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 150 | ``` 151 | 152 | #### 详情见动图吧!如有问题,请 issues 153 | - 因为`okhttp_poker.js`覆盖了`okhttp_cat.js`的所有功能,所以放弃了`okhttp_cat.js` 154 | 155 | #### 免责声明 156 | - 仅做学习交流! 请勿商用!! 157 | - 若因使用本服务与相关软件官方造成不必要的纠纷,本人概不负责! 158 | - 本人纯粹技术爱好,若侵相关公司的权益,请告知删除! 159 | #### 特别感谢 160 | - https://github.com/r0ysue/AndroidSecurityStudy 161 | -------------------------------------------------------------------------------- /aweme_httpcat.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 抖音的http请求打印小工具 版本 11.0.0 其他版本请自行适配吧,地方也不多 3 | * 4 | * 5 | */ 6 | 7 | 8 | var HttpOnly = "com.bytedance.frameworks.baselib.network.http.cronet.impl.b$a" 9 | var HttpOnlyMethod = "a" 10 | var HttpRequestClassName = "com.bytedance.retrofit2.client.Request" 11 | var HttpRequest = "e" 12 | 13 | var fileFilterArray = [".webp",".png",".jpg",".jpeg",".image"] 14 | 15 | function filterUrl(url) { 16 | for (var i = 0; i < fileFilterArray.length; i++) { 17 | if (url.indexOf(fileFilterArray[i]) != -1) { 18 | console.log(url + " ?? " + fileFilterArray[i]) 19 | return true; 20 | } 21 | } 22 | return false; 23 | } 24 | 25 | function splitLine(string, tag) { 26 | var lineLength = 150; 27 | var newSB = Java.use("java.lang.StringBuilder").$new() 28 | var newString = Java.use("java.lang.String").$new(string) 29 | var lineNum = Math.ceil(newString.length() / lineLength) 30 | for (var i = 0; i < lineNum; i++) { 31 | var start = i * lineLength; 32 | var end = (i + 1) * lineLength 33 | newSB.append(tag) 34 | if (end > newString.length()) { 35 | newSB.append(newString.substring(start, newString.length())) 36 | } else { 37 | newSB.append(newString.substring(start, end)) 38 | } 39 | newSB.append("\n") 40 | } 41 | return newSB.deleteCharAt(newSB.length() - 1).toString() 42 | } 43 | 44 | 45 | function aweme_httpcat() { 46 | Java.perform(function () { 47 | Java.openClassFile("/data/local/tmp/okhttpfind.dex").load() 48 | 49 | var JavaString = Java.use("java.lang.String") 50 | var JavaInteger = Java.use("java.lang.Integer") 51 | var JavaMap = Java.use("java.util.Map") 52 | var JavaLinkedTreeMap = Java.use("com.singleman.gson.internal.LinkedTreeMap") 53 | var ByteString = Java.use("com.singleman.okio.ByteString") 54 | var ByteArrayOutputStream = Java.use("java.io.ByteArrayOutputStream") 55 | var JSONObject = Java.use("org.json.JSONObject") 56 | 57 | var gson = Java.use("com.singleman.gson.Gson").$new() 58 | 59 | Java.use(HttpOnly)[HttpOnlyMethod].overload().implementation = function () { 60 | var response = this[HttpOnlyMethod]() 61 | var logString = Java.use("java.lang.StringBuffer").$new() 62 | //Request 63 | try { 64 | var requestField = null 65 | var fields = this.class.getDeclaredFields() 66 | for(var i=0;i>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Invoke .....") 87 | if(param_method.getName() == M_Interceptor_intercept){ 88 | 89 | try { 90 | 91 | var chain = getWrapper(param_arg[0]); 92 | 93 | var request = chain[M_chain_request]() 94 | var requestBody = request[M_req_body](); 95 | var hasRequestBody = true 96 | if(null == requestBody){ 97 | hasRequestBody = false 98 | } 99 | var connection = chain[M_chain_connection]() 100 | 101 | var protocol = "http/1.1" 102 | if(null != connection){ 103 | protocol = connection[M_connection_protocol]() 104 | } 105 | 106 | var httpUrl = request[M_req_url]() 107 | 108 | console.log(""); 109 | console.log("┌─────────────────────────────────────────────────────────────────────────────────────"); 110 | 111 | var requestStartMessage = "--->"+request[M_req_method]()+" "+httpUrl+" "+protocol 112 | if(hasRequestBody){ 113 | requestStartMessage += "("+requestBody[M_reqbody_contentLength]()+"-byte body)" 114 | } 115 | console.log("| "+requestStartMessage) 116 | 117 | if(hasRequestBody){ 118 | var contentType = requestBody[M_reqbody_contentType]() 119 | if(null != contentType){ 120 | console.log("| "+"Content-Type: " + contentType) 121 | } 122 | var contentLength = requestBody[M_reqbody_contentLength]() 123 | if(contentLength != -1){ 124 | console.log("| "+"Content-Length: "+contentLength) 125 | } 126 | } 127 | var requestHeaders = request[M_req_headers]() 128 | var headersSize = requestHeaders[M_header_size]() 129 | console.log("| headersSize : "+headersSize) 130 | for(var i=0;i END " + request[M_req_method]()); 139 | }else if(bodyEncoded(requestHeaders)){ 140 | console.log("|"+"--> END " + request[M_req_method]() + " (encoded body omitted)"); 141 | }else { 142 | var BufferCls = Java.use(Cls_okio_Buffer) 143 | var buffer = BufferCls.$new() 144 | requestBody[M_reqbody_writeTo](buffer) 145 | 146 | var reqByteString = getByteString(buffer) 147 | 148 | var charset = defChatset 149 | var contentType = requestBody[M_reqbody_contentType]() 150 | if(null != contentType){ 151 | var appcharset = contentType[M_contentType_charset](); 152 | if(null != appcharset){ 153 | charset = appcharset; 154 | } 155 | } 156 | console.log("|") 157 | if(isPlaintext(reqByteString)){ 158 | console.log("|"+readBufferString(reqByteString,charset)) 159 | console.log("|"+"--> END "+request[M_req_method]()+" ("+requestBody[M_reqbody_contentLength]()+"-byte body)") 160 | }else{ 161 | console.log("|"+"--> END "+request[M_req_method]()+" (binary "+requestBody[M_reqbody_contentLength]()+"-byte body omitted)") 162 | } 163 | } 164 | var startNs = new Date().getTime() 165 | 166 | var response = null; 167 | try { 168 | response = chain[M_chain_proceed](request) 169 | } catch (error) { 170 | console.log("M_chain_proceed error : "+ error) 171 | } 172 | 173 | if(null == response){ 174 | console.log("| <-- HTTP FAILED") 175 | return param_method.invoke(param_obj,param_arg) 176 | } 177 | 178 | var tookMs = new Date().getTime() - startNs 179 | var responseBody = response[M_rsp_body]() 180 | // console.log("responseBody : "+responseBody) 181 | var contentLength = responseBody[M_rspBody_contentLength]() 182 | // console.log("contentLength : "+contentLength) 183 | var bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length"; 184 | 185 | console.log("|<---"+response[M_rsp_code]()+" "+ response[M_rsp_message]()+" "+ 186 | response[M_rsp_request]()[M_req_url]()+ 187 | " (" + tookMs + "ms ," + bodySize + " body"+")" 188 | ) 189 | 190 | var resp_headers = response[M_rsp_headers]() 191 | var respHeaderSize = resp_headers[M_header_size]() 192 | for (var i = 0; i < respHeaderSize;i++) { 193 | console.log("| "+resp_headers[M_header_name](i)+": "+resp_headers[M_header_value](i)) 194 | } 195 | var hasbody = !hasBody(response) 196 | // console.log("hasbody : ",hasbody) 197 | if(hasbody){ 198 | console.log("| No Body : ",response) 199 | console.log("|"+"<-- END HTTP") 200 | }else if(bodyEncoded(resp_headers)){ 201 | console.log("|"+"<-- END HTTP (encoded body omitted)") 202 | }else{ 203 | var source = responseBody[M_rspBody_source]() 204 | 205 | var rspByteString = getByteString(source) 206 | 207 | var charset = defChatset 208 | var contentType = responseBody[M_rspBody_contentType]() 209 | if(null != contentType){ 210 | var appcharset = contentType[M_contentType_charset]() 211 | if(null != appcharset){ 212 | charset = appcharset 213 | } 214 | } 215 | if(!isPlaintext(rspByteString)){ 216 | console.log("| "); 217 | console.log("|"+"<-- END HTTP (binary " + rspByteString.size() + "-byte body omitted)"); 218 | return param_method.invoke(param_obj,param_arg); 219 | } 220 | 221 | if (contentLength != 0) { 222 | console.log("| "+response) 223 | console.log("| "+JSON.stringify(JSON.parse(readBufferString(rspByteString, charset)), null, 4)); 224 | console.log("| "); 225 | } 226 | console.log("|"+"<-- END HTTP (" + rspByteString.size() + "-byte body)"); 227 | 228 | } 229 | console.log("└───────────────────────────────────────────────────────────────────────────────────────"); 230 | console.log(""); 231 | var content = responseBody[M_rspBody_string]() 232 | var mediaType = responseBody[M_rspBody_contentType]() 233 | var class_responseBody = Java.use(Cls_ResponseBody) 234 | var newBody = class_responseBody[M_rspBody_create](mediaType, content) 235 | var newBuilder = response[M_rsp_newBuilder]() 236 | return newBuilder[M_rsp$builder_body](newBody)[M_rsp$builder_build]() 237 | } catch (error) { 238 | console.log("Error MyInvocationHandler : ",error) 239 | } 240 | 241 | }else{ 242 | // console.log(">>>>>>>>>>>>error "); 243 | } 244 | return param_method.invoke(param_obj,param_arg); 245 | } 246 | } 247 | 248 | }) 249 | 250 | loggerInterceptor = Proxy.newProxyInstance(classLoader,interfaces,mInvocationHandler.$new()) 251 | 252 | }) 253 | 254 | return loggerInterceptor; 255 | } 256 | 257 | 258 | function getWrapper(handle){ 259 | var chooseInstance = null; 260 | Java.choose(handle.getClass().getName(),{ 261 | 262 | onMatch:function(instance){ 263 | if(instance.hashCode() == handle.hashCode()){ 264 | chooseInstance = instance 265 | } 266 | }, 267 | onComplete : function(){ 268 | // console.log("invokeMethod choose complete") 269 | } 270 | }) 271 | return chooseInstance; 272 | } 273 | 274 | function bodyEncoded(headers){ 275 | if(null == headers) return false; 276 | var javaString = Java.use("java.lang.String") 277 | var contentEncoding = headers[M_header_get]("Content-Encoding") 278 | return contentEncoding != null && !contentEncoding.equalsIgnoreCase(javaString.$new("identity")) 279 | 280 | } 281 | 282 | 283 | function hasBody(response){ 284 | var javaString = Java.use("java.lang.String") 285 | var m = response[M_rsp_request]()[M_req_method](); 286 | // console.log(" >>>> hasBody mmmm : ",m) 287 | if(javaString.$new("HEAD").equals(m)){ 288 | return false; 289 | } 290 | var Transfer_Encoding = ""; 291 | var resp_headers = response[M_rsp_headers]() 292 | var respHeaderSize = resp_headers[M_header_size]() 293 | for (var i = 0; i < respHeaderSize;i++) { 294 | if(javaString.$new("Transfer-Encoding").equals(resp_headers[M_header_name](i))){ 295 | Transfer_Encoding = resp_headers[M_header_value](i); 296 | break 297 | } 298 | } 299 | // console.log(" >>>> hasBody Transfer_Encoding : ",Transfer_Encoding) 300 | var code = response[M_rsp_code]() 301 | // console.log(" >>>> hasBody code : ",code) 302 | if(((code >= 100 && code < 200) || code == 204 || code == 304) 303 | && response[M_rspBody_contentLength] == -1 304 | && !javaString.$new("chunked").equalsIgnoreCase(Transfer_Encoding) 305 | ){ 306 | return false; 307 | } 308 | return true; 309 | } 310 | 311 | 312 | 313 | function isPlaintext(byteString){ 314 | try { 315 | var bufferSize = byteString.size() 316 | // console.log(" isPlaintext > bufferSize >>> "+ bufferSize) 317 | 318 | var buffer = NewBuffer(byteString) 319 | 320 | for (var i = 0; i < 16; i++) { 321 | if(bufferSize == 0){ 322 | break 323 | } 324 | var codePoint = buffer.readUtf8CodePoint() 325 | // console.log(" isPlaintext > codePoint >>> "+ codePoint) 326 | var Character = Java.use("java.lang.Character") 327 | //console.log(" isPlaintext ???? "+(Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint))) 328 | if(Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)){ 329 | return false; 330 | } 331 | } 332 | return true; 333 | } catch (error) { 334 | // return false; 335 | return true; 336 | } 337 | 338 | } 339 | 340 | 341 | function getByteString(buffer){ 342 | var bytearray = buffer[M_buffer_readByteArray](); 343 | var byteString = Java.use("com.singleman.okio.ByteString").of(bytearray) 344 | return byteString; 345 | } 346 | 347 | function NewBuffer(byteString){ 348 | var bufferCls = Java.use("com.singleman.okio.Buffer"); 349 | var buffer = bufferCls.$new() 350 | byteString.write(buffer) 351 | return buffer; 352 | } 353 | 354 | 355 | function readBufferString(byteString, chatset){ 356 | 357 | var byteArray = byteString.toByteArray(); 358 | 359 | var str = Java.use("java.lang.String").$new(byteArray,chatset) 360 | 361 | return str; 362 | } 363 | 364 | function findClassLoader(){ 365 | Java.perform(function(){ 366 | Java.enumerateClassLoaders({ 367 | 368 | onMatch:function(loader){ 369 | // console.log("loader : "+loader) 370 | try { 371 | if(loader.findClass(Cls_OkHttpClient$Builder)){ 372 | Java.classFactory.loader = loader 373 | console.log("") 374 | console.log("Change ClassLoader Success !") 375 | console.log("") 376 | } 377 | } catch (error) { 378 | // console.log(error) 379 | } 380 | 381 | }, 382 | onComplete:function(){ 383 | console.log("") 384 | console.log("CenumerateClassLoaders onComplete !") 385 | console.log("") 386 | } 387 | 388 | }) 389 | 390 | }) 391 | } 392 | 393 | 394 | 395 | function hookbychoose(){ 396 | 397 | Java.perform(function(){ 398 | 399 | var okhttpDex = Java.openClassFile("/mnt/sdcard/okhttpfind.dex").load() 400 | 401 | Java.choose(Cls_OkHttpClient,{ 402 | onMatch:function(instance){ 403 | 404 | try { 405 | 406 | var interceptorsF = instance.class.getDeclaredField(F_Client_interceptors) 407 | interceptorsF.setAccessible(true) 408 | var interceptors = interceptorsF.get(instance) 409 | 410 | var newInterceptors = Java.use("java.util.ArrayList").$new() 411 | if(null != interceptors){ 412 | newInterceptors.addAll(interceptors) 413 | } 414 | if(newInterceptors.size() > 0){ 415 | var interImplClassName = newInterceptors.get(0).getClass().getName() 416 | console.log("already Interceptor : "+interImplClassName) 417 | var proxyObj = newInterceptor(interImplClassName); 418 | newInterceptors.add(proxyObj) 419 | console.log("") 420 | console.log("Add Interceptor Success !! ") 421 | console.log("") 422 | } 423 | instance[F_Client_interceptors].value = newInterceptors 424 | 425 | } catch (error) { 426 | 427 | console.log("Error : "+ error) 428 | 429 | } 430 | 431 | }, 432 | onComplete : function(){ 433 | } 434 | }) 435 | 436 | }) 437 | 438 | } 439 | 440 | 441 | 442 | function hookbyuse(){ 443 | 444 | Java.perform(function(){ 445 | 446 | var okhttpDex = Java.openClassFile("/mnt/sdcard/okhttpfind.dex").load() 447 | 448 | Java.use(Cls_OkHttpClient$Builder)[M_Builder_build].overload().implementation = function(){ 449 | try { 450 | 451 | var interceptorsF = this.class.getDeclaredField(F_Builder_interceptors) 452 | interceptorsF.setAccessible(true) 453 | var interceptors = interceptorsF.get(this) 454 | 455 | var newInterceptors = Java.use("java.util.ArrayList").$new() 456 | if(null != interceptors){ 457 | newInterceptors.addAll(interceptors) 458 | } 459 | if(newInterceptors.size() > 0){ 460 | // var interImplClassName = newInterceptors.get(0).getClass().getName() 461 | // console.log("already Interceptor : "+interImplClassName) 462 | // var proxyObj = newInterceptor(interImplClassName); 463 | var proxyObj = newInterceptor("com.xingin.xhs.model.b.a"); 464 | newInterceptors.add(proxyObj) 465 | console.log("Add Interceptor Success !! ") 466 | }else{ 467 | var proxyObj = newInterceptor("com.xingin.xhs.model.b.a"); 468 | newInterceptors.add(proxyObj) 469 | console.log("....Add Interceptor Success !! ") 470 | } 471 | this[F_Builder_interceptors].value = newInterceptors 472 | 473 | } catch (error) { 474 | 475 | console.log("Error : "+ error) 476 | 477 | } 478 | 479 | return this[M_Builder_build]() 480 | } 481 | 482 | }) 483 | 484 | } 485 | 486 | // setImmediate(hookbychoose) 487 | setImmediate(hookbyuse) 488 | */ -------------------------------------------------------------------------------- /okhttp_poker.js: -------------------------------------------------------------------------------- 1 | /** 2 | 使用说明 3 | 首先将 okhttpfind.dex 拷贝到 /data/local/tmp/ 目录下 4 | 例:frida -U -l okhttp_poker.js -f com.example.demo --no-pause 5 | 接下来使用okhttp的所有请求将被拦截并打印出来; 6 | 扩展函数: 7 | find() 检查是否使用了Okhttp & 是否可能被混淆 & 寻找okhttp3关键类及函数 8 | switchLoader(\"okhttp3.OkHttpClient\") 参数:静态分析到的okhttpclient类名 9 | hold() 开启HOOK拦截 10 | history() 打印可重新发送的请求 11 | resend(index) 重新发送请求 12 | 13 | 备注 : okhtpfind.dex 内包含了 更改了包名的okio以及Gson,以及Java写的寻找okhttp特征的代码。 14 | okhttpfind.dex 源码链接 https://github.com/siyujie/okhttp_find 15 | 16 | 原理:由于所有使用的okhttp框架的App发出的请求都是通过RealCall.java发出的,那么我们可以hook此类拿到request和response, 17 | 也可以缓存下来每一个请求的call对象,进行再次请求,所以选择了此处进行hook。 18 | 19 | */ 20 | var Cls_Call = "okhttp3.Call"; 21 | var Cls_CallBack = "okhttp3.Callback"; 22 | var Cls_OkHttpClient = "okhttp3.OkHttpClient"; 23 | var Cls_Request = "okhttp3.Request"; 24 | var Cls_Response = "okhttp3.Response"; 25 | var Cls_ResponseBody = "okhttp3.ResponseBody"; 26 | var Cls_okio_Buffer = "okio.Buffer"; 27 | var F_header_namesAndValues = "namesAndValues"; 28 | var F_req_body = "body"; 29 | var F_req_headers = "headers"; 30 | var F_req_method = "method"; 31 | var F_req_url = "url"; 32 | var F_rsp$builder_body = "body"; 33 | var F_rsp_body = "body"; 34 | var F_rsp_code = "code"; 35 | var F_rsp_headers = "headers"; 36 | var F_rsp_message = "message"; 37 | var F_rsp_request = "request"; 38 | var M_CallBack_onFailure = "onFailure"; 39 | var M_CallBack_onResponse = "onResponse"; 40 | var M_Call_enqueue = "enqueue"; 41 | var M_Call_execute = "execute"; 42 | var M_Call_request = "request"; 43 | var M_Client_newCall = "newCall"; 44 | var M_buffer_readByteArray = "readByteArray"; 45 | var M_contentType_charset = "charset"; 46 | var M_reqbody_contentLength = "contentLength"; 47 | var M_reqbody_contentType = "contentType"; 48 | var M_reqbody_writeTo = "writeTo"; 49 | var M_rsp$builder_build = "build"; 50 | var M_rspBody_contentLength = "contentLength"; 51 | var M_rspBody_contentType = "contentType"; 52 | var M_rspBody_create = "create"; 53 | var M_rspBody_source = "source"; 54 | var M_rsp_newBuilder = "newBuilder"; 55 | 56 | //---------------------------------- 57 | var JavaStringWapper = null; 58 | var JavaIntegerWapper = null; 59 | var JavaStringBufferWapper = null; 60 | var GsonWapper = null; 61 | var ListWapper = null; 62 | var ArrayListWapper = null; 63 | var ArraysWapper = null; 64 | var CharsetWapper = null; 65 | var CharacterWapper = null; 66 | 67 | var OkioByteStrngWapper = null; 68 | var OkioBufferWapper = null; 69 | 70 | var OkHttpClientWapper = null; 71 | var ResponseBodyWapper = null; 72 | var BufferWapper = null; 73 | var Utils = null; 74 | //---------------------------------- 75 | var CallCache = [] 76 | var hookedArray = [] 77 | var filterArray = ["JPG", "jpg", "PNG", "png", "WEBP", "webp", "JPEG", "jpeg", "GIF", "gif",".zip", ".data"] 78 | 79 | 80 | function buildNewResponse(responseObject) { 81 | var newResponse = null; 82 | Java.perform(function () { 83 | try { 84 | var logString = JavaStringBufferWapper.$new() 85 | 86 | logString.append("").append("\n"); 87 | logString.append("┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────").append("\n"); 88 | 89 | newResponse = printAll(responseObject, logString) 90 | 91 | logString.append("└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────").append("\n"); 92 | logString.append("").append("\n"); 93 | 94 | console.log(logString) 95 | } catch (error) { 96 | console.log("printAll ERROR : " + error); 97 | } 98 | }) 99 | return newResponse; 100 | } 101 | 102 | 103 | function printAll(responseObject, logString) { 104 | try { 105 | var request = getFieldValue(responseObject, F_rsp_request) 106 | printerRequest(request, logString) 107 | } catch (error) { 108 | console.log("print request error : ", error.stack) 109 | return responseObject; 110 | } 111 | var newResponse = printerResponse(responseObject, logString) 112 | return newResponse; 113 | } 114 | 115 | 116 | function printerRequest(request, logString) { 117 | var defChatset = CharsetWapper.forName("UTF-8") 118 | //URL 119 | var httpUrl = getFieldValue(request, F_req_url) 120 | logString.append("| URL: " + httpUrl).append("\n") 121 | logString.append("|").append("\n") 122 | logString.append("| Method: " + getFieldValue(request, F_req_method)).append("\n") 123 | logString.append("|").append("\n") 124 | var requestBody = getFieldValue(request, F_req_body); 125 | var hasRequestBody = true 126 | if (null == requestBody) { 127 | hasRequestBody = false 128 | } 129 | //Headers 130 | var requestHeaders = getFieldValue(request, F_req_headers) 131 | var headersList = headersToList(requestHeaders) 132 | var headersSize = getHeaderSize(headersList) 133 | 134 | logString.append("| Request Headers: ").append("" + headersSize).append("\n") 135 | if (hasRequestBody) { 136 | var requestBody = getWrapper(requestBody) 137 | var contentType = requestBody[M_reqbody_contentType]() 138 | if (null != contentType) { 139 | logString.append("| ┌─" + "Content-Type: " + contentType).append("\n") 140 | } 141 | var contentLength = requestBody[M_reqbody_contentLength]() 142 | if (contentLength != -1) { 143 | var tag = headersSize == 0 ? "└─" : "┌─" 144 | logString.append("| " + tag + "Content-Length: " + contentLength).append("\n") 145 | } 146 | } 147 | if (headersSize == 0) { 148 | logString.append("| no headers").append("\n") 149 | } 150 | for (var i = 0; i < headersSize; i++) { 151 | var name = getHeaderName(headersList, i) 152 | if (!JavaStringWapper.$new("Content-Type").equalsIgnoreCase(name) && !JavaStringWapper.$new("Content-Length").equalsIgnoreCase(name)) { 153 | var value = getHeaderValue(headersList, i) 154 | var tag = i == (headersSize - 1) ? "└─" : "┌─" 155 | logString.append("| " + tag + name + ": " + value).append("\n") 156 | } 157 | } 158 | var shielded = filterUrl(httpUrl.toString()) 159 | if (shielded) { 160 | logString.append("|" + " File Request Body Omit.....").append("\n") 161 | return; 162 | } 163 | logString.append("|").append("\n") 164 | if (!hasRequestBody) { 165 | logString.append("|" + "--> END ").append("\n") 166 | } else if (bodyEncoded(headersList)) { 167 | logString.append("|" + "--> END (encoded body omitted > bodyEncoded)").append("\n") 168 | } else { 169 | logString.append("| Request Body:").append("\n") 170 | var buffer = BufferWapper.$new() 171 | requestBody[M_reqbody_writeTo](buffer) 172 | var reqByteString = getByteString(buffer) 173 | 174 | var charset = defChatset 175 | var contentType = requestBody[M_reqbody_contentType]() 176 | if (null != contentType) { 177 | var appcharset = contentType[M_contentType_charset](defChatset); 178 | if (null != appcharset) { 179 | charset = appcharset; 180 | } 181 | } 182 | //LOG Request Body 183 | try { 184 | if (isPlaintext(reqByteString)) { 185 | logString.append(splitLine(readBufferString(reqByteString, charset), "| ")).append("\n") 186 | logString.append("|").append("\n") 187 | logString.append("|" + "--> END ").append("\n") 188 | } else { 189 | logString.append(splitLine(hexToUtf8(reqByteString.hex()), "| ")).append("\n") 190 | logString.append("|").append("\n"); 191 | logString.append("|" + "--> END (binary body omitted -> isPlaintext)").append("\n") 192 | } 193 | } catch (error) { 194 | logString.append(splitLine(hexToUtf8(reqByteString.hex()), "| ")).append("\n") 195 | logString.append("|").append("\n"); 196 | logString.append("|" + "--> END (binary body omitted -> isPlaintext)").append("\n") 197 | } 198 | } 199 | logString.append("|").append("\n"); 200 | } 201 | 202 | 203 | function printerResponse(response, logString) { 204 | var newResponse = null; 205 | try { 206 | var defChatset = CharsetWapper.forName("UTF-8") 207 | 208 | var request = getFieldValue(response, F_rsp_request) 209 | var url = getFieldValue(request, F_req_url) 210 | var shielded = filterUrl(url.toString()) 211 | if (shielded) { 212 | logString.append("|" + " File Response Body Omit.....").append("\n") 213 | return response; 214 | } 215 | //URL 216 | logString.append("| URL: " + url).append("\n") 217 | logString.append("|").append("\n") 218 | logString.append("| Status Code: " + getFieldValue(response, F_rsp_code) + " / " + getFieldValue(response, F_rsp_message)).append("\n") 219 | logString.append("|").append("\n") 220 | var responseBodyObj = getFieldValue(response, F_rsp_body) 221 | var responseBody = getWrapper(responseBodyObj) 222 | var contentLength = responseBody[M_rspBody_contentLength]() 223 | //Headers 224 | var resp_headers = getFieldValue(response, F_rsp_headers) 225 | var respHeadersList = headersToList(resp_headers) 226 | var respHeaderSize = getHeaderSize(respHeadersList) 227 | logString.append("| Response Headers: ").append("" + respHeaderSize).append("\n") 228 | if (respHeaderSize == 0) { 229 | logString.append("| no headers").append("\n") 230 | } 231 | for (var i = 0; i < respHeaderSize; i++) { 232 | var tag = i == (respHeaderSize - 1) ? "└─" : "┌─" 233 | logString.append("| " + tag + getHeaderName(respHeadersList, i) + ": " + getHeaderValue(respHeadersList, i)).append("\n") 234 | } 235 | //Body 236 | var content = ""; 237 | var nobody = !hasBody(response, respHeadersList) 238 | if (nobody) { 239 | logString.append("| No Response Body : " + response).append("\n") 240 | logString.append("|" + "<-- END HTTP").append("\n") 241 | } else if (bodyEncoded(respHeadersList)) { 242 | logString.append("|" + "<-- END HTTP (encoded body omitted)").append("\n") 243 | } else { 244 | logString.append("| ").append("\n"); 245 | logString.append("| Response Body:").append("\n") 246 | var source = responseBody[M_rspBody_source]() 247 | var rspByteString = getByteString(source) 248 | var charset = defChatset 249 | var contentType = responseBody[M_rspBody_contentType]() 250 | if (null != contentType) { 251 | var appcharset = contentType[M_contentType_charset](defChatset) 252 | if (null != appcharset) { 253 | charset = appcharset 254 | } 255 | } 256 | //newResponse 257 | var mediaType = responseBody[M_rspBody_contentType]() 258 | var newBody = null; 259 | try { 260 | newBody = ResponseBodyWapper[M_rspBody_create](mediaType, rspByteString.toByteArray()) 261 | } catch (error) { 262 | newBody = ResponseBodyWapper[M_rspBody_create](mediaType, readBufferString(rspByteString, charset)) 263 | } 264 | var newBuilder = null; 265 | if ("" == M_rsp_newBuilder) { 266 | var ResponseBuilderClazz = response.class.getDeclaredClasses()[0] 267 | newBuilder = Java.use(ResponseBuilderClazz.getName()).$new(response) 268 | } else { 269 | newBuilder = response[M_rsp_newBuilder]() 270 | } 271 | var bodyField = newBuilder.class.getDeclaredField(F_rsp$builder_body) 272 | bodyField.setAccessible(true) 273 | bodyField.set(newBuilder, newBody) 274 | newResponse = newBuilder[M_rsp$builder_build]() 275 | 276 | if (!isPlaintext(rspByteString)) { 277 | logString.append("|" + "<-- END HTTP (binary body omitted)").append("\n"); 278 | } 279 | if (contentLength != 0) { 280 | try { 281 | var content = readBufferString(rspByteString, charset) 282 | logString.append(splitLine(content, "| ")).append("\n") 283 | } catch (error) { 284 | logString.append(splitLine(hexToUtf8(rspByteString.hex()), "| ")).append("\n") 285 | } 286 | 287 | logString.append("| ").append("\n"); 288 | } 289 | logString.append("|" + "<-- END HTTP").append("\n"); 290 | } 291 | } catch (error) { 292 | logString.append("print response error : " + error).append("\n") 293 | if (null == newResponse) { 294 | return response; 295 | } 296 | } 297 | return newResponse; 298 | } 299 | 300 | /** 301 | * hex to string 302 | */ 303 | function hexToUtf8(hex) { 304 | try { 305 | return decodeURIComponent('%' + hex.match(/.{1,2}/g).join('%')); 306 | } catch (error) { 307 | return "hex[" + hex + "]"; 308 | } 309 | } 310 | 311 | /** 312 | */ 313 | function getFieldValue(object, fieldName) { 314 | var field = object.class.getDeclaredField(fieldName); 315 | field.setAccessible(true) 316 | var fieldValue = field.get(object) 317 | if (null == fieldValue) { 318 | return null; 319 | } 320 | var FieldClazz = Java.use(fieldValue.$className) 321 | var fieldValueWapper = Java.cast(fieldValue, FieldClazz) 322 | return fieldValueWapper 323 | } 324 | /** 325 | */ 326 | function getWrapper(javaobject) { 327 | return Java.cast(javaobject, Java.use(javaobject.$className)) 328 | } 329 | 330 | /** 331 | */ 332 | function headersToList(headers) { 333 | var gson = GsonWapper.$new() 334 | var namesAndValues = getFieldValue(headers, F_header_namesAndValues) 335 | var jsonString = gson.toJson(namesAndValues) 336 | var namesAndValuesList = Java.cast(gson.fromJson(jsonString, ListWapper.class), ListWapper) 337 | return namesAndValuesList; 338 | } 339 | 340 | function getHeaderSize(namesAndValuesList) { 341 | return namesAndValuesList.size() / 2 342 | } 343 | 344 | function getHeaderName(namesAndValuesList, index) { 345 | return namesAndValuesList.get(index * 2) 346 | } 347 | function getHeaderValue(namesAndValuesList, index) { 348 | return namesAndValuesList.get((index * 2) + 1) 349 | } 350 | 351 | function getByHeader(namesAndValuesList, name) { 352 | var nameString = JavaStringWapper.$new(name) 353 | Java.perform(function () { 354 | var length = namesAndValuesList.size() 355 | var nameByList = ""; 356 | do { 357 | length -= 2; 358 | if (length < 0) { 359 | return null; 360 | } 361 | // console.log("namesAndValuesList: "+namesAndValuesList.$className) 362 | nameByList = namesAndValuesList.get(JavaIntegerWapper.valueOf(length).intValue()) 363 | } while (!nameString.equalsIgnoreCase(nameByList)); 364 | return namesAndValuesList.get(length + 1); 365 | 366 | }) 367 | } 368 | 369 | function bodyEncoded(namesAndValuesList) { 370 | if (null == namesAndValuesList) return false; 371 | var contentEncoding = getByHeader(namesAndValuesList, "Content-Encoding") 372 | var bodyEncoded = contentEncoding != null && !JavaStringWapper.$new("identity").equalsIgnoreCase(contentEncoding) 373 | return bodyEncoded 374 | 375 | } 376 | 377 | function hasBody(response, namesAndValuesList) { 378 | var request = getFieldValue(response, F_rsp_request) 379 | var m = getFieldValue(request, F_req_method); 380 | if (JavaStringWapper.$new("HEAD").equals(m)) { 381 | return false; 382 | } 383 | var Transfer_Encoding = ""; 384 | var respHeaderSize = getHeaderSize(namesAndValuesList) 385 | for (var i = 0; i < respHeaderSize; i++) { 386 | if (JavaStringWapper.$new("Transfer-Encoding").equals(getHeaderName(namesAndValuesList, i))) { 387 | Transfer_Encoding = getHeaderValue(namesAndValuesList, i); 388 | break 389 | } 390 | } 391 | var code = getFieldValue(response, F_rsp_code) 392 | if (((code >= 100 && code < 200) || code == 204 || code == 304) 393 | && response[M_rspBody_contentLength] == -1 394 | && !JavaStringWapper.$new("chunked").equalsIgnoreCase(Transfer_Encoding) 395 | ) { 396 | return false; 397 | } 398 | return true; 399 | } 400 | 401 | 402 | function isPlaintext(byteString) { 403 | try { 404 | var bufferSize = byteString.size() 405 | var buffer = NewBuffer(byteString) 406 | for (var i = 0; i < 16; i++) { 407 | if (bufferSize == 0) { 408 | console.log("bufferSize == 0") 409 | break 410 | } 411 | var codePoint = buffer.readUtf8CodePoint() 412 | if (CharacterWapper.isISOControl(codePoint) && !CharacterWapper.isWhitespace(codePoint)) { 413 | return false; 414 | } 415 | } 416 | return true; 417 | } catch (error) { 418 | // console.log(error) 419 | // console.log(Java.use("android.util.Log").getStackTraceString(error)) 420 | return false; 421 | } 422 | } 423 | 424 | function getByteString(buffer) { 425 | var bytearray = buffer[M_buffer_readByteArray](); 426 | var byteString = OkioByteStrngWapper.of(bytearray) 427 | return byteString; 428 | } 429 | 430 | function NewBuffer(byteString) { 431 | var buffer = OkioBufferWapper.$new() 432 | byteString.write(buffer) 433 | return buffer; 434 | } 435 | 436 | function readBufferString(byteString, chatset) { 437 | var byteArray = byteString.toByteArray(); 438 | var str = JavaStringWapper.$new(byteArray, chatset) 439 | return str; 440 | } 441 | 442 | function splitLine(string, tag) { 443 | var newSB = JavaStringBufferWapper.$new() 444 | var newString = JavaStringWapper.$new(string) 445 | var lineNum = Math.ceil(newString.length() / 150) 446 | for (var i = 0; i < lineNum; i++) { 447 | var start = i * 150; 448 | var end = (i + 1) * 150 449 | newSB.append(tag) 450 | if (end > newString.length()) { 451 | newSB.append(newString.substring(start, newString.length())) 452 | } else { 453 | newSB.append(newString.substring(start, end)) 454 | } 455 | newSB.append("\n") 456 | } 457 | var lineStr = ""; 458 | if (newSB.length() > 0) { 459 | lineStr = newSB.deleteCharAt(newSB.length() - 1).toString() 460 | } 461 | return lineStr 462 | } 463 | 464 | /** 465 | * 466 | */ 467 | function alreadyHook(str) { 468 | for (var i = 0; i < hookedArray.length; i++) { 469 | if (str == hookedArray[i]) { 470 | return true; 471 | } 472 | } 473 | return false; 474 | } 475 | 476 | /** 477 | * 478 | */ 479 | function filterUrl(url) { 480 | for (var i = 0; i < filterArray.length; i++) { 481 | if (url.indexOf(filterArray[i]) != -1) { 482 | // console.log(url + " ?? " + filterArray[i]) 483 | return true; 484 | } 485 | } 486 | return false; 487 | } 488 | 489 | function hookRealCall(realCallClassName) { 490 | Java.perform(function () { 491 | console.log(" ........... hookRealCall : " + realCallClassName) 492 | var RealCall = Java.use(realCallClassName) 493 | if ("" != Cls_CallBack) { 494 | //异步 495 | RealCall[M_Call_enqueue].overload(Cls_CallBack).implementation = function (callback) { 496 | // console.log("-------------------------------------HOOK SUCCESS 异步--------------------------------------------------") 497 | var realCallBack = Java.use(callback.$className) 498 | realCallBack[M_CallBack_onResponse].overload(Cls_Call,Cls_Response).implementation = function(call, response){ 499 | var newResponse = buildNewResponse(response) 500 | this[M_CallBack_onResponse](call,newResponse) 501 | } 502 | this[M_Call_enqueue](callback) 503 | realCallBack.$dispose 504 | } 505 | } 506 | //同步 507 | RealCall[M_Call_execute].overload().implementation = function () { 508 | // console.log("-------------------------------------HOOK SUCCESS 同步--------------------------------------------------") 509 | var response = this[M_Call_execute]() 510 | var newResponse = buildNewResponse(response) 511 | return newResponse; 512 | } 513 | }) 514 | } 515 | 516 | /** 517 | * check className & filter 518 | */ 519 | function checkClass(name) { 520 | if (name.startsWith("com.") 521 | || name.startsWith("cn.") 522 | || name.startsWith("io.") 523 | || name.startsWith("org.") 524 | || name.startsWith("android") 525 | || name.startsWith("kotlin") 526 | || name.startsWith("[") 527 | || name.startsWith("java") 528 | || name.startsWith("sun.") 529 | || name.startsWith("net.") 530 | || name.indexOf(".") < 0 531 | || name.startsWith("dalvik") 532 | 533 | ) { 534 | return false; 535 | } 536 | return true; 537 | } 538 | 539 | /** 540 | * print request history 541 | */ 542 | function history() { 543 | Java.perform(function () { 544 | try { 545 | console.log("") 546 | console.log("History Size : " + CallCache.length) 547 | for (var i = 0; i < CallCache.length; i++) { 548 | var call = CallCache[i] 549 | if ("" != M_Call_request) { 550 | console.log("-----> index[" + i + "]" + " >> " + call[M_Call_request]()) 551 | } else { 552 | console.log("-----> index[" + i + "]" + " ???? M_Call_execute = \"\"") 553 | } 554 | console.log("") 555 | } 556 | console.log("") 557 | } catch (error) { 558 | console.log(error) 559 | } 560 | }) 561 | } 562 | 563 | /** 564 | * resend request 565 | */ 566 | function resend(index) { 567 | Java.perform(function () { 568 | try { 569 | console.log("resend >> " + index) 570 | var call = CallCache[index] 571 | if ("" != M_Call_execute) { 572 | call[M_Call_execute]() 573 | } else { 574 | console.log("M_Call_execute = null") 575 | } 576 | } catch (error) { 577 | console.log("Error : " + error) 578 | } 579 | }) 580 | } 581 | 582 | /** 583 | * 开启HOOK拦截 584 | */ 585 | function hold() { 586 | Java.perform(function () { 587 | // 588 | Utils = Java.use("com.singleman.okhttp.Utils") 589 | //Init common 590 | JavaStringWapper = Java.use("java.lang.String") 591 | JavaStringBufferWapper = Java.use("java.lang.StringBuilder") 592 | JavaIntegerWapper = Java.use("java.lang.Integer") 593 | GsonWapper = Java.use("com.singleman.gson.Gson") 594 | ListWapper = Java.use("java.util.List") 595 | ArraysWapper = Java.use("java.util.Arrays") 596 | ArrayListWapper = Java.use("java.util.ArrayList") 597 | CharsetWapper = Java.use("java.nio.charset.Charset") 598 | CharacterWapper = Java.use("java.lang.Character") 599 | 600 | OkioByteStrngWapper = Java.use("com.singleman.okio.ByteString") 601 | OkioBufferWapper = Java.use("com.singleman.okio.Buffer") 602 | 603 | //Init OKHTTP 604 | OkHttpClientWapper = Java.use(Cls_OkHttpClient) 605 | ResponseBodyWapper = Java.use(Cls_ResponseBody) 606 | BufferWapper = Java.use(Cls_okio_Buffer) 607 | 608 | //Start Hook 609 | OkHttpClientWapper[M_Client_newCall].overload(Cls_Request).implementation = function (request) { 610 | var call = this[M_Client_newCall](request) 611 | try { 612 | CallCache.push(call["clone"]()) 613 | } catch (error) { 614 | console.log("not fount clone method!") 615 | } 616 | var realCallClassName = call.$className 617 | if (!alreadyHook(realCallClassName)) { 618 | hookedArray.push(realCallClassName) 619 | hookRealCall(realCallClassName) 620 | } 621 | return call; 622 | } 623 | }) 624 | } 625 | 626 | function switchLoader(clientName) { 627 | Java.perform(function () { 628 | if ("" != clientName) { 629 | try { 630 | var clz = Java.classFactory.loader.findClass(clientName) 631 | console.log("") 632 | console.log(">>>>>>>>>>>>> ", clz, " <<<<<<<<<<<<<<<<") 633 | } catch (error) { 634 | console.log(error) 635 | Java.enumerateClassLoaders({ 636 | onMatch: function (loader) { 637 | try { 638 | if (loader.findClass(clientName)) { 639 | Java.classFactory.loader = loader 640 | console.log("") 641 | console.log("Switch ClassLoader To : ", loader) 642 | console.log("") 643 | } 644 | } catch (error) { 645 | // console.log(error) 646 | } 647 | }, 648 | onComplete: function () { 649 | console.log("") 650 | console.log("Switch ClassLoader Complete !") 651 | console.log("") 652 | } 653 | }) 654 | } 655 | } 656 | Java.openClassFile("/data/local/tmp/okhttpfind.dex").load() 657 | }) 658 | } 659 | 660 | /** 661 | * find & print used location 662 | */ 663 | function find() { 664 | Java.perform(function () { 665 | ArraysWapper = Java.use("java.util.Arrays") 666 | ArrayListWapper = Java.use("java.util.ArrayList") 667 | var isSupport = false; 668 | var clz_Protocol = null; 669 | try { 670 | var clazzNameList = Java.enumerateLoadedClassesSync() 671 | if (clazzNameList.length == 0) { 672 | console.log("ERROR >> [enumerateLoadedClasses] return null !!!!!!") 673 | return 674 | } 675 | for (var i = 0; i < clazzNameList.length; i++) { 676 | var name = clazzNameList[i] 677 | if (!checkClass(name)) { 678 | continue 679 | } 680 | try { 681 | var loadedClazz = Java.classFactory.loader.loadClass(name); 682 | if (loadedClazz.isEnum()) { 683 | var Protocol = Java.use(name); 684 | var toString = ArraysWapper.toString(Protocol.values()); 685 | if (toString.indexOf("http/1.0") != -1 686 | && toString.indexOf("http/1.1") != -1 687 | && toString.indexOf("spdy/3.1") != -1 688 | && toString.indexOf("h2") != -1 689 | ) { 690 | clz_Protocol = loadedClazz; 691 | break; 692 | } 693 | } 694 | } catch (error) { 695 | } 696 | } 697 | if (null == clz_Protocol) { 698 | console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~ 寻找okhttp特征失败,请确认是否使用okhttp ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 699 | return 700 | } 701 | //enum values >> Not to be confused with! 702 | var okhttp_pn = clz_Protocol.getPackage().getName(); 703 | var likelyOkHttpClient = okhttp_pn + ".OkHttpClient" 704 | try { 705 | var clz_okclient = Java.use(likelyOkHttpClient).class 706 | if (null != clz_okclient) { 707 | console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 未 混 淆 (仅参考)~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 708 | isSupport = true; 709 | } 710 | } catch (error) { 711 | console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 被 混 淆 (仅参考)~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 712 | isSupport = true; 713 | } 714 | 715 | } catch (error) { 716 | console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~未使用okhttp~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 717 | isSupport = false; 718 | } 719 | 720 | if (!isSupport) { 721 | console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~ 寻找okhttp特征失败,请确认是否使用okhttp ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 722 | return 723 | } 724 | 725 | var likelyClazzList = ArrayListWapper.$new() 726 | for (var i = 0; i < clazzNameList.length; i++) { 727 | var name = clazzNameList[i] 728 | if (!checkClass(name)) { 729 | continue 730 | } 731 | try { 732 | var loadedClazz = Java.classFactory.loader.loadClass(name); 733 | likelyClazzList.add(loadedClazz) 734 | } catch (error) { 735 | } 736 | } 737 | 738 | console.log("likelyClazzList size :" + likelyClazzList.size()) 739 | if (likelyClazzList.size() == 0) { 740 | console.log("Please make a network request and try again!") 741 | } 742 | 743 | console.log("") 744 | console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Start Find~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 745 | console.log("") 746 | try { 747 | var OkHttpFinder = Java.use("com.singleman.okhttp.OkHttpFinder") 748 | OkHttpFinder.getInstance().findClassInit(likelyClazzList) 749 | 750 | console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Find Result~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 751 | 752 | var OkCompatClazz = Java.use("com.singleman.okhttp.OkCompat").class 753 | var fields = OkCompatClazz.getDeclaredFields(); 754 | for (var i = 0; i < fields.length; i++) { 755 | var field = fields[i] 756 | field.setAccessible(true); 757 | var name = field.getName() 758 | var value = field.get(null) 759 | console.log("var " + name + " = \"" + value + "\";") 760 | } 761 | console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Find Complete~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 762 | 763 | } catch (error) { 764 | console.log(error) 765 | //console.log(Java.use("android.util.Log").getStackTraceString(error)) 766 | } 767 | }) 768 | } 769 | 770 | /** 771 | */ 772 | function main() { 773 | Java.perform(function () { 774 | Java.openClassFile("/data/local/tmp/okhttpfind.dex").load() 775 | var version = Java.use("com.singleman.SingleMan").class.getDeclaredField("version").get(null) 776 | console.log(""); 777 | console.log("------------------------- OkHttp Poker by SingleMan [" + version + "]------------------------------------"); 778 | console.log("API:") 779 | console.log(" >>> find() 检查是否使用了Okhttp & 是否可能被混淆 & 寻找okhttp3关键类及函数"); 780 | console.log(" >>> switchLoader(\"okhttp3.OkHttpClient\") 参数:静态分析到的okhttpclient类名"); 781 | console.log(" >>> hold() 开启HOOK拦截"); 782 | console.log(" >>> history() 打印可重新发送的请求"); 783 | console.log(" >>> resend(index) 重新发送请求"); 784 | console.log("----------------------------------------------------------------------------------------"); 785 | 786 | }) 787 | } 788 | 789 | setImmediate(main) 790 | -------------------------------------------------------------------------------- /okhttpfind.dex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyujie/OkHttpLogger-Frida/c70da16d107c67d451d7112cfee7ee090589a527/okhttpfind.dex -------------------------------------------------------------------------------- /screen.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyujie/OkHttpLogger-Frida/c70da16d107c67d451d7112cfee7ee090589a527/screen.gif --------------------------------------------------------------------------------