frida-test 0.1.7 → 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 +33 -14
- package/dist/cli-frida-test.js +89 -15
- package/dist/device.d.ts +15 -3
- package/dist/device.d.ts.map +1 -1
- package/dist/device.js +44 -14
- package/dist/runner.d.ts +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/target.d.ts +18 -0
- package/dist/target.d.ts.map +1 -0
- package/dist/target.js +54 -0
- package/package.json +2 -2
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
|
|
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
|
-
| `-
|
|
92
|
-
| `-
|
|
93
|
-
| `-
|
|
94
|
-
| `-
|
|
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 -
|
|
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
|
|
109
|
-
frida-test
|
|
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
|
|
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
|
|
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,
|
|
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
|
```
|
package/dist/cli-frida-test.js
CHANGED
|
@@ -12,10 +12,19 @@ const usage = `
|
|
|
12
12
|
Usage: frida-test [options] <src_path>...
|
|
13
13
|
|
|
14
14
|
Options:
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
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 -
|
|
28
|
-
frida-test -
|
|
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
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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.
|
|
71
|
-
targetDef = {
|
|
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
|
|
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
|
-
|
|
12
|
+
file: string;
|
|
7
13
|
} | {
|
|
8
|
-
|
|
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 {
|
package/dist/device.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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 ("
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
20
|
-
|
|
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 ===
|
|
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
|
-
|
|
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 '${
|
|
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;
|
package/dist/runner.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/target.d.ts
ADDED
|
@@ -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.
|
|
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",
|
|
@@ -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 -
|
|
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": {
|