frida-test 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # frida-test Documentation
2
2
 
3
- This is a small test framework which runs on the target. It is used to unit test Frida code running on actual devices. It was originaly developed to test the Frida agent code used in [frooky](https://github.com/cpholguera/frooky).
3
+ This is a small test framework which runs on the target. It is used to unit test Frida code running on actual devices. It was originally developed to test the Frida agent code used in [frooky](https://github.com/cpholguera/frooky).
4
4
 
5
5
  The following chapters explain how to write and run tests.
6
6
 
@@ -8,6 +8,7 @@ The following chapters explain how to write and run tests.
8
8
 
9
9
  ```sh
10
10
  npm install --save-dev frida-test
11
+
11
12
  ```
12
13
 
13
14
  ## Writing Tests
@@ -28,6 +29,7 @@ describe('Classloader', () => {
28
29
  }).toThrow(new Error("Class 'badClass' is not available."));
29
30
  })
30
31
  });
32
+
31
33
  ```
32
34
 
33
35
  Tests can be nested to any depth and can be synchronous or asynchronous.
@@ -49,6 +51,7 @@ myProject/
49
51
  └── shared/
50
52
  ├── helper.ts
51
53
  └── helper.test.ts
54
+
52
55
  ```
53
56
 
54
57
  ## Matchers
@@ -82,16 +85,26 @@ myProject/
82
85
 
83
86
  ```sh
84
87
  frida-test [options] <dir...>
88
+
85
89
  ```
86
90
 
87
91
  ### Options
88
92
 
89
93
  | Option | Description |
90
94
  | --- | --- |
91
- | `-i, --id <id>` | Bundle/package id or app name (spawns the app) |
92
- | `-p, --pid <id>` | Attach to a running process instead |
93
- | `-U, --usb` | Use the USB device |
94
- | `-D, --device <id>` | Use a specific device by id |
95
+ | `-D, --device <id>` | Connect to device with the given ID |
96
+ | `-U, --usb` | Connect to USB device |
97
+ | `-R, --remote` | Connect to remote frida-server |
98
+ | `-H, --host <host>` | Connect to remote frida-server on HOST |
99
+ | `--certificate <cert>` | Speak TLS with HOST, expecting CERTIFICATE |
100
+ | `--origin <origin>` | Connect to remote server with "Origin" header set to ORIGIN |
101
+ | `--token <token>` | Authenticate with HOST using TOKEN |
102
+ | `--keepalive-interval <interval>` | Set keepalive interval in seconds, or 0 to disable (defaults to -1) |
103
+ | `-f, --file <target>` | Spawn FILE |
104
+ | `-F, --attach-frontmost` | Attach to frontmost application |
105
+ | `-n, --attach-name <name>` | Attach to NAME |
106
+ | `-N, --attach-identifier <id>` | Attach to IDENTIFIER |
107
+ | `-p, --attach-pid <pid>` | Attach to PID |
95
108
  | `-o, --out <path>` | Path of the output file for JSON reporter (default: disabled) |
96
109
  | `-t, --timeout <s>` | Abort the run after this many seconds (default: `600`, `0` disables) |
97
110
  | `-d, --delay <s>` | Start running the test suites after this many seconds (default: `0`) |
@@ -102,24 +115,29 @@ frida-test [options] <dir...>
102
115
  ### `frida-test` Examples
103
116
 
104
117
  ```sh
105
- # Android app on a USB device, tests in ./tests/android and ./tests/shared
106
- frida-test -i org.owasp.mastestapp -U ./tests/android ./tests/shared
118
+ # Spawn an Android app on a USB device, tests in ./tests/android and ./tests/shared
119
+ frida-test -U -f org.owasp.mastestapp ./tests/android ./tests/shared
107
120
 
108
- # Attach to a running process by pid
109
- frida-test --pid 4926 -u ./tests/android
121
+ # Attach to a running process by PID on a USB device
122
+ frida-test -U -p 4926 ./tests/android
123
+
124
+ # Attach to a running iOS app by identifier
125
+ frida-test -U -N org.owasp.mastestapp.MASTestApp-iOS ./tests/ios ./tests/shared
126
+
127
+ # Connect to a remote frida-server on HOST with authentication
128
+ frida-test -H 192.168.1.10:27042 --token secret -p 4926 ./tests/shared
110
129
 
111
- # iOS app in the local simulator
112
- frida-test -i org.owasp.mastestapp.MASTestApp-iOS ./tests/ios ./tests/shared
113
130
  ```
