/* * Universal OkHttp Response Interceptor * Hooks OkHttp's response chain to capture ALL HTTP responses. * Works with Spirit, Delta, United (all use OkHttp/Retrofit). * * Sends captured data to a local server via Frida's send(). */ Java.perform(function() { console.log("[*] OkHttp Response Interceptor starting..."); // Filter: only capture responses from these domains var targetDomains = [ "api.spirit.com", "www.delta.com", "mobileapi.united.com", "content.spirit.com", "content.delta.com" ]; // Filter: only capture these API paths var targetPaths = [ // Spirit "/customermobileprod/", "/v1/getboastatus", "/v1/getboaparameters", "/v2/Token", "/v3/mytrips", "/v1/booking", "/v3/GetFlightInfoBI", "/v5/Flight/Search", // Delta "/api/mobile/asl", "/api/mobile/getFlightStatus", "/api/mobile/getFlightStatusByLeg", "/api/mobile/login", "/api/mobile/getDashboard", "/api/mobile/getUpgradeEligibilityInfo", // United "/standbylistservice/", "/upgradelistservice/", "/flightstatusservice/", "/checkinservice/", "/passriderlistservice/" ]; function shouldCapture(url) { var domainMatch = false; var pathMatch = false; for (var i = 0; i < targetDomains.length; i++) { if (url.indexOf(targetDomains[i]) !== -1) { domainMatch = true; break; } } if (!domainMatch) return false; for (var j = 0; j < targetPaths.length; j++) { if (url.indexOf(targetPaths[j]) !== -1) { pathMatch = true; break; } } return pathMatch; } // === Hook OkHttp3 RealCall.getResponseWithInterceptorChain === try { var OkHttpClient = Java.use("okhttp3.OkHttpClient"); var Request = Java.use("okhttp3.Request"); var Response = Java.use("okhttp3.Response"); var ResponseBody = Java.use("okhttp3.ResponseBody"); var BufferClass = Java.use("okio.Buffer"); var MediaType = Java.use("okhttp3.MediaType"); // Hook the Interceptor.Chain.proceed to capture request+response var RealInterceptorChain = Java.use("okhttp3.internal.http.RealInterceptorChain"); RealInterceptorChain.proceed.overload("okhttp3.Request").implementation = function(request) { var url = request.url().toString(); var response = this.proceed(request); if (shouldCapture(url)) { try { var method = request.method(); var reqBody = null; // Capture request body if (request.body() !== null) { var reqBuffer = BufferClass.$new(); request.body().writeTo(reqBuffer); reqBody = reqBuffer.readUtf8(); } // Capture request headers var reqHeaders = {}; var headerNames = request.headers(); for (var i = 0; i < headerNames.size(); i++) { reqHeaders[headerNames.name(i)] = headerNames.value(i); } // Capture response var statusCode = response.code(); var respBody = null; var respHeaders = {}; // Response headers var respHeaderObj = response.headers(); for (var j = 0; j < respHeaderObj.size(); j++) { respHeaders[respHeaderObj.name(j)] = respHeaderObj.value(j); } // Response body (need to peek without consuming) var body = response.body(); if (body !== null) { var source = body.source(); source.request(Long.MAX_VALUE); var buffer = source.getBuffer().clone(); respBody = buffer.readUtf8(); } var captured = { type: "HTTP_RESPONSE", timestamp: new Date().toISOString(), method: method, url: url, status: statusCode, requestHeaders: reqHeaders, requestBody: reqBody, responseHeaders: respHeaders, responseBody: respBody }; // Send to Frida host send(captured); console.log("[+] CAPTURED: " + method + " " + url + " -> " + statusCode + " (" + (respBody ? respBody.length : 0) + " chars)"); } catch(e) { console.log("[-] Capture error for " + url + ": " + e); } } return response; }; console.log("[+] OkHttp RealInterceptorChain.proceed hooked"); } catch(e) { console.log("[-] OkHttp3 hook failed: " + e); console.log("[*] Trying alternative hook..."); // Alternative: Hook at a higher level try { var Interceptor = Java.use("okhttp3.Interceptor"); // This approach hooks via adding our own interceptor console.log("[*] Alternative approach needed - see app-specific hooks"); } catch(e2) { console.log("[-] Alternative also failed: " + e2); } } // === Also hook Retrofit response callbacks === try { var CallbackClass = Java.use("retrofit2.OkHttpCall"); CallbackClass.parseResponse.implementation = function(rawResponse) { var response = this.parseResponse(rawResponse); try { var url = rawResponse.request().url().toString(); if (shouldCapture(url)) { var body = response.body(); if (body !== null) { console.log("[+] Retrofit response: " + url); console.log("[+] Body class: " + body.getClass().getName()); console.log("[+] Body: " + body.toString().substring(0, Math.min(500, body.toString().length))); send({ type: "RETROFIT_RESPONSE", timestamp: new Date().toISOString(), url: url, bodyClass: body.getClass().getName(), body: body.toString() }); } } } catch(e) { // Ignore parse errors } return response; }; console.log("[+] Retrofit parseResponse hooked"); } catch(e) { console.log("[-] Retrofit hook failed: " + e); } // === Java Long for buffer request === var Long = Java.use("java.lang.Long"); Long.MAX_VALUE.value; console.log("[*] OkHttp Response Interceptor ready. Waiting for traffic..."); });