Skip to content

Commit 5361da5

Browse files
committed
Dynamically fetch & cache Frida server downloads
1 parent 7cd0e3a commit 5361da5

File tree

4 files changed

+72
-14
lines changed

4 files changed

+72
-14
lines changed

src/dynamic-dep-store.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import * as path from 'path';
2+
import * as fs from './util/fs';
3+
import * as stream from 'stream';
4+
5+
import { HtkConfig } from './config';
6+
7+
/**
8+
* This retrieves a stream for a dependency file, either from disk if it's already available
9+
* or by calling the fetch() function (and then saving the result to disk in parallel for
10+
* future calls).
11+
*/
12+
export async function getDependencyStream<K extends readonly string[]>(options: {
13+
config: HtkConfig,
14+
key: K,
15+
ext: string,
16+
fetch: (key: K) => Promise<stream.Readable>
17+
}) {
18+
const depPath = path.join(options.config.configPath, `${options.key.join('-')}.${options.ext}`);
19+
20+
if (await fs.canAccess(depPath)) {
21+
return fs.createReadStream(depPath);
22+
}
23+
24+
const tmpDownloadPath = depPath + `.tmp-${Math.random().toString(36).slice(2)}`;
25+
const downloadStream = await options.fetch(options.key);
26+
const diskStream = fs.createWriteStream(tmpDownloadPath);
27+
downloadStream.pipe(diskStream);
28+
29+
downloadStream.on('error', (e) => {
30+
console.warn(`Failed to download dependency to ${depPath}:`, e);
31+
diskStream.destroy();
32+
fs.deleteFile(tmpDownloadPath).catch(() => {});
33+
});
34+
35+
diskStream.on('finish', () => {
36+
fs.moveFile(tmpDownloadPath, depPath)
37+
.catch(console.warn);
38+
});
39+
40+
return downloadStream;
41+
}

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

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
import { Client as AdbClient, DeviceClient } from '@devicefarmer/adbkit';
22
import * as FridaJs from 'frida-js';
33

4+
import { waitUntil } from '../../util/promise';
5+
import { HtkConfig } from '../../config';
46
import {
57
EMULATOR_HOST_IPS,
68
createPersistentReverseTunnel,
79
getConnectedDevices,
810
getRootCommand,
911
isProbablyRooted
1012
} from '../android/adb-commands';
11-
import { waitUntil } from '../../util/promise';
1213
import { buildAndroidFridaScript } from './frida-scripts';
1314
import {
1415
FRIDA_ALTERNATE_PORT,
1516
FRIDA_BINARY_NAME,
1617
FRIDA_DEFAULT_PORT,
17-
FRIDA_SRIS,
18-
FRIDA_VERSION,
1918
FridaHost,
19+
getFridaServer,
2020
killProcess,
2121
launchScript,
2222
testAndSelectProxyAddress
@@ -96,7 +96,7 @@ const ANDROID_ABI_FRIDA_ARCH_MAP = {
9696
'x86_64': 'x86_64'
9797
} as const;
9898

99-
export async function setupAndroidHost(adbClient: AdbClient, hostId: string) {
99+
export async function setupAndroidHost(config: HtkConfig, adbClient: AdbClient, hostId: string) {
100100
const deviceClient = adbClient.getDevice(hostId);
101101

102102
const deviceProperties = await deviceClient.getProperties();
@@ -111,13 +111,7 @@ export async function setupAndroidHost(adbClient: AdbClient, hostId: string) {
111111
if (!firstKnownAbi) throw new Error(`Did not recognize any device ABIs from ${supportedAbis.join(',')}`);
112112

113113
const deviceArch = ANDROID_ABI_FRIDA_ARCH_MAP[firstKnownAbi];
114-
115-
const serverStream = await FridaJs.downloadFridaServer({
116-
version: FRIDA_VERSION,
117-
platform: 'android',
118-
arch: deviceArch,
119-
sri: FRIDA_SRIS.android[deviceArch]
120-
});
114+
const serverStream = await getFridaServer(config, 'android', deviceArch);
121115

122116
await deviceClient.push(serverStream, ANDROID_FRIDA_BINARY_PATH, 0o555);
123117
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Interceptor } from "..";
66
import { HtkConfig } from '../../config';
77

88
import { createAdbClient } from '../android/adb-commands';
9-
import { FridaHost, FridaTarget, killProcess } from './frida-integration';
9+
import { FridaTarget, killProcess } from './frida-integration';
1010
import {
1111
getAndroidFridaHosts,
1212
getAndroidFridaTargets,
@@ -61,7 +61,7 @@ export class FridaAndroidInterceptor implements Interceptor {
6161
| { action: 'intercept', hostId: string, targetId: string }
6262
): Promise<void> {
6363
if (options.action === 'setup') {
64-
await setupAndroidHost(this.adbClient, options.hostId);
64+
await setupAndroidHost(this.config, this.adbClient, options.hostId);
6565
} else if (options.action === 'launch') {
6666
const fridaServer = await launchAndroidHost(this.adbClient, options.hostId);
6767

src/interceptors/frida/frida-integration.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import * as FridaJs from 'frida-js';
22
import { CustomError, delay } from '@httptoolkit/util';
33

4+
import { withTimeout } from '../../util/promise';
45
import { getReachableInterfaces } from '../../util/network';
6+
import { HtkConfig } from '../../config';
7+
import * as dynamicDeps from '../../dynamic-dep-store';
8+
59
import { buildIpTestScript } from './frida-scripts';
6-
import { withTimeout } from '../../util/promise';
710

811
/**
912
* Terminology:
@@ -44,6 +47,26 @@ export const FRIDA_SRIS = {
4447
}
4548
} as const;
4649

50+
export const getFridaServer = (
51+
config: HtkConfig,
52+
platform: 'android',
53+
arch: 'arm' | 'arm64' | 'x86' | 'x86_64'
54+
) => {
55+
return dynamicDeps.getDependencyStream({
56+
config,
57+
key: ['frida-server', platform, arch, FRIDA_VERSION] as const,
58+
ext: '.bin',
59+
fetch: ([, platform, arch, version]) => {
60+
return FridaJs.downloadFridaServer({
61+
version: version,
62+
platform: platform,
63+
arch: arch,
64+
sri: FRIDA_SRIS.android[arch]
65+
});
66+
}
67+
});
68+
}
69+
4770
class FridaScriptError extends CustomError {
4871
constructor(
4972
message: FridaJs.ScriptAgentErrorMessage

0 commit comments

Comments
 (0)