From 0461d7a4ed39242d742c066ce16d0353c9801f5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Wed, 19 Jul 2023 15:19:55 +0200 Subject: [PATCH] Add frida-itrace tool for instruction tracing --- Makefile | 5 +- agents/itracer/agent.ts | 169 ++++++ agents/itracer/meson.build | 21 + agents/itracer/package-lock.json | 918 +++++++++++++++++++++++++++++++ agents/itracer/package.json | 20 + agents/itracer/tsconfig.json | 10 + agents/meson.build | 1 + frida_tools/itracer.py | 466 ++++++++++++++++ scripts/meson.build | 1 + setup.py | 1 + 10 files changed, 1611 insertions(+), 1 deletion(-) create mode 100644 agents/itracer/agent.ts create mode 100644 agents/itracer/meson.build create mode 100644 agents/itracer/package-lock.json create mode 100644 agents/itracer/package.json create mode 100644 agents/itracer/tsconfig.json create mode 100644 frida_tools/itracer.py diff --git a/Makefile b/Makefile index 24c5098..e8ae630 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -all: frida_tools/fs_agent.js frida_tools/tracer_agent.js +all: frida_tools/fs_agent.js frida_tools/tracer_agent.js frida_tools/itracer_agent.js frida_tools/fs_agent.js: agents/fs/agent.ts cd agents/fs && npm install && npm run build @@ -6,4 +6,7 @@ frida_tools/fs_agent.js: agents/fs/agent.ts frida_tools/tracer_agent.js: agents/tracer/agent.ts cd agents/tracer && npm install && npm run build +frida_tools/itracer_agent.js: agents/itracer/agent.ts + cd agents/itracer && npm install && npm run build + .PHONY: all diff --git a/agents/itracer/agent.ts b/agents/itracer/agent.ts new file mode 100644 index 0000000..402c5fc --- /dev/null +++ b/agents/itracer/agent.ts @@ -0,0 +1,169 @@ +import { + TraceBuffer, + TraceBufferReader, + TraceSession, + TraceStrategy, +} from "frida-itrace"; + +type RawTraceStrategy = RawTraceThreadStrategy | RawTraceRangeStrategy; +type RawTraceThreadStrategy = ["thread", ["id", number] | ["index", number]]; +type RawTraceRangeStrategy = ["range", [CodeLocation, CodeLocation | null]] + +type CodeLocation = + | ["address", string] + | ["module-export", [string, string]] + | ["module-offset", [string, number]] + | ["symbol", string] + ; + +const BUFFER_READER_POLL_INTERVAL_MSEC = 10; + +class Agent { + session: TraceSession | null = null; + buffer: TraceBuffer | null = null; + reader: TraceBufferReader | null = null; + drainTimer: NodeJS.Timer | null = null; + + createBuffer(): string { + this.buffer = TraceBuffer.create(); + return this.buffer.location; + } + + openBuffer(location: string) { + this.buffer = TraceBuffer.open(location); + } + + launchBufferReader() { + this.reader = new TraceBufferReader(this.buffer!); + this.drainTimer = setInterval(this.#drainBuffer, BUFFER_READER_POLL_INTERVAL_MSEC); + } + + stopBufferReader() { + clearInterval(this.drainTimer!); + this.drainTimer = null; + + this.#drainBuffer(); + + this.reader = null; + } + + #drainBuffer = () => { + const chunk = this.reader!.read(); + if (chunk.byteLength === 0) { + return; + } + send({ type: "itrace:chunk" }, chunk); + + const lost = this.reader!.lost; + if (lost !== 0) { + send({ type: "itrace:lost", payload: { lost } }); + } + }; + + launchTraceSession(rawStrategy: RawTraceStrategy) { + const strategy = parseTraceStrategy(rawStrategy); + const session = new TraceSession(strategy, this.buffer!); + this.session = session; + + session.events.on("start", (regSpecs, regValues) => { + send({ type: "itrace:start", payload: regSpecs }, regValues); + }); + session.events.on("end", () => { + send({ type: "itrace:end" }); + }); + session.events.on("compile", block => { + send({ type: "itrace:compile", payload: block }); + }); + session.events.on("panic", message => { + console.error(message); + }); + + session.open(); + } + + queryProgramName() { + return Process.enumerateModules()[0].name; + } + + listThreads() { + return Process.enumerateThreads(); + } +} + +function parseTraceStrategy(rawStrategy: RawTraceStrategy): TraceStrategy { + const [kind, params] = rawStrategy; + switch (kind) { + case "thread": { + let thread: ThreadDetails; + const threads = Process.enumerateThreads(); + switch (params[0]) { + case "id": { + const desiredId = params[1]; + const th = threads.find(t => t.id === desiredId); + if (th === undefined) { + throw new Error("invalid thread ID"); + } + thread = th; + break; + } + case "index": { + thread = threads[params[1]]; + if (thread === undefined) { + throw new Error("invalid thread index"); + } + break; + } + } + return { + type: "thread", + threadId: thread.id + }; + } + case "range": { + return { + type: "range", + start: parseCodeLocation(params[0]), + end: parseCodeLocation(params[1]) + }; + } + } +} + +function parseCodeLocation(location: CodeLocation | null): NativePointer { + if (location === null) { + return NULL; + } + + const [kind, params] = location; + switch (kind) { + case "address": { + const address = ptr(params); + try { + address.readVolatile(1); + } catch (e) { + throw new Error(`invalid address: ${address}`); + } + return address; + } + case "module-export": + return Module.getExportByName(params[0], params[1]); + case "module-offset": + return Module.getBaseAddress(params[0]).add(params[1]); + case "symbol": { + const name = params; + const { address } = DebugSymbol.fromName(name); + if (!address.isNull()) { + return address; + } + return Module.getExportByName(null, name); + } + } +} + +const agent = new Agent(); + +const agentMethodNames = Object.getOwnPropertyNames(Object.getPrototypeOf(agent)) + .filter(name => name !== "constructor"); +for (const name of agentMethodNames) { + rpc.exports[name] = (agent as any)[name].bind(agent); +} diff --git a/agents/itracer/meson.build b/agents/itracer/meson.build new file mode 100644 index 0000000..efbb936 --- /dev/null +++ b/agents/itracer/meson.build @@ -0,0 +1,21 @@ +entrypoint = 'agent.ts' + +sources = [ + entrypoint, + 'package.json', + 'package-lock.json', + 'tsconfig.json', +] + +custom_target('itracer-agent.js', + input: sources, + output: ['itracer_agent.js'], + command: [ + build_agent, + '@INPUT@', + '@OUTPUT0@', + '@PRIVATE_DIR@', + ], + install: true, + install_dir: pkgdir, +) diff --git a/agents/itracer/package-lock.json b/agents/itracer/package-lock.json new file mode 100644 index 0000000..f4fca70 --- /dev/null +++ b/agents/itracer/package-lock.json @@ -0,0 +1,918 @@ +{ + "name": "itracer-agent", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "itracer-agent", + "version": "1.0.0", + "dependencies": { + "frida-itrace": "^2.0.1" + }, + "devDependencies": { + "@types/frida-gum": "^18.4.0", + "@types/node": "^20.4.2", + "frida-compile": "^16.3.0" + } + }, + "node_modules/@frida/assert": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@frida/assert/-/assert-3.0.2.tgz", + "integrity": "sha512-JXJq5SbXGrM5EkjrZKfRmB29zOoEOix02NC6A5TSJ+C1GE/X051EinJJsuOO2pEOx7KZwpvAHvS0WXW0+levKg==", + "dev": true + }, + "node_modules/@frida/base64-js": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@frida/base64-js/-/base64-js-2.0.3.tgz", + "integrity": "sha512-2w0F+1TynOTCZ/v7du9LdHPWwq0lJhazjo2fF9upMyQmA1zHetT14fLuQ1v/6T0qPgyeEGkiSrybstU8EsgeUA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/@frida/buffer": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@frida/buffer/-/buffer-7.0.4.tgz", + "integrity": "sha512-RxQ1lZRRiCJj7nhcCiD8xeJx0NsLpGGnjqsmTg7jShGmbnVFMN5W7+J+3gqdPSQhc/IxNBIWc6zRXVp4+qnYHg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.5.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/@frida/crosspath": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@frida/crosspath/-/crosspath-3.0.0.tgz", + "integrity": "sha512-bNdO1spIPD2P40XtK89N49oZpJhstdlnkJZcD4yJ17jrdkm9Ctu0sd9MIEX6Z8Tm8ydhVJBAOMEKl9/R27onAQ==", + "dev": true, + "dependencies": { + "@types/node": "^17.0.36" + }, + "engines": { + "node": ">=14.9.0" + } + }, + "node_modules/@frida/crosspath/node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "dev": true + }, + "node_modules/@frida/diagnostics_channel": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@frida/diagnostics_channel/-/diagnostics_channel-1.0.0.tgz", + "integrity": "sha512-mYX1jp/5Bpk24tHArJNx65iCk7qSuV8YJkdU0gFNVtJUXxfV8BG5WuPa4mL+ynxsbWWpsg/cwKZbLAepYKTdQQ==", + "dev": true + }, + "node_modules/@frida/events": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@frida/events/-/events-4.0.4.tgz", + "integrity": "sha512-qJVQ6VWHf9sjUKuiJzoCAC00frbpcwxeYfvQ+PP9LU/d70j+QvjWgYe98Qa3ekLaBU6r/AvWm8ThKCDUCLWrQQ==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/@frida/http": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@frida/http/-/http-4.0.2.tgz", + "integrity": "sha512-cvkc7ex7GmVXVOWqtjXKBWUUbYEBgpNRKZbEEoMeI8KiIs8zejKwg+N7rx7296Ao+EP3+xcUr4wBVr3xLaUVfQ==", + "dev": true, + "dependencies": { + "http-parser-js": "^0.5.3" + } + }, + "node_modules/@frida/http-parser-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@frida/http-parser-js/-/http-parser-js-1.0.0.tgz", + "integrity": "sha512-2nMrNXt/OeTlWbqnE8AH4Sfz4I2+BGoN206dzKEyC/g2svtn83Xu+zuv/V3TkwrA27s26Mcy84ZwsXeNlqNxUQ==", + "dev": true + }, + "node_modules/@frida/https": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@frida/https/-/https-1.0.0.tgz", + "integrity": "sha512-OiqQ6qsALcWOktRLq07oJ0i6sH8eX6MXb/MdZS1qVKDRf6wchH4Pjn6fiLB+pt/OlYbggk+DOfpHwSdjTwuHMQ==", + "dev": true + }, + "node_modules/@frida/ieee754": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@frida/ieee754/-/ieee754-2.0.2.tgz", + "integrity": "sha512-wlcUebnne4ENN7GDr5pTH598ZDLMVOsh0FjenxeVOe6u7ewZkz9gGRnLnZKJAm9kl5G6XhdxhI0cSXVQK/rQUw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/@frida/net": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@frida/net/-/net-4.0.2.tgz", + "integrity": "sha512-qQRe7hQ+ZfCcG/SE3P1TRqQ9bmuK/T7wPCYaT4z56rBPWAxsaQbQHpX4fR6OrFaSDr7X0xJLsTbdIp9hGhhLZg==", + "dev": true + }, + "node_modules/@frida/os": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@frida/os/-/os-1.0.2.tgz", + "integrity": "sha512-3ISAiGNiyIya3QN2EHBCz1wqP0enTdSxP99wUeroeh8+AQRmgoOr/5TRnrVry8pe378anay3fmV/tdUMMSkehQ==", + "dev": true + }, + "node_modules/@frida/path": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@frida/path/-/path-2.0.3.tgz", + "integrity": "sha512-2RQy36QatoC846fzBhBhV8sXMsSOBGoYvwTHeaE1zUdz7F4RNScP4QEekTTooBYWYX/XjiF36KQpYAzc9OYFtg==", + "dev": true + }, + "node_modules/@frida/process": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@frida/process/-/process-1.2.1.tgz", + "integrity": "sha512-nvCu22DstFW2ttGFtOKekHM7vnjbZm+XgtvavOt427GNT6uV7k0JYK9tnMbcLMRWv57DG6udAmuJlWs8Paq1ag==", + "dev": true + }, + "node_modules/@frida/punycode": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@frida/punycode/-/punycode-3.0.0.tgz", + "integrity": "sha512-XVSDY2KamDs1D5/fTVgHcOSNxdU4kTboxzqJMBbTjcQC7XScIT9c0EfbwKCq7Kci6gWQdsHSCr7lU+9Oc4KAdg==", + "dev": true + }, + "node_modules/@frida/querystring": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@frida/querystring/-/querystring-1.0.0.tgz", + "integrity": "sha512-15m1fOZPmoO/vWlgPJrG/J9/BJDz6a2/JpVGpS8ynNzo+fBhTznaStX5nHxUs24mVTqh/OqLo0EiYJM5WWHXxg==", + "dev": true + }, + "node_modules/@frida/readable-stream": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@frida/readable-stream/-/readable-stream-4.1.3.tgz", + "integrity": "sha512-ntGUFmi+CryRGRJIK13a/VST2Ad19uivbln8Xd92vKPAARq+6vMIASDyZIqyl5BLRccfiyCHdYgrgQ6RI5rUig==", + "dev": true + }, + "node_modules/@frida/reserved-words": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@frida/reserved-words/-/reserved-words-1.0.0.tgz", + "integrity": "sha512-2yG/XxJlsGlk/mm6eZTb4OAaQEhkTI2qaFfZFtAsrA/XuCpuMWkS4y/guyBlsRu4hAuhK2HPmNM8+OLLK1zM9Q==", + "dev": true + }, + "node_modules/@frida/stream": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@frida/stream/-/stream-1.0.2.tgz", + "integrity": "sha512-4OuaC1ztmEKgTq3WeBhsy8Oq+AwW9n9cYnvLklcC9jwD93AEwgbWpecLlxJCVuALvTMdhKPg0nQVfyGYP/i9Bw==", + "dev": true, + "dependencies": { + "@frida/readable-stream": "^4.1.3" + } + }, + "node_modules/@frida/string_decoder": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@frida/string_decoder/-/string_decoder-2.0.0.tgz", + "integrity": "sha512-in371tYZMHQiW9HF5MS3JDw6Ao6tyBoq34UWy2rzOswYyMG1rpizh85ofi/yVkxDiaqybEZefxzkVittpPGT6g==", + "dev": true + }, + "node_modules/@frida/terser": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@frida/terser/-/terser-1.0.0.tgz", + "integrity": "sha512-59h9WuNzD1Rx/zwoWqQ/FW/4Y/Q3R91Eng2hEwdHapqiTDvtKbZ08F6CynCR7ZVinrh4tLYsF46AtVPTz1ys9g==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@frida/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/@frida/timers": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@frida/timers/-/timers-3.0.0.tgz", + "integrity": "sha512-3b+0igv10aT8TMxefrTAd06rActqbxJLY2Xkkq9vYcPBffB/yHszl0NYIp/5ko8WC3ecDYPU6bQiY6fjs72zTA==", + "dev": true + }, + "node_modules/@frida/tty": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@frida/tty/-/tty-1.0.0.tgz", + "integrity": "sha512-p/kjLnKYxEAB1MdYP8+5rKv9CsHzyA+0jg9BcGETzjQVKHHcroHDULRxDYUh+DC7qs6cpX8QdDQh9E+a6ydgsQ==", + "dev": true + }, + "node_modules/@frida/url": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@frida/url/-/url-1.0.2.tgz", + "integrity": "sha512-ZKunbKJHMr8w2Eb/5K1avy0MzK1B998S17wYXNv3RmzBGxMm8S5T0F3qEpRxkU7/72P8m4izyQU87fWl+FjQsQ==", + "dev": true, + "dependencies": { + "@frida/punycode": "^3.0.0", + "@frida/querystring": "^1.0.0" + } + }, + "node_modules/@frida/util": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@frida/util/-/util-1.0.3.tgz", + "integrity": "sha512-htcG3uDiRXv89ERVNNYhfase39kJ2X75ZARfrYcYEtJLFEsSk0nemM1YnEIR4CjrHvdvkWHrwgKkS+acOyoNEg==", + "dev": true + }, + "node_modules/@frida/vm": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@frida/vm/-/vm-2.0.0.tgz", + "integrity": "sha512-7fsjLWscZT5odNIBtg6qbLNI+vAk1xmii6H5W2kaYkMYt0vRohQEcDSUWacA+eaWlu5SvMjZI82Yibj/3G9pJw==", + "dev": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@types/frida-gum": { + "version": "18.4.0", + "resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-18.4.0.tgz", + "integrity": "sha512-QbApR0hB86zveqjLUTwhgcO5ls8CjEYOBzFPYW86PKHJlJ43fBG+JDl27StkPjNRVooLT5nEtqP/nkz3Bh6yvg==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.4.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.2.tgz", + "integrity": "sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/frida-compile": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/frida-compile/-/frida-compile-16.3.0.tgz", + "integrity": "sha512-WpnvUzQtWnc8xPnFmmYCajJKzyKQLM/QEOvasgFYTjaXNfDJHjrHIDxgx/ZXSNidmxK3FZofs+LsuGMq9wEqqA==", + "dev": true, + "dependencies": { + "@frida/assert": "^3.0.1", + "@frida/base64-js": "^2.0.3", + "@frida/buffer": "^7.0.4", + "@frida/crosspath": "^3.0.0", + "@frida/diagnostics_channel": "^1.0.0", + "@frida/events": "^4.0.4", + "@frida/http": "^4.0.2", + "@frida/http-parser-js": "^1.0.0", + "@frida/https": "^1.0.0", + "@frida/ieee754": "^2.0.2", + "@frida/net": "^4.0.1", + "@frida/os": "^1.0.0", + "@frida/path": "^2.0.3", + "@frida/process": "^1.2.1", + "@frida/punycode": "^3.0.0", + "@frida/querystring": "^1.0.0", + "@frida/readable-stream": "^4.1.3", + "@frida/reserved-words": "^1.0.0", + "@frida/stream": "^1.0.2", + "@frida/string_decoder": "^2.0.0", + "@frida/terser": "^1.0.0", + "@frida/timers": "^3.0.0", + "@frida/tty": "^1.0.0", + "@frida/url": "^1.0.2", + "@frida/util": "^1.0.3", + "@frida/vm": "^2.0.0", + "commander": "^9.4.0", + "frida-fs": "^5.2.3", + "typed-emitter": "^2.1.0" + }, + "bin": { + "frida-compile": "dist/cli.js" + } + }, + "node_modules/frida-fs": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/frida-fs/-/frida-fs-5.2.5.tgz", + "integrity": "sha512-Eyb4OqUlcv1/Eq7Q+B9IZmYZIgIM2YjqDojrjmAGzPSSXBuUKwSkuObQcQ8Dup9JTOMIUcSII9/I8DaTe6LFKw==", + "dev": true + }, + "node_modules/frida-itrace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/frida-itrace/-/frida-itrace-2.0.1.tgz", + "integrity": "sha512-1Dlc4hqJ58ktNEJs24vYK3CJAgiVdmTCxVXFYmZPiLwobSrFMUioM3PlQwNKV2Z5Skc7FE2SZO+JpVf4YMWvqA==", + "dependencies": { + "tiny-typed-emitter": "^2.1.0" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/tiny-typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz", + "integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==" + }, + "node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "dev": true, + "optional": true + }, + "node_modules/typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz", + "integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==", + "dev": true, + "optionalDependencies": { + "rxjs": "*" + } + } + }, + "dependencies": { + "@frida/assert": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@frida/assert/-/assert-3.0.2.tgz", + "integrity": "sha512-JXJq5SbXGrM5EkjrZKfRmB29zOoEOix02NC6A5TSJ+C1GE/X051EinJJsuOO2pEOx7KZwpvAHvS0WXW0+levKg==", + "dev": true + }, + "@frida/base64-js": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@frida/base64-js/-/base64-js-2.0.3.tgz", + "integrity": "sha512-2w0F+1TynOTCZ/v7du9LdHPWwq0lJhazjo2fF9upMyQmA1zHetT14fLuQ1v/6T0qPgyeEGkiSrybstU8EsgeUA==", + "dev": true + }, + "@frida/buffer": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@frida/buffer/-/buffer-7.0.4.tgz", + "integrity": "sha512-RxQ1lZRRiCJj7nhcCiD8xeJx0NsLpGGnjqsmTg7jShGmbnVFMN5W7+J+3gqdPSQhc/IxNBIWc6zRXVp4+qnYHg==", + "dev": true, + "requires": { + "base64-js": "^1.5.1", + "ieee754": "^1.2.1" + } + }, + "@frida/crosspath": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@frida/crosspath/-/crosspath-3.0.0.tgz", + "integrity": "sha512-bNdO1spIPD2P40XtK89N49oZpJhstdlnkJZcD4yJ17jrdkm9Ctu0sd9MIEX6Z8Tm8ydhVJBAOMEKl9/R27onAQ==", + "dev": true, + "requires": { + "@types/node": "^17.0.36" + }, + "dependencies": { + "@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "dev": true + } + } + }, + "@frida/diagnostics_channel": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@frida/diagnostics_channel/-/diagnostics_channel-1.0.0.tgz", + "integrity": "sha512-mYX1jp/5Bpk24tHArJNx65iCk7qSuV8YJkdU0gFNVtJUXxfV8BG5WuPa4mL+ynxsbWWpsg/cwKZbLAepYKTdQQ==", + "dev": true + }, + "@frida/events": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@frida/events/-/events-4.0.4.tgz", + "integrity": "sha512-qJVQ6VWHf9sjUKuiJzoCAC00frbpcwxeYfvQ+PP9LU/d70j+QvjWgYe98Qa3ekLaBU6r/AvWm8ThKCDUCLWrQQ==", + "dev": true + }, + "@frida/http": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@frida/http/-/http-4.0.2.tgz", + "integrity": "sha512-cvkc7ex7GmVXVOWqtjXKBWUUbYEBgpNRKZbEEoMeI8KiIs8zejKwg+N7rx7296Ao+EP3+xcUr4wBVr3xLaUVfQ==", + "dev": true, + "requires": { + "http-parser-js": "^0.5.3" + } + }, + "@frida/http-parser-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@frida/http-parser-js/-/http-parser-js-1.0.0.tgz", + "integrity": "sha512-2nMrNXt/OeTlWbqnE8AH4Sfz4I2+BGoN206dzKEyC/g2svtn83Xu+zuv/V3TkwrA27s26Mcy84ZwsXeNlqNxUQ==", + "dev": true + }, + "@frida/https": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@frida/https/-/https-1.0.0.tgz", + "integrity": "sha512-OiqQ6qsALcWOktRLq07oJ0i6sH8eX6MXb/MdZS1qVKDRf6wchH4Pjn6fiLB+pt/OlYbggk+DOfpHwSdjTwuHMQ==", + "dev": true + }, + "@frida/ieee754": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@frida/ieee754/-/ieee754-2.0.2.tgz", + "integrity": "sha512-wlcUebnne4ENN7GDr5pTH598ZDLMVOsh0FjenxeVOe6u7ewZkz9gGRnLnZKJAm9kl5G6XhdxhI0cSXVQK/rQUw==", + "dev": true + }, + "@frida/net": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@frida/net/-/net-4.0.2.tgz", + "integrity": "sha512-qQRe7hQ+ZfCcG/SE3P1TRqQ9bmuK/T7wPCYaT4z56rBPWAxsaQbQHpX4fR6OrFaSDr7X0xJLsTbdIp9hGhhLZg==", + "dev": true + }, + "@frida/os": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@frida/os/-/os-1.0.2.tgz", + "integrity": "sha512-3ISAiGNiyIya3QN2EHBCz1wqP0enTdSxP99wUeroeh8+AQRmgoOr/5TRnrVry8pe378anay3fmV/tdUMMSkehQ==", + "dev": true + }, + "@frida/path": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@frida/path/-/path-2.0.3.tgz", + "integrity": "sha512-2RQy36QatoC846fzBhBhV8sXMsSOBGoYvwTHeaE1zUdz7F4RNScP4QEekTTooBYWYX/XjiF36KQpYAzc9OYFtg==", + "dev": true + }, + "@frida/process": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@frida/process/-/process-1.2.1.tgz", + "integrity": "sha512-nvCu22DstFW2ttGFtOKekHM7vnjbZm+XgtvavOt427GNT6uV7k0JYK9tnMbcLMRWv57DG6udAmuJlWs8Paq1ag==", + "dev": true + }, + "@frida/punycode": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@frida/punycode/-/punycode-3.0.0.tgz", + "integrity": "sha512-XVSDY2KamDs1D5/fTVgHcOSNxdU4kTboxzqJMBbTjcQC7XScIT9c0EfbwKCq7Kci6gWQdsHSCr7lU+9Oc4KAdg==", + "dev": true + }, + "@frida/querystring": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@frida/querystring/-/querystring-1.0.0.tgz", + "integrity": "sha512-15m1fOZPmoO/vWlgPJrG/J9/BJDz6a2/JpVGpS8ynNzo+fBhTznaStX5nHxUs24mVTqh/OqLo0EiYJM5WWHXxg==", + "dev": true + }, + "@frida/readable-stream": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@frida/readable-stream/-/readable-stream-4.1.3.tgz", + "integrity": "sha512-ntGUFmi+CryRGRJIK13a/VST2Ad19uivbln8Xd92vKPAARq+6vMIASDyZIqyl5BLRccfiyCHdYgrgQ6RI5rUig==", + "dev": true + }, + "@frida/reserved-words": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@frida/reserved-words/-/reserved-words-1.0.0.tgz", + "integrity": "sha512-2yG/XxJlsGlk/mm6eZTb4OAaQEhkTI2qaFfZFtAsrA/XuCpuMWkS4y/guyBlsRu4hAuhK2HPmNM8+OLLK1zM9Q==", + "dev": true + }, + "@frida/stream": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@frida/stream/-/stream-1.0.2.tgz", + "integrity": "sha512-4OuaC1ztmEKgTq3WeBhsy8Oq+AwW9n9cYnvLklcC9jwD93AEwgbWpecLlxJCVuALvTMdhKPg0nQVfyGYP/i9Bw==", + "dev": true, + "requires": { + "@frida/readable-stream": "^4.1.3" + } + }, + "@frida/string_decoder": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@frida/string_decoder/-/string_decoder-2.0.0.tgz", + "integrity": "sha512-in371tYZMHQiW9HF5MS3JDw6Ao6tyBoq34UWy2rzOswYyMG1rpizh85ofi/yVkxDiaqybEZefxzkVittpPGT6g==", + "dev": true + }, + "@frida/terser": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@frida/terser/-/terser-1.0.0.tgz", + "integrity": "sha512-59h9WuNzD1Rx/zwoWqQ/FW/4Y/Q3R91Eng2hEwdHapqiTDvtKbZ08F6CynCR7ZVinrh4tLYsF46AtVPTz1ys9g==", + "dev": true, + "requires": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + } + } + }, + "@frida/timers": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@frida/timers/-/timers-3.0.0.tgz", + "integrity": "sha512-3b+0igv10aT8TMxefrTAd06rActqbxJLY2Xkkq9vYcPBffB/yHszl0NYIp/5ko8WC3ecDYPU6bQiY6fjs72zTA==", + "dev": true + }, + "@frida/tty": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@frida/tty/-/tty-1.0.0.tgz", + "integrity": "sha512-p/kjLnKYxEAB1MdYP8+5rKv9CsHzyA+0jg9BcGETzjQVKHHcroHDULRxDYUh+DC7qs6cpX8QdDQh9E+a6ydgsQ==", + "dev": true + }, + "@frida/url": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@frida/url/-/url-1.0.2.tgz", + "integrity": "sha512-ZKunbKJHMr8w2Eb/5K1avy0MzK1B998S17wYXNv3RmzBGxMm8S5T0F3qEpRxkU7/72P8m4izyQU87fWl+FjQsQ==", + "dev": true, + "requires": { + "@frida/punycode": "^3.0.0", + "@frida/querystring": "^1.0.0" + } + }, + "@frida/util": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@frida/util/-/util-1.0.3.tgz", + "integrity": "sha512-htcG3uDiRXv89ERVNNYhfase39kJ2X75ZARfrYcYEtJLFEsSk0nemM1YnEIR4CjrHvdvkWHrwgKkS+acOyoNEg==", + "dev": true + }, + "@frida/vm": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@frida/vm/-/vm-2.0.0.tgz", + "integrity": "sha512-7fsjLWscZT5odNIBtg6qbLNI+vAk1xmii6H5W2kaYkMYt0vRohQEcDSUWacA+eaWlu5SvMjZI82Yibj/3G9pJw==", + "dev": true + }, + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + }, + "dependencies": { + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + } + } + }, + "@types/frida-gum": { + "version": "18.4.0", + "resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-18.4.0.tgz", + "integrity": "sha512-QbApR0hB86zveqjLUTwhgcO5ls8CjEYOBzFPYW86PKHJlJ43fBG+JDl27StkPjNRVooLT5nEtqP/nkz3Bh6yvg==", + "dev": true + }, + "@types/node": { + "version": "20.4.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.2.tgz", + "integrity": "sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==", + "dev": true + }, + "acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true + }, + "frida-compile": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/frida-compile/-/frida-compile-16.3.0.tgz", + "integrity": "sha512-WpnvUzQtWnc8xPnFmmYCajJKzyKQLM/QEOvasgFYTjaXNfDJHjrHIDxgx/ZXSNidmxK3FZofs+LsuGMq9wEqqA==", + "dev": true, + "requires": { + "@frida/assert": "^3.0.1", + "@frida/base64-js": "^2.0.3", + "@frida/buffer": "^7.0.4", + "@frida/crosspath": "^3.0.0", + "@frida/diagnostics_channel": "^1.0.0", + "@frida/events": "^4.0.4", + "@frida/http": "^4.0.2", + "@frida/http-parser-js": "^1.0.0", + "@frida/https": "^1.0.0", + "@frida/ieee754": "^2.0.2", + "@frida/net": "^4.0.1", + "@frida/os": "^1.0.0", + "@frida/path": "^2.0.3", + "@frida/process": "^1.2.1", + "@frida/punycode": "^3.0.0", + "@frida/querystring": "^1.0.0", + "@frida/readable-stream": "^4.1.3", + "@frida/reserved-words": "^1.0.0", + "@frida/stream": "^1.0.2", + "@frida/string_decoder": "^2.0.0", + "@frida/terser": "^1.0.0", + "@frida/timers": "^3.0.0", + "@frida/tty": "^1.0.0", + "@frida/url": "^1.0.2", + "@frida/util": "^1.0.3", + "@frida/vm": "^2.0.0", + "commander": "^9.4.0", + "frida-fs": "^5.2.3", + "typed-emitter": "^2.1.0" + } + }, + "frida-fs": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/frida-fs/-/frida-fs-5.2.5.tgz", + "integrity": "sha512-Eyb4OqUlcv1/Eq7Q+B9IZmYZIgIM2YjqDojrjmAGzPSSXBuUKwSkuObQcQ8Dup9JTOMIUcSII9/I8DaTe6LFKw==", + "dev": true + }, + "frida-itrace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/frida-itrace/-/frida-itrace-2.0.1.tgz", + "integrity": "sha512-1Dlc4hqJ58ktNEJs24vYK3CJAgiVdmTCxVXFYmZPiLwobSrFMUioM3PlQwNKV2Z5Skc7FE2SZO+JpVf4YMWvqA==", + "requires": { + "tiny-typed-emitter": "^2.1.0" + } + }, + "http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "optional": true, + "requires": { + "tslib": "^2.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "tiny-typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz", + "integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==" + }, + "tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "dev": true, + "optional": true + }, + "typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz", + "integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==", + "dev": true, + "requires": { + "rxjs": "*" + } + } + } +} diff --git a/agents/itracer/package.json b/agents/itracer/package.json new file mode 100644 index 0000000..4b8072f --- /dev/null +++ b/agents/itracer/package.json @@ -0,0 +1,20 @@ +{ + "name": "itracer-agent", + "version": "1.0.0", + "description": "Agent used by frida-itrace", + "private": true, + "main": "agent.ts", + "type": "module", + "scripts": { + "build": "frida-compile agent.ts -c -o ../../frida_tools/itracer_agent.js", + "watch": "frida-compile agent.ts -w -o ../../frida_tools/itracer_agent.js" + }, + "devDependencies": { + "@types/frida-gum": "^18.4.0", + "@types/node": "^20.4.2", + "frida-compile": "^16.3.0" + }, + "dependencies": { + "frida-itrace": "^2.0.1" + } +} diff --git a/agents/itracer/tsconfig.json b/agents/itracer/tsconfig.json new file mode 100644 index 0000000..e1f6728 --- /dev/null +++ b/agents/itracer/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020"], + "module": "Node16", + "moduleResolution": "Node16", + "strict": true, + "noEmit": true + } +} diff --git a/agents/meson.build b/agents/meson.build index 182b3fc..4a55408 100644 --- a/agents/meson.build +++ b/agents/meson.build @@ -2,3 +2,4 @@ build_agent = find_program('build.py') subdir('fs') subdir('tracer') +subdir('itracer') diff --git a/frida_tools/itracer.py b/frida_tools/itracer.py new file mode 100644 index 0000000..d21d21c --- /dev/null +++ b/frida_tools/itracer.py @@ -0,0 +1,466 @@ +from abc import ABC, abstractmethod +import json +import os +from pathlib import Path +import struct +from typing import List, Optional, Sequence, Tuple, TypeVar, Union + +import frida +from frida.core import RPCException + +from frida_tools.reactor import Reactor + + +CodeLocation = Union[ + Tuple["address", str], + Tuple["module-export", Tuple[str, str]], + Tuple["module-offset", Tuple[str, int]], + Tuple["symbol", str], +] + +TraceThreadStrategy = Tuple["thread", Union[ + Tuple["id", int], + Tuple["index", int], +]] +TraceRangeStrategy = Tuple["range", Tuple[CodeLocation, Optional[CodeLocation]]] +TraceStrategy = Union[TraceThreadStrategy, TraceRangeStrategy] + + +def main() -> None: + import argparse + import threading + + from prompt_toolkit import PromptSession, prompt + from prompt_toolkit.application import Application + from prompt_toolkit.formatted_text import AnyFormattedText, FormattedText + from prompt_toolkit.key_binding.defaults import load_key_bindings + from prompt_toolkit.key_binding.key_bindings import KeyBindings, merge_key_bindings + from prompt_toolkit.layout import Layout + from prompt_toolkit.layout.containers import HSplit + from prompt_toolkit.styles import BaseStyle + from prompt_toolkit.widgets import Label, RadioList + + from frida_tools.application import ConsoleApplication + + class InstructionTracerApplication(ConsoleApplication, InstructionTracerUI): + _itracer: Optional[InstructionTracer] + + def __init__(self) -> None: + self._state = "starting" + self._ready = threading.Event() + self._cli = PromptSession() + super().__init__(self._process_input) + + def _add_options(self, parser: argparse.ArgumentParser) -> None: + parser.add_argument("-t", "--thread-id", + help="trace THREAD_ID", metavar="THREAD_ID", + dest="strategy", type=parse_thread_id) + parser.add_argument("-i", "--thread-index", + help="trace THREAD_INDEX", metavar="THREAD_INDEX", + dest="strategy", type=parse_thread_index) + parser.add_argument("-r", "--range", + help="trace RANGE, e.g.: 0x1000..0x1008, libc.so!sleep, libc.so!0x1234, recv..memcpy", metavar="RANGE", + dest="strategy", type=parse_range) + parser.add_argument("-o", "--output", help="output to file", dest="outpath") + + def _initialize(self, parser: argparse.ArgumentParser, options: argparse.Namespace, args: List[str]) -> None: + self._itracer = None + self._strategy = options.strategy + self._outpath = options.outpath + + def _usage(self) -> str: + return "%(prog)s [options] target" + + def _needs_target(self) -> bool: + return True + + def _start(self) -> None: + self._update_status("Injecting script...") + self._itracer = InstructionTracer(self._reactor) + self._itracer.start(self._device, self._session, self._runtime, self) + self._ready.set() + + def _stop(self) -> None: + assert self._itracer is not None + self._itracer.dispose() + self._itracer = None + + try: + self._cli.app.exit() + except: + pass + + def _process_input(self, reactor: Reactor) -> None: + try: + while self._ready.wait(0.5) != True: + if not reactor.is_running(): + return + except KeyboardInterrupt: + reactor.cancel_io() + return + + if self._state != "started": + return + + try: + self._cli.prompt() + except: + pass + + def get_trace_strategy(self) -> Optional[TraceStrategy]: + return self._strategy + + def prompt_for_trace_strategy(self, threads: List[dict]) -> Optional[TraceStrategy]: + kind = radiolist_prompt(title="Tracing strategy:", + values=[ + ("thread", "Thread"), + ("range", "Range"), + ]) + if kind is None: + raise KeyboardInterrupt + + if kind == "thread": + thread_id = radiolist_prompt(title="Running threads:", + values=[(t["id"], json.dumps(t)) for t in threads]) + if thread_id is None: + raise KeyboardInterrupt + return ("thread", ("id", thread_id)) + + while True: + try: + text = prompt("Start address: ").strip() + if len(text) == 0: + continue + start = parse_code_location(text) + break + except Exception as e: + print(str(e)) + continue + + while True: + try: + text = prompt("End address (optional): ").strip() + if len(text) > 0: + end = parse_code_location(text) + else: + end = None + break + except Exception as e: + print(str(e)) + continue + + return ("range", (start, end)) + + def get_trace_output_path(self, suggested_name: Optional[str] = None) -> os.PathLike: + return self._outpath + + def prompt_for_trace_output_path(self, suggested_name: str) -> Optional[os.PathLike]: + while True: + outpath = prompt("Output filename: ", default=suggested_name).strip() + if len(outpath) != 0: + break + return outpath + + def on_trace_started(self) -> None: + self._state = "started" + + def on_trace_stopped(self, error_message: Optional[str] = None) -> None: + self._state = "stopping" + + if error_message is not None: + self._log(level="error", text=error_message) + self._exit(1) + else: + self._exit(0) + + try: + self._cli.app.exit() + except: + pass + + def on_trace_progress(self, total_blocks: int, total_bytes: int) -> None: + blocks_suffix = "s" if total_blocks != 1 else "" + self._cli.message = FormattedText([ + ("bold", "Tracing!"), + ("", " Collected "), + ("fg:green bold", human_readable_size(total_bytes)), + ("", f" from {total_blocks} basic block{blocks_suffix}"), + ]) + self._cli.app.invalidate() + + def parse_thread_id(value: str) -> TraceThreadStrategy: + return ("thread", ("id", int(value))) + + def parse_thread_index(value: str) -> TraceThreadStrategy: + return ("thread", ("index", int(value))) + + def parse_range(value: str) -> TraceRangeStrategy: + tokens = value.split("..", 1) + start = tokens[0] + end = tokens[1] if len(tokens) == 2 else None + return ("range", (parse_code_location(start), parse_code_location(end))) + + def parse_code_location(value: Optional[str]) -> CodeLocation: + if value is None: + return None + + if value.startswith("0x"): + return ("address", value) + + tokens = value.split("!", 1) + if len(tokens) == 2: + name = tokens[0] + subval = tokens[1] + if subval.startswith("0x"): + return ("module-offset", (name, int(subval, 16))) + return ("module-export", (name, subval)) + + return ("symbol", tokens[0]) + + # Based on https://stackoverflow.com/a/43690506 + def human_readable_size(size): + for unit in ["B", "KiB", "MiB", "GiB"]: + if size < 1024.0 or unit == "GiB": + break + size /= 1024.0 + return f"{size:.2f} {unit}" + + T = TypeVar("T") + + # Based on https://github.com/prompt-toolkit/python-prompt-toolkit/issues/756#issuecomment-1294742392 + def radiolist_prompt( + title: str = "", + values: Sequence[Tuple[T, AnyFormattedText]] = None, + default: Optional[T] = None, + cancel_value: Optional[T] = None, + style: Optional[BaseStyle] = None, + ) -> T: + radio_list = RadioList(values, default) + radio_list.control.key_bindings.remove("enter") + + bindings = KeyBindings() + + @bindings.add("enter") + def exit_with_value(event): + radio_list._handle_enter() + event.app.exit(result=radio_list.current_value) + + @bindings.add("c-c") + def backup_exit_with_value(event): + event.app.exit(result=cancel_value) + + application = Application( + layout=Layout(HSplit([Label(title), radio_list])), + key_bindings=merge_key_bindings([load_key_bindings(), bindings]), + mouse_support=True, + style=style, + full_screen=False, + ) + return application.run() + + app = InstructionTracerApplication() + app.run() + + +class InstructionTracerUI(ABC): + @abstractmethod + def get_trace_strategy(self) -> Optional[TraceStrategy]: + raise NotImplementedError + + def prompt_for_trace_strategy(self, threads: List[dict]) -> Optional[TraceStrategy]: + return None + + @abstractmethod + def get_trace_output_path(self) -> Optional[os.PathLike]: + raise NotImplementedError + + def prompt_for_trace_output_path(self, suggested_name: str) -> Optional[os.PathLike]: + return None + + @abstractmethod + def on_trace_started(self) -> None: + raise NotImplementedError + + @abstractmethod + def on_trace_stopped(self, error_message: Optional[str] = None) -> None: + raise NotImplementedError + + def on_trace_progress(self, total_blocks: int, total_bytes: int) -> None: + pass + + def _on_script_created(self, script: frida.core.Script) -> None: + pass + + +class InstructionTracer: + FILE_MAGIC = b"ITRC" + + def __init__(self, reactor: Reactor) -> None: + self._reactor = reactor + self._outfile = None + self._ui: Optional[InstructionTracerUI] = None + self._total_blocks = 0 + self._tracer_script: Optional[frida.core.Script] = None + self._reader_script: Optional[frida.core.Script] = None + self._reader_api = None + + def dispose(self) -> None: + if self._reader_api is not None: + try: + self._reader_api.stop_buffer_reader() + except: + pass + self._reader_api = None + + if self._reader_script is not None: + try: + self._reader_script.unload() + except: + pass + self._reader_script = None + + if self._tracer_script is not None: + try: + self._tracer_script.unload() + except: + pass + self._tracer_script = None + + def start(self, device: frida.core.Device, session: frida.core.Session, runtime: str, ui: InstructionTracerUI) -> None: + def on_message(message, data) -> None: + self._reactor.schedule(lambda: self._on_message(message, data)) + + self._ui = ui + + agent_source = (Path(__file__).parent / "itracer_agent.js").read_text(encoding="utf-8") + + try: + tracer_script = session.create_script(name="itracer", source=agent_source, runtime=runtime) + self._tracer_script = tracer_script + self._ui._on_script_created(tracer_script) + tracer_script.on("message", on_message) + tracer_script.load() + + tracer_api = tracer_script.exports_sync + + outpath = ui.get_trace_output_path() + if outpath is None: + outpath = ui.prompt_for_trace_output_path(suggested_name=tracer_api.query_program_name() + ".itrace") + if outpath is None: + ui.on_trace_stopped("Missing output path") + return + + self._outfile = open(outpath, "wb") + self._outfile.write(self.FILE_MAGIC) + + strategy = ui.get_trace_strategy() + if strategy is None: + strategy = ui.prompt_for_trace_strategy(threads=tracer_api.list_threads()) + if strategy is None: + ui.on_trace_stopped("Missing strategy") + return + + buffer_location = tracer_api.create_buffer() + + try: + system_session = device.attach(0) + + reader_script = system_session.create_script(name="itracer", source=agent_source, runtime=runtime) + self._reader_script = reader_script + self._ui._on_script_created(reader_script) + reader_script.on("message", on_message) + reader_script.load() + + reader_script.exports_sync.open_buffer(buffer_location) + except Exception as e: + if self._reader_script is not None: + self._reader_script.unload() + self._reader_script = None + reader_script = None + + if reader_script is not None: + reader_api = reader_script.exports_sync + else: + reader_api = tracer_script.exports_sync + self._reader_api = reader_api + reader_api.launch_buffer_reader() + + tracer_script.exports_sync.launch_trace_session(strategy) + + ui.on_trace_started() + except RPCException as e: + ui.on_trace_stopped(f"Unable to start: {e.args[0]}") + except Exception as e: + ui.on_trace_stopped(str(e)) + except KeyboardInterrupt: + ui.on_trace_stopped() + + def _on_message(self, message, data) -> None: + handled = False + + if message["type"] == "send": + try: + payload = message["payload"] + mtype = payload["type"] + params = (mtype, payload, data) + except: + params = None + if params is not None: + handled = self._try_handle_message(*params) + + if not handled: + print(message) + + def _try_handle_message(self, mtype, message, data) -> bool: + if not mtype.startswith("itrace:"): + return False + + if mtype == "itrace:chunk": + self._write_chunk(data) + else: + self._write_message(message, data) + + if mtype == "itrace:compile": + self._total_blocks += 1 + + self._update_progress() + + if mtype == "itrace:end": + self._ui.on_trace_stopped() + + return True + + def _update_progress(self) -> None: + self._ui.on_trace_progress(self._total_blocks, self._outfile.tell()) + + def _write_message(self, message, data) -> None: + f = self._outfile + + raw_message = json.dumps(message).encode("utf-8") + f.write(struct.pack(">II", RecordType.MESSAGE, len(raw_message))) + f.write(raw_message) + + data_size = len(data) if data is not None else 0 + f.write(struct.pack(">I", data_size)) + if data_size != 0: + f.write(data) + + f.flush() + + def _write_chunk(self, chunk) -> None: + f = self._outfile + f.write(struct.pack(">II", RecordType.CHUNK, len(chunk))) + f.write(chunk) + f.flush() + + +class RecordType: + MESSAGE = 1 + CHUNK = 2 + + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + pass diff --git a/scripts/meson.build b/scripts/meson.build index d1d5fe8..d80b84e 100644 --- a/scripts/meson.build +++ b/scripts/meson.build @@ -4,6 +4,7 @@ scripts = [ ['frida-compile', 'compiler'], ['frida-create', 'create'], ['frida-discover', 'discover'], + ['frida-itrace', 'itracer'], ['frida-join', 'join'], ['frida-kill', 'kill'], ['frida-ls', 'ls'], diff --git a/setup.py b/setup.py index f6f103e..257d9c8 100755 --- a/setup.py +++ b/setup.py @@ -65,6 +65,7 @@ "frida-push = frida_tools.push:main", "frida-discover = frida_tools.discoverer:main", "frida-trace = frida_tools.tracer:main", + "frida-itrace = frida_tools.itracer:main", "frida-join = frida_tools.join:main", "frida-create = frida_tools.creator:main", "frida-compile = frida_tools.compiler:main",