Files
Flights/frida/okhttp_hook.js
Trey t 3790792040 Initial commit: Flights iOS app
Flight search app built on FlightConnections.com API data.
Features: airport search with autocomplete, browse by country/state/map,
flight schedules by route and date, multi-airline support with per-airline
schedule loading. Includes 4,561-airport GPS database for map browsing.
Adaptive light/dark mode UI inspired by Flighty.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 15:01:07 -05:00

207 lines
7.1 KiB
JavaScript

/*
* 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...");
});