Skip to content

Commit 861f352

Browse files
committed
Extend Android Frida caching to iOS
1 parent 618bd03 commit 861f352

File tree

3 files changed

+107
-61
lines changed

3 files changed

+107
-61
lines changed

src/interceptors/frida/frida-android-integration.ts

Lines changed: 15 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ import {
2020
getFridaServer,
2121
killProcess,
2222
launchScript,
23-
testAndSelectProxyAddress
23+
testAndSelectProxyAddress,
24+
createFridaSessionCache,
25+
clearFridaSessionCache,
26+
getOrCreateFridaSession
2427
} from './frida-integration';
2528

2629
const ANDROID_DEVICE_HTK_PATH = '/data/local/tmp/.httptoolkit';
@@ -181,47 +184,15 @@ const getFridaStream = (hostId: string, deviceClient: DeviceClient) =>
181184
});
182185
});
183186

184-
const fridaSessionCache: Record<string, {
185-
fridaSession: FridaJs.FridaSession,
186-
cleanup: () => void,
187-
timeout: NodeJS.Timeout
188-
}> = {};
187+
// Frida session cache for Android hosts
188+
const fridaSessionCache = createFridaSessionCache();
189189

190-
const FRIDA_SESSION_IDLE_TIMEOUT = 30_000;
191-
192-
function clearFridaSessionCache(hostId: string) {
193-
const cached = fridaSessionCache[hostId];
194-
if (cached) {
195-
clearTimeout(cached.timeout);
196-
cached.cleanup();
197-
delete fridaSessionCache[hostId];
198-
}
199-
}
200-
201-
async function getOrCreateFridaSession(hostId: string, deviceClient: DeviceClient) {
202-
let cached = fridaSessionCache[hostId];
203-
if (cached) {
204-
clearTimeout(cached.timeout);
205-
cached.timeout = setTimeout(() => clearFridaSessionCache(hostId), FRIDA_SESSION_IDLE_TIMEOUT);
206-
return { fridaSession: cached.fridaSession, wasCached: true };
207-
}
208-
209-
const fridaStream = await getFridaStream(hostId, deviceClient);
210-
const fridaSession = await FridaJs.connect({ stream: fridaStream });
211-
212-
const timeout = setTimeout(() => clearFridaSessionCache(hostId), FRIDA_SESSION_IDLE_TIMEOUT);
213-
const cleanup = () => fridaSession.disconnect()
214-
.catch(() => {})
215-
.finally(() => fridaStream.destroy());
216-
217-
fridaSessionCache[hostId] = {
218-
fridaSession,
219-
cleanup,
220-
timeout
221-
};
222-
223-
fridaStream.on('error', cleanup);
224-
return { fridaSession, wasCached: false };
190+
async function getOrCreateAndroidFridaSession(hostId: string, deviceClient: DeviceClient) {
191+
return getOrCreateFridaSession(
192+
fridaSessionCache,
193+
hostId,
194+
() => getFridaStream(hostId, deviceClient)
195+
);
225196
}
226197

227198
export async function getAndroidFridaTargets(adbClient: AdbClient, hostId: string): Promise<Array<{
@@ -235,11 +206,10 @@ export async function getAndroidFridaTargets(adbClient: AdbClient, hostId: strin
235206
let wasCached: boolean = false;
236207

237208
try {
238-
({ fridaSession, wasCached } = await getOrCreateFridaSession(hostId, deviceClient));
239-
const apps = await fridaSession.enumerateApplications();
240-
return apps;
209+
({ fridaSession, wasCached } = await getOrCreateAndroidFridaSession(hostId, deviceClient));
210+
return await fridaSession.enumerateApplications();
241211
} catch (e) {
242-
clearFridaSessionCache(hostId);
212+
clearFridaSessionCache(fridaSessionCache, hostId);
243213
if (wasCached) {
244214
// When a cached session fails, we retry with a fresh one:
245215
return getAndroidFridaTargets(adbClient, hostId);

src/interceptors/frida/frida-integration.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,4 +202,56 @@ export async function launchScript(targetName: string, session: FridaJs.FridaAge
202202
});
203203

204204
scriptLoaded = true;
205+
}
206+
207+
// Common Frida session cache logic for Android & iOS
208+
export interface FridaSessionCache {
209+
fridaSession: FridaJs.FridaSession;
210+
cleanup: () => void;
211+
timeout: NodeJS.Timeout;
212+
}
213+
214+
export const FRIDA_SESSION_IDLE_TIMEOUT = 30_000;
215+
216+
export function createFridaSessionCache() {
217+
return {} as Record<string, FridaSessionCache>;
218+
}
219+
220+
export function clearFridaSessionCache(cache: Record<string, FridaSessionCache>, hostId: string) {
221+
const cached = cache[hostId];
222+
if (cached) {
223+
clearTimeout(cached.timeout);
224+
cached.cleanup();
225+
delete cache[hostId];
226+
}
227+
}
228+
229+
export async function getOrCreateFridaSession(
230+
cache: Record<string, FridaSessionCache>,
231+
hostId: string,
232+
getStream: () => Promise<any>
233+
): Promise<{ fridaSession: FridaJs.FridaSession; wasCached: boolean }> {
234+
let cached = cache[hostId];
235+
if (cached) {
236+
clearTimeout(cached.timeout);
237+
cached.timeout = setTimeout(() => clearFridaSessionCache(cache, hostId), FRIDA_SESSION_IDLE_TIMEOUT);
238+
return { fridaSession: cached.fridaSession, wasCached: true };
239+
}
240+
241+
const fridaStream = await getStream();
242+
const fridaSession = await FridaJs.connect({ stream: fridaStream });
243+
244+
const timeout = setTimeout(() => clearFridaSessionCache(cache, hostId), FRIDA_SESSION_IDLE_TIMEOUT);
245+
const cleanup = () => fridaSession.disconnect()
246+
.catch(() => {})
247+
.finally(() => fridaStream.destroy());
248+
249+
cache[hostId] = {
250+
fridaSession,
251+
cleanup,
252+
timeout
253+
};
254+
255+
fridaStream.on('error', cleanup);
256+
return { fridaSession, wasCached: false };
205257
}

src/interceptors/frida/frida-ios-integration.ts

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import {
88
FridaHost,
99
killProcess,
1010
launchScript,
11-
testAndSelectProxyAddress
11+
testAndSelectProxyAddress,
12+
createFridaSessionCache,
13+
clearFridaSessionCache,
14+
getOrCreateFridaSession
1215
} from './frida-integration';
1316

1417
const isDevicePortOpen = (usbmuxClient: UsbmuxClient, deviceId: number, port: number) =>
@@ -102,27 +105,48 @@ async function getDeviceId(usbmuxClient: UsbmuxClient, hostId: string) {
102105
return deviceId;
103106
};
104107

105-
export async function getIosFridaTargets(usbmuxClient: UsbmuxClient, hostId: string) {
106-
const deviceId = await getDeviceId(usbmuxClient, hostId);
107-
108-
// Since we don't start Frida ourselves, alt port will never be used
109-
const fridaStream = await usbmuxClient.createDeviceTunnel(deviceId, FRIDA_DEFAULT_PORT);
110-
111-
const fridaSession = await FridaJs.connect({
112-
stream: fridaStream
113-
});
114-
115-
const apps = await fridaSession.enumerateApplications();
116-
fridaSession.disconnect().catch(() => {});
117-
return apps;
118-
}
119-
120108
// Various ports which we know that certain apps use for non-HTTP traffic that we
121109
// can't currently intercept, so we avoid capturing for now.
122110
const KNOWN_APP_PROBLEMATIC_PORTS: Record<string, number[] | undefined> = {
123111
'com.spotify.client': [4070]
124112
};
125113

114+
const fridaSessionCache = createFridaSessionCache();
115+
116+
async function getOrCreateIosFridaSession(hostId: string, usbmuxClient: UsbmuxClient) {
117+
return getOrCreateFridaSession(
118+
fridaSessionCache,
119+
hostId,
120+
async () => {
121+
const deviceId = await getDeviceId(usbmuxClient, hostId);
122+
123+
// Since we don't start Frida ourselves, alt port will never be used
124+
return usbmuxClient.createDeviceTunnel(deviceId, FRIDA_DEFAULT_PORT);
125+
}
126+
);
127+
}
128+
129+
export async function getIosFridaTargets(usbmuxClient: UsbmuxClient, hostId: string): Promise<Array<{
130+
pid: number | null;
131+
id: string;
132+
name: string;
133+
}>> {
134+
let fridaSession: FridaJs.FridaSession;
135+
let wasCached: boolean = false;
136+
try {
137+
({ fridaSession, wasCached } = await getOrCreateIosFridaSession(hostId, usbmuxClient));
138+
return await fridaSession.enumerateApplications();
139+
} catch (e) {
140+
clearFridaSessionCache(fridaSessionCache, hostId);
141+
if (wasCached) {
142+
// When a cached session fails, we retry with a fresh one:
143+
return getIosFridaTargets(usbmuxClient, hostId);
144+
} else {
145+
throw e;
146+
}
147+
}
148+
}
149+
126150
export async function interceptIosFridaTarget(
127151
usbmuxClient: UsbmuxClient,
128152
hostId: string,

0 commit comments

Comments
 (0)