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>
This commit is contained in:
206
frida/okhttp_hook.js
Normal file
206
frida/okhttp_hook.js
Normal file
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* 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...");
|
||||
});
|
||||
Reference in New Issue
Block a user