114
131
 
115
132
  ## Compile Agent
116
133
 
117
- `frida-test` automatically bundles the test suites, and compiles them together with the testing framework into a Frida agent.
134
+ `frida-test` automatically bundles the test suites and compiles them together with the testing framework into a Frida agent.
118
135
 
119
136
  If you only want to compile this agent, use `frida-test-compile`:
120
137
 
121
138
  ```sh
122
139
  frida-test-compile [options] <dir...>
140
+
123
141
  ```
124
142
 
125
143
  | Option | Description |
@@ -130,9 +148,10 @@ frida-test-compile [options] <dir...>
130
148
  ### `frida-test-compile` Examples
131
149
 
132
150
  ```sh
133
- # Collects all tests in ./tests, compiles the frida-test agent, and print it to stdout
151
+ # Collects all tests in ./tests, compiles the frida-test agent, and prints it to stdout
134
152
  frida-test-compile ./tests
135
153
 
136
- # Collects all tests in ./tests, compile the frida-test agent, and stores it in the path ./frida-test-agent.js
154
+ # Collects all tests in ./tests, compiles the frida-test agent, and stores it in ./frida-test-agent.js
137
155
  frida-test-compile ./tests -o ./frida-test-agent.js
156
+
138
157
  ```
@@ -12,10 +12,19 @@ const usage = `
12
12
  Usage: frida-test [options] <src_path>...
13
13
 
14
14
  Options:
15
- -i, --id <id> Bundle/package id or app name (spawns the app)
16
- -p, --pid <id> Attach to a running process instead
17
- -U, --usb Use the USB device
18
- -D, --device <id> Use a specific device by id
15
+ -D, --device <id> Connect to device with the given ID
16
+ -U, --usb Connect to USB device
17
+ -R, --remote Connect to remote frida-server
18
+ -H, --host <host> Connect to remote frida-server on HOST
19
+ --certificate <cert> Speak TLS with HOST, expecting CERTIFICATE
20
+ --origin <origin> Connect to remote server with "Origin" header set to ORIGIN
21
+ --token <token> Authenticate with HOST using TOKEN
22
+ --keepalive-interval <interval> Set keepalive interval in seconds, or 0 to disable (defaults to -1)
23
+ -f, --file <target> Spawn FILE
24
+ -F, --attach-frontmost Attach to frontmost application
25
+ -n, --attach-name <name> Attach to NAME
26
+ -N, --attach-identifier <id> Attach to IDENTIFIER
27
+ -p, --attach-pid <pid> Attach to PID
19
28
  -o, --out <path> Path of the output file for JSON reporter (default: disabled)
20
29
  -t, --timeout <s> Abort the run after this many seconds (default: 600, 0 disables)
21
30
  -d, --delay <s> Start running the test suites after this many seconds (default: 0)
@@ -24,8 +33,8 @@ const usage = `
24
33
  -h, --help Show this help message
25
34
 
26
35
  Examples:
27
- frida-test -U -i org.owasp.mastestapp ./test -r json -o ./testing/reports/out.json
28
- frida-test -U --pid 4926 ./src/hooks.test.ts ./lib/utils
36
+ frida-test -U -f org.owasp.mastestapp ./test -o ./testing/reports/out.json
37
+ frida-test -H 192.168.1.10:27042 --token secret -p 4926 ./src/hooks.test.ts
29
38
  `;
30
39
  class CliError extends Error {
31
40
  }
@@ -39,14 +48,33 @@ function parseNonNegativeInt(value, name) {
39
48
  }
40
49
  return n;
41
50
  }
51
+ function parseKeepaliveInterval(value) {
52
+ const n = Number(value);
53
+ if (!Number.isInteger(n) || n < -1) {
54
+ throw new Error("keepalive-interval must be an integer >= -1");
55
+ }
56
+ return n;
57
+ }
42
58
  async function main() {
43
59
  const { values, positionals } = parseArgs({
44
60
  allowPositionals: true,
45
61
  options: {
46
- id: { type: "string", short: "i" },
47
- pid: { type: "string", short: "p" },
48
- usb: { type: "boolean", short: "U", default: false },
62
+ // Device options
49
63
  device: { type: "string", short: "D" },
64
+ usb: { type: "boolean", short: "U", default: false },
65
+ remote: { type: "boolean", short: "R", default: false },
66
+ host: { type: "string", short: "H" },
67
+ certificate: { type: "string" },
68
+ origin: { type: "string" },
69
+ token: { type: "string" },
70
+ "keepalive-interval": { type: "string" },
71
+ // Target options
72
+ file: { type: "string", short: "f" },
73
+ "attach-frontmost": { type: "boolean", short: "F", default: false },
74
+ "attach-name": { type: "string", short: "n" },
75
+ "attach-identifier": { type: "string", short: "N" },
76
+ "attach-pid": { type: "string", short: "p" },
77
+ // Test runner options
50
78
  out: { type: "string", short: "o" },
51
79
  delay: { type: "string", short: "d", default: "0" },
52
80
  timeout: { type: "string", short: "t", default: "600" },
@@ -63,22 +91,68 @@ async function main() {
63
91
  if (positionals.length === 0) {
64
92
  fail("Missing required argument <src_path>...");
65
93
  }
66
- if (Boolean(values.id) === Boolean(values.pid)) {
67
- fail("Must provide either --id (-i) or --pid (-p), but not both");
94
+ const primaryDeviceFlags = [values.device !== undefined, values.usb, values.remote, values.host !== undefined].filter(Boolean).length;
95
+ if (primaryDeviceFlags > 1) {
96
+ fail("Cannot specify multiple primary device options (-D, -U, -R, -H)");
97
+ }
98
+ const hasRemoteParams = values.certificate !== undefined || values.origin !== undefined || values.token !== undefined || values["keepalive-interval"] !== undefined;
99
+ if (hasRemoteParams && (values.device !== undefined || values.usb)) {
100
+ fail("Remote connection options (--certificate, --origin, --token, --keepalive-interval) cannot be used with -D or -U");
101
+ }
102
+ let deviceSelector;
103
+ if (values.device !== undefined) {
104
+ deviceSelector = { id: values.device };
105
+ }
106
+ else if (values.usb) {
107
+ deviceSelector = "usb";
108
+ }
109
+ else if (values.host !== undefined || values.remote || hasRemoteParams) {
110
+ const keepaliveInterval = values["keepalive-interval"] !== undefined ? parseKeepaliveInterval(values["keepalive-interval"]) : undefined;
111
+ deviceSelector = {
112
+ host: values.host ?? "127.0.0.1:27042",
113
+ ...(values.certificate && { certificate: values.certificate }),
114
+ ...(values.origin && { origin: values.origin }),
115
+ ...(values.token && { token: values.token }),
116
+ ...(keepaliveInterval !== undefined && { keepaliveInterval }),
117
+ };
118
+ }
119
+ else {
120
+ deviceSelector = "local";
121
+ }
122
+ const targetFlags = [
123
+ values.file !== undefined ? "file" : null,
124
+ values["attach-frontmost"] ? "attach-frontmost" : null,
125
+ values["attach-name"] !== undefined ? "attach-name" : null,
126
+ values["attach-identifier"] !== undefined ? "attach-identifier" : null,
127
+ values["attach-pid"] !== undefined ? "attach-pid" : null,
128
+ ].filter(Boolean);
129
+ if (targetFlags.length === 0) {
130
+ fail("Must provide a target option: -f, -F, -n, -N, or -p");
131
+ }
132
+ if (targetFlags.length > 1) {
133
+ fail("Must provide only one target option (-f, -F, -n, -N, or -p)");
68
134
  }
69
135
  let targetDef;
70
- if (values.id) {
71
- targetDef = { id: values.id };
136
+ if (values.file !== undefined) {
137
+ targetDef = { file: values.file };
138
+ }
139
+ else if (values["attach-frontmost"]) {
140
+ targetDef = { frontmost: true };
141
+ }
142
+ else if (values["attach-name"] !== undefined) {
143
+ targetDef = { name: values["attach-name"] };
144
+ }
145
+ else if (values["attach-identifier"] !== undefined) {
146
+ targetDef = { identifier: values["attach-identifier"] };
72
147
  }
73
148
  else {
74
- const parsedPid = parseNonNegativeInt(values.pid, "pid");
149
+ const parsedPid = parseNonNegativeInt(values["attach-pid"], "attach-pid");
75
150
  if (parsedPid === 0)
76
151
  fail("Invalid pid: must be greater than 0");
77
152
  targetDef = { pid: parsedPid };
78
153
  }
79
154
  const delay = parseNonNegativeInt(values.delay, "delay");
80
155
  const timeout = parseNonNegativeInt(values.timeout, "timeout");
81
- const deviceSelector = values.device !== undefined ? { id: values.device } : values.usb ? "usb" : "local";
82
156
  logger.info(`Collecting tests from ${positionals.join(", ")}...`);
83
157
  const testSuitePaths = await collectTestSuitePaths(positionals);
84
158
  logger.info(`Bundling testSuites with frida-test agent...`);
package/dist/device.d.ts CHANGED
@@ -1,11 +1,23 @@
1
1
  import type { Device } from "frida";
2
- export type DeviceSelector = "usb" | "local" | {
2
+ export type DeviceSelector = "usb" | "local" | "remote" | {
3
3
  id: string;
4
+ } | {
5
+ host: string;
6
+ certificate?: string;
7
+ origin?: string;
8
+ token?: string;
9
+ keepaliveInterval?: number;
4
10
  };
5
11
  export type TargetDef = {
6
- pid: number;
12
+ file: string;
7
13
  } | {
8
- id: string;
14
+ frontmost: true;
15
+ } | {
16
+ name: string;
17
+ } | {
18
+ identifier: string;
19
+ } | {
20
+ pid: number;
9
21
  };
10
22
  export declare function resolveDevice(selector?: DeviceSelector): Promise<Device>;
11
23
  export interface Target {
@@ -1 +1 @@
1
- {"version":3,"file":"device.d.ts","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAGpC,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,OAAO,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAC9D,MAAM,MAAM,SAAS,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzD,wBAAsB,aAAa,CAAC,QAAQ,GAAE,cAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CAIvF;AAED,MAAM,WAAW,MAAM;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CA8BzF"}
1
+ {"version":3,"file":"device.d.ts","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAGpC,MAAM,MAAM,cAAc,GACtB,KAAK,GACL,OAAO,GACP,QAAQ,GACR;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,GACd;IACE,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEN,MAAM,MAAM,SAAS,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,SAAS,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7H,wBAAsB,aAAa,CAAC,QAAQ,GAAE,cAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CAcvF;AAED,MAAM,WAAW,MAAM;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAuDzF"}
package/dist/device.js CHANGED
@@ -4,35 +4,65 @@ export async function resolveDevice(selector = "local") {
4
4
  return frida.getUsbDevice();
5
5
  if (selector === "local")
6
6
  return frida.getLocalDevice();
7
- return frida.getDevice(selector.id);
7
+ if (selector === "remote")
8
+ return frida.getRemoteDevice();
9
+ if ("id" in selector)
10
+ return frida.getDevice(selector.id);
11
+ // Pass network parameters to addRemoteDevice
12
+ const manager = frida.getDeviceManager();
13
+ return manager.addRemoteDevice(selector.host, {
14
+ certificate: selector.certificate,
15
+ origin: selector.origin,
16
+ token: selector.token,
17
+ keepaliveInterval: selector.keepaliveInterval,
18
+ });
8
19
  }
9
20
  export async function resolveTarget(device, targetDef) {
10
- if ("pid" in targetDef)
11
- return { pid: targetDef.pid, wasSpawned: false };
12
- const { id } = targetDef;
13
- if (!id.trim())
14
- throw new Error("Target id must not be empty");
15
- try {
16
- const pid = await device.spawn(id);
21
+ if ("file" in targetDef) {
22
+ const file = targetDef.file.trim();
23
+ if (!file)
24
+ throw new Error("Target file/program must not be empty");
25
+ const pid = await device.spawn(file);
17
26
  return { pid, wasSpawned: true };
18
27
  }
19
- catch (spawnError) {
20
- const needle = id.toLowerCase();
28
+ if ("pid" in targetDef) {
29
+ return { pid: targetDef.pid, wasSpawned: false };
30
+ }
31
+ if ("frontmost" in targetDef) {
32
+ const app = await device.getFrontmostApplication();
33
+ if (!app) {
34
+ throw new Error("No frontmost application found on the target device");
35
+ }
36
+ return { pid: app.pid, wasSpawned: false };
37
+ }
38
+ if ("name" in targetDef) {
39
+ const targetName = targetDef.name.trim();
40
+ if (!targetName)
41
+ throw new Error("Target process name must not be empty");
21
42
  const processes = await device.enumerateProcesses();
43
+ const needle = targetName.toLowerCase();
22
44
  const tiers = [
23
- processes.filter((p) => p.name === id),
45
+ processes.filter((p) => p.name === targetName),
24
46
  processes.filter((p) => p.name.toLowerCase() === needle),
25
47
  processes.filter((p) => p.name.toLowerCase().includes(needle)),
26
48
  ];
27
49
  const matches = tiers.find((tier) => tier.length > 0) ?? [];
28
50
  if (matches.length === 0) {
29
- const reason = spawnError instanceof Error ? spawnError.message : String(spawnError);
30
- throw new Error(`Unable to spawn '${id}' and no matching running process was found (spawn failed: ${reason}).`);
51
+ throw new Error(`No running process matching '${targetName}' was found.`);
31
52
  }
32
53
  if (matches.length > 1) {
33
54
  const names = matches.map((p) => `${p.name} (pid ${p.pid})`).join(", ");
34
- throw new Error(`Ambiguous target '${id}', matches: ${names}`);
55
+ throw new Error(`Ambiguous process target '${targetName}', matches: ${names}`);
35
56
  }
36
57
  return { pid: matches[0].pid, wasSpawned: false };
37
58
  }
59
+ const identifier = targetDef.identifier.trim();
60
+ if (!identifier)
61
+ throw new Error("Target identifier must not be empty");
62
+ const apps = await device.enumerateApplications();
63
+ const matchedApp = apps.find((a) => a.identifier === identifier);
64
+ if (!matchedApp || !matchedApp.pid || matchedApp.pid === 0) {
65
+ throw new Error(`Application '${identifier}' is not currently running.`);
66
+ }
67
+ return { pid: matchedApp.pid, wasSpawned: false };
38
68
  }
package/dist/runner.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Device } from "frida";
2
- import type { Target } from "./device.js";
3
2
  import type { RunSummary, TestSuiteResult } from "./protocol.js";
3
+ import type { Target } from "./target.js";
4
4
  export declare class TestRunner {
5
5
  private readonly device;
6
6
  private readonly target;
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAA4B,MAAM,OAAO,CAAC;AAC9D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAIjE,qBAAa,UAAU;IAQnB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAV1B,OAAO,CAAC,OAAO,CAAC,CAAU;IAC1B,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;IACjD,OAAO,CAAC,UAAU,CAAC,CAAQ;IAE3B,YACmB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,OAAO,EAC/B;IAEJ,IAAI,YAAY,IAAI,SAAS,eAAe,EAAE,CAE7C;IAEK,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAyBhC;IAEK,QAAQ,IAAI,OAAO,CAAC,UAAU,CAAC,CASpC;IAEK,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAS7B;IAED,OAAO,CAAC,SAAS;CA6BlB"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAA4B,MAAM,OAAO,CAAC;AAE9D,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGjE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,qBAAa,UAAU;IAQnB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAV1B,OAAO,CAAC,OAAO,CAAC,CAAU;IAC1B,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;IACjD,OAAO,CAAC,UAAU,CAAC,CAAQ;IAE3B,YACmB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,OAAO,EAC/B;IAEJ,IAAI,YAAY,IAAI,SAAS,eAAe,EAAE,CAE7C;IAEK,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAyBhC;IAEK,QAAQ,IAAI,OAAO,CAAC,UAAU,CAAC,CASpC;IAEK,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAS7B;IAED,OAAO,CAAC,SAAS;CA6BlB"}
@@ -0,0 +1,18 @@
1
+ import type { Device } from "frida";
2
+ export interface Target {
3
+ pid: number;
4
+ wasSpawned: boolean;
5
+ }
6
+ export type TargetDef = {
7
+ file: string;
8
+ } | {
9
+ frontmost: true;
10
+ } | {
11
+ name: string;
12
+ } | {
13
+ identifier: string;
14
+ } | {
15
+ pid: number;
16
+ };
17
+ export declare function resolveTarget(device: Device, targetDef: TargetDef): Promise<Target>;
18
+ //# sourceMappingURL=target.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"target.d.ts","sourceRoot":"","sources":["../src/target.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAEpC,MAAM,WAAW,MAAM;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,OAAO,CAAC;CACrB;AACD,MAAM,MAAM,SAAS,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,SAAS,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7H,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CA4DzF"}
package/dist/target.js ADDED
@@ -0,0 +1,54 @@
1
+ export async function resolveTarget(device, targetDef) {
2
+ // Spawn target binary or app (-f, --file)
3
+ if ("file" in targetDef) {
4
+ const file = targetDef.file.trim();
5
+ if (!file)
6
+ throw new Error("Target file/program must not be empty");
7
+ const pid = await device.spawn(file);
8
+ return { pid, wasSpawned: true };
9
+ }
10
+ // Attach by PID (-p, --attach-pid)
11
+ if ("pid" in targetDef) {
12
+ return { pid: targetDef.pid, wasSpawned: false };
13
+ }
14
+ // Attach to frontmost application (-F, --attach-frontmost)
15
+ if ("frontmost" in targetDef) {
16
+ const app = await device.getFrontmostApplication();
17
+ if (!app) {
18
+ throw new Error("No frontmost application found on the target device");
19
+ }
20
+ return { pid: app.pid, wasSpawned: false };
21
+ }
22
+ // Attach by process name (-n, --attach-name)
23
+ if ("name" in targetDef) {
24
+ const targetName = targetDef.name.trim();
25
+ if (!targetName)
26
+ throw new Error("Target process name must not be empty");
27
+ const processes = await device.enumerateProcesses();
28
+ const needle = targetName.toLowerCase();
29
+ const tiers = [
30
+ processes.filter((p) => p.name === targetName),
31
+ processes.filter((p) => p.name.toLowerCase() === needle),
32
+ processes.filter((p) => p.name.toLowerCase().includes(needle)),
33
+ ];
34
+ const matches = tiers.find((tier) => tier.length > 0) ?? [];
35
+ if (matches.length === 0) {
36
+ throw new Error(`No running process matching '${targetName}' was found.`);
37
+ }
38
+ if (matches.length > 1) {
39
+ const names = matches.map((p) => `${p.name} (pid ${p.pid})`).join(", ");
40
+ throw new Error(`Ambiguous process target '${targetName}', matches: ${names}`);
41
+ }
42
+ return { pid: matches[0].pid, wasSpawned: false };
43
+ }
44
+ // Attach to running application by identifier (-N, --attach-identifier) without spawning
45
+ const identifier = targetDef.identifier.trim();
46
+ if (!identifier)
47
+ throw new Error("Target identifier must not be empty");
48
+ const apps = await device.enumerateApplications();
49
+ const matchedApp = apps.find((a) => a.identifier === identifier);
50
+ if (!matchedApp || !matchedApp.pid || matchedApp.pid === 0) {
51
+ throw new Error(`Application '${identifier}' is not currently running.`);
52
+ }
53
+ return { pid: matchedApp.pid, wasSpawned: false };
54
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frida-test",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "type": "module",
5
5
  "license": "GPL-3.0-only",
6
6
  "homepage": "https://github.com/bernhste/frida-test/blob/main/README.md",
@@ -32,8 +32,8 @@
32
32
  },
33
33
  "exports": {
34
34
  ".": {
35
- "types": "./dist/agent-runtime/globals.d.ts",
36
- "default": "./dist/agent-runtime/globals.js"
35
+ "types": "./src/agent-runtime/globals.d.ts",
36
+ "default": "./src/agent-runtime/globals.js"
37
37
  },
38
38
  "./protocol.js": {
39
39
  "types": "./dist/protocol.d.ts",
@@ -49,7 +49,7 @@
49
49
  "build": "tsc -b",
50
50
  "watch": "tsc -b --watch",
51
51
  "clean": "tsc -b --clean && rimraf dist",
52
- "test:android": "npm run build && frida-test -U -i 'com.google.android.dialer' ./tests/",
52
+ "test:android": "npm run build && frida-test -U -f 'com.google.android.dialer' ./tests/",
53
53
  "prepublishOnly": "npm run clean && npm run build && npm run test:android"
54
54
  },
55
55
  "allowScripts": {