Repository: nowsecure/frida-trace Branch: main Commit: 9b92c1762373 Files: 12 Total size: 28.3 KB Directory structure: gitextract_24od24fx/ ├── .gitignore ├── LICENSE.md ├── README.md ├── example/ │ ├── bin/ │ │ └── trace.js │ ├── lib/ │ │ ├── agent/ │ │ │ └── index.js │ │ └── application.js │ └── package.json ├── frida-trace-tools/ │ ├── bin/ │ │ ├── generate-boilerplate.js │ │ └── parse-header.js │ └── package.json ├── index.js └── package.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ /example/lib/_agent.js /example/node_modules /node_modules npm-debug.log ================================================ FILE: LICENSE.md ================================================ The MIT License (MIT) Copyright (c) 2016 NowSecure, Inc Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # frida-trace Trace APIs declaratively through [Frida](https://www.frida.re). ## Example ```js import trace from 'frida-trace'; const func = trace.func; const argIn = trace.argIn; const argOut = trace.argOut; const retval = trace.retval; const types = trace.types; const pointer = types.pointer; const INT = types.INT; const POINTER = types.POINTER; const UTF8 = types.UTF8; trace({ module: 'libsqlite3.dylib', functions: [ func('sqlite3_open', retval(INT), [ argIn('filename', UTF8), argOut('ppDb', pointer(POINTER), when('result', isZero)), ]), func('sqlite3_prepare_v2', retval(INT), [ argIn('db', POINTER), argIn('zSql', [UTF8, bind('length', 'nByte')]), argIn('nByte', INT), argOut('ppStmt', pointer(POINTER), when('result', isZero)), ]) ], callbacks: { onEvent(event) { console.log('onEvent! ' + JSON.stringify(event, null, 2)); }, onEnter(event, context) { event.trace = Thread.backtrace(context) .map(DebugSymbol.fromAddress) .filter(x => x.name); }, onError(e) { console.error(e); } } }); function isZero(value) { return value === 0; } ``` ## Auto-generating boilerplate from header files ```sh $ ./bin/parse-header.js /usr/include/sqlite3.h | ./bin/generate-boilerplate.js trace({ module: 'libfoo.dylib', functions: [ func('sqlite3_libversion', retval(UTF8), []), func('sqlite3_sourceid', retval(UTF8), []), func('sqlite3_libversion_number', retval(INT), []), func('sqlite3_compileoption_used', retval(INT), [ argIn('zOptName', UTF8) ]), func('sqlite3_compileoption_get', retval(UTF8), [ argIn('N', INT) ]), func('sqlite3_threadsafe', retval(INT), []), func('sqlite3_close', retval(INT), [ argIn('a1', POINTER) ]), func('sqlite3_close_v2', retval(INT), [ argIn('a1', POINTER) ]), func('sqlite3_exec', retval(INT), [ argIn('a1', POINTER), argIn('sql', UTF8), argIn('callback', POINTER), argIn('a4', POINTER), argOut('errmsg', pointer(POINTER), when('result', isZero)) ]), ... ``` ================================================ FILE: example/bin/trace.js ================================================ #!/usr/bin/env node import Application from '../lib/application'; import chalk from 'chalk'; import { hexy } from 'hexy'; class ConsoleUI { onEvents(events) { events.forEach(event => { const args = event.args; const argNames = Object.keys(args); const heading = event.name + '(' + argNames.reduce((result, name) => { const value = args[name]; if (!(value instanceof Buffer)) result.push(name + '=' + annotate(value)); return result; }, []).join(', ') + ') => ' + event.result; const sections = argNames.reduce((result, name) => { const value = args[name]; if (value instanceof Buffer) { result.push('\n' + name + ':\n' + hexy(value, { format: 'twos' })); } return result; }, []).join('\n'); console.log(chalk.green(heading) + '\n' + sections); }); } onOutput(pid, fd, data) { let text = data.toString(); if (text[text.length - 1] === '\n') text = text.substr(0, text.length - 1); const lines = text.split('\n'); const prefix = ((fd === 1) ? chalk.bold('stdout>') : chalk.red('stderr>')) + ' '; console.log(prefix + lines.join('\n' + prefix) + '\n'); } onError(error) { console.error(error); process.exitCode = 1; } } function annotate(value) { if (typeof value === 'string') return '"' + value + '"'; else return value; } function usage() { console.error('Usage: ' + process.argv[0] + ' launch|attach args...'); process.exit(1); } if (process.argv.length < 5) usage(); const device = process.argv[2]; const action = process.argv[3]; let target; if (action === 'launch') { target = { device: device, argv: process.argv.slice(4) }; } else if (action === 'attach') { target = { device: device, pid: parseInt(process.argv[4], 10) }; } else { usage(); } const ui = new ConsoleUI(); const application = new Application(ui); application.run(target).catch(ui.onError.bind(ui)); ================================================ FILE: example/lib/agent/index.js ================================================ import trace from 'frida-trace'; const func = trace.func; const argIn = trace.argIn; const argOut = trace.argOut; const retval = trace.retval; const types = trace.types; const pointer = types.pointer; const INT = types.INT; const POINTER = types.POINTER; const UTF8 = types.UTF8; const BYTE_ARRAY = types.BYTE_ARRAY; const bind = trace.bind; const when = trace.when; rpc.exports = { dispose() { flushEvents(); }, }; trace({ module: null, functions: [ func('strlen', retval(INT), [ argIn('s', UTF8) ]), func('read', retval(INT), [ argIn('fd', INT), argOut('buffer', [BYTE_ARRAY, bind('length', 'result')], when('result', isGreaterThanZero)), argIn('length', INT), ]), ], callbacks: { onEvent(event) { scheduleEvent(event); }, onError(e) { console.error(e); } } }); const pending = []; let timer = null; function scheduleEvent(event) { pending.push(event); if (timer === null) { timer = setTimeout(() => { timer = null; flushEvents(); }, 500); } } function flushEvents() { if (timer !== null) { clearTimeout(timer); timer = null; } const events = pending.splice(0); const blobs = events.reduce((result, event, index) => { const args = event.args; Object.keys(args).forEach(argName => { const argValue = args[argName]; if (argValue instanceof ArrayBuffer) { result.push([index, argName, argValue]); } }); return result; }, []); const dataSize = blobs.reduce((total, blob) => total + blob[2].byteLength, 0); const data = new Uint8Array(dataSize); let offset = 0; const mappings = blobs.map(blob => { const chunk = new Uint8Array(blob[2]); data.set(chunk, offset); const length = chunk.length; const mapping = [blob[0], blob[1], offset, length]; offset += length; return mapping; }); send({ name: '+events', payload: { items: events, mappings: mappings } }, data.buffer); } function isGreaterThanZero(value) { return value > 0; } ================================================ FILE: example/lib/application.js ================================================ import frida from 'frida'; import fs from 'fs'; import path from 'path'; import util from 'util'; const readFile = util.promisify(fs.readFile); export default class Application { constructor(ui) { this.ui = ui; this._device = null; this._pid = 0; this._session = null; this._script = null; this._done = new Promise((resolve) => { this._onDone = resolve; }); } async run(target) { const device = await frida.getDevice(target.device); this._device = device; const onOutput = this._onOutput.bind(this); device.output.connect(onOutput); try { const spawn = target.hasOwnProperty('argv'); let pid; if (spawn) pid = await device.spawn(target.argv); else pid = target.pid; this._pid = pid; const session = await device.attach(pid); this._session = session; const agentCode = await import('./_agent'); const agent = await readFile(agentCode, 'utf8'); const script = await session.createScript(agent); this._script = script; const onMessage = this._onMessage.bind(this); script.message.connect(onMessage); try { await script.load(); if (spawn) await device.resume(pid); await this._waitUntilDone(); } finally { script.message.disconnect(onMessage); } } finally { device.output.disconnect(onOutput); } } _waitUntilDone() { return this._done; } _onOutput(pid, fd, data) { this.ui.onOutput(pid, fd, data); } _onMessage(message, data) { if (message.type === 'send') { const stanza = message.payload; switch (stanza.name) { case '+events': { const payload = stanza.payload; const items = payload.items; const mappings = payload.mappings; mappings.forEach(mapping => { const index = mapping[0]; const argName = mapping[1]; const offset = mapping[2]; const length = mapping[3]; items[index].args[argName] = Buffer.from(data, offset, length); }); this.ui.onEvents(items); break; } default: console.error(JSON.stringify(message, null, 2)); break; } } else { console.error(JSON.stringify(message, null, 2)); } } } ================================================ FILE: example/package.json ================================================ { "name": "frida-trace-example", "version": "1.0.0", "description": "Example showing how to use frida-trace", "main": "lib/application.js", "license": "MIT", "scripts": { "prepare": "npm run build", "build": "frida-compile lib/agent/index.js -o lib/_agent.js", "watch": "frida-compile lib/agent/index.js -o lib/_agent.js -w" }, "dependencies": { "chalk": "^2.4.2", "frida": "^16.1.4", "hexy": "^0.3.0" }, "devDependencies": { "frida-compile": "^16.3.0", "frida-trace": "^3.0.0" } } ================================================ FILE: frida-trace-tools/bin/generate-boilerplate.js ================================================ #!/usr/bin/env node import concat from 'concat-stream'; const input = process.stdin; input.setEncoding('utf-8'); input.pipe(concat(writeCode)); input.resume(); const output = process.stdout; function writeCode (data) { const api = JSON.parse(data); const code = apiDescriptionToCode(api); process.stdout.write(code); } function apiDescriptionToCode (api) { const {functions, structs} = api; return [] .concat(functionDescriptionsToCode(functions)) .concat(structDescriptionsToCode(structs)) .join('\n'); } function functionDescriptionsToCode (funcs) { if (funcs.length === 0) { return []; } const funcDecls = funcs.map(func => { const [name, retType, args] = func; return funcDescriptionToCode({ name: name, retType: retType, args: args }, 1); }); const code = `trace({ module: 'libfoo.dylib', functions: [ ${funcDecls.join(',\n ')} ], callbacks: { onEvent(event) { console.log('onEvent! ' + JSON.stringify(event, null, 2)); }, onError(e) { console.error(e); } } }); function isZero(value) { return value === 0; } `; return [code]; } function structDescriptionsToCode (structs) { if (structs.length === 0) { return []; } const code = structs .reduce((state, [name, funcs]) => { const body = structDescriptionToCode(funcs); let code; const {previous} = state; if (previous !== null && strippedName(name) === strippedName(previous.name) && body.startsWith(previous.body)) { code = `const ${name} = ${previous.name}.concat([ ${body.substr(previous.body.length + 2)} ]);`; } else { code = `const ${name} = [ ${body} ];`; } state.entries.push(code); state.previous = { name: name, body: body }; return state; }, { entries: [], previous: null }) .entries .join('\n\n'); return [code]; } function strippedName (name) { return name.replace(/[0-9]/g, ''); } function structDescriptionToCode (funcs) { const funcDecls = funcs .filter(([, , retType]) => retType !== null) .reduce((state, [offset, name, retType, args]) => { const padding = offset - state.previousOffset - 1; if (padding > 0) { state.items.push(`padding(${padding})`); } state.previousOffset = offset; state.items.push(funcDescriptionToCode({ offset: offset, name: name, retType: retType, args: args }, 0)); return state; }, { items: [], previousOffset: 0 }) .items; return funcDecls.join(',\n '); } function funcDescriptionToCode (func, indentLevel) { const indentation = makeIndentation(indentLevel); let argDecls; if (func.args.length > 0) { argDecls = `[ ${indentation} ${func.args.map(argDescriptionToCode, func).join(',\n' + indentation + ' ')} ${indentation} ]`; } else { argDecls = '[]'; } return `func('${func.name}', ${retTypeDescriptionToCode(func.retType)}, ${argDecls})`; } function argDescriptionToCode (arg) { const name = arg[0]; const type = arg[1]; const direction = argDirection(arg); let condition; if (direction === 'Out' && this.retType === 'Int') condition = `, when('result', isZero)`; else condition = ''; return `arg${direction}('${name}', ${typeDescriptionToCode(type)}${condition})`; } function retTypeDescriptionToCode (type) { if (type === 'Void') { return 'null'; } return `retval(${typeDescriptionToCode(type)})`; } function typeDescriptionToCode (type) { if (typeof type === 'object') { if (type.length === 2) { const pointee = type[1]; if (pointee[0] === 'Char_S') return 'UTF8'; } else if (type.length > 2) { if (isPointer(type[1])) return 'pointer(POINTER)'; } return 'POINTER'; } else if (type === 'UChar') { return 'BYTE'; } else { return type.toUpperCase(); } } function argDirection (arg) { const type = arg[1]; if (typeof type === 'object' && type.length > 2) { if (isPointer(type[1])) return 'Out'; else return 'In'; } else { return 'In'; } } function isPointer (type) { return type[0] === 'Pointer'; } function makeIndentation (level) { const result = []; for (let i = 0; i !== level; i++) { result.push(' '); } return result.join(''); } ================================================ FILE: frida-trace-tools/bin/parse-header.js ================================================ #!/usr/bin/env node import clang from 'frida-libclang'; import dynamicClang from 'frida-libclang/lib/dynamic_clang'; const clangApi = dynamicClang.libclang; const {CXCallingConv_Invalid} = dynamicClang.CONSTANTS.CXCallingConv; const Cursor = clang.Cursor; const Index = clang.Index; const TranslationUnit = clang.TranslationUnit; const Type = clang.Type; if (process.argv.length !== 3) { process.stderr.write('Usage: ' + process.argv[1] + ' /path/to/header.h\n'); process.exit(1); } const data = parseHeader(process.argv[2]); process.stdout.write(JSON.stringify(data)); function parseHeader (path) { const index = new Index(true, true); const unit = TranslationUnit.fromSource(index, path, ['-I/usr/include']); const funcs = []; const structs = []; unit.cursor.visitChildren(function (parent) { switch (this.kind) { case Cursor.FunctionDecl: funcs.push(parseFunction(this)); break; case Cursor.StructDecl: { const struct = parseStruct(this); const [name, funcs] = struct; if (name !== null && funcs.length > 0) { structs.push(struct); } break; } } return Cursor.Continue; }); index.dispose(); return { functions: funcs, structs: structs }; } function parseFunction (cursor) { const name = cursor.spelling; const retType = parseType(new Type(clangApi.clang_getCursorResultType(cursor._instance))); const args = []; cursor.visitChildren(function (parent) { switch (this.kind) { case Cursor.ParmDecl: const argName = this.spelling || 'a' + (args.length + 1); const argType = parseType(this.type); args.push([argName, argType]); break; default: break; } return Cursor.Continue; }); return [name, retType, args]; } function parseStruct (cursor) { const name = cursor.spelling; if (name === '') { return [null, []]; } const funcs = []; let func = null; let args = null; cursor.visitChildren(function (parent) { switch (this.kind) { case Cursor.FieldDecl: if (isFunctionPointer(this.type)) { const offset = clangApi.clang_Cursor_getOffsetOfField(this._instance) / 8 / 8; args = []; func = [offset, this.spelling, 'Void', args]; funcs.push(func); return Cursor.Recurse; } break; case Cursor.TypeRef: func[2] = parseType(this); break; case Cursor.ParmDecl: const argName = this.spelling || 'a' + (args.length + 1); const argType = parseType(this.type); args.push([argName, argType]); break; default: break; } return Cursor.Continue; }); return [name, funcs]; } function isFunctionPointer (type) { const name = type.spelling; if (name !== 'Pointer') { return false; } const conv = clangApi.clang_getFunctionTypeCallingConv(clangApi.clang_getPointeeType(type._instance)); return conv !== CXCallingConv_Invalid; } function parseType (type) { const name = type.spelling; if (name === 'Pointer') { const path = [ ['Pointer', parseQualifiers(type)] ]; let t = type; do { t = new Type(clangApi.clang_getPointeeType(t._instance)); path.push([t.spelling, parseQualifiers(t)]); } while (t.spelling === 'Pointer'); return path; } else if (name === 'Typedef') { return parseType(new Type(clangApi.clang_getCanonicalType(type._instance))); } else { return name; } } function parseQualifiers (type) { return clangApi.clang_isConstQualifiedType(type._instance) ? ['const'] : []; } ================================================ FILE: frida-trace-tools/package.json ================================================ { "name": "frida-trace-tools", "version": "3.3.0", "description": "Generate trace declarations from docs", "files": [ "/bin/*.js" ], "repository": { "type": "git", "url": "git+https://github.com/nowsecure/frida-trace.git" }, "license": "MIT", "bugs": { "url": "https://github.com/nowsecure/frida-trace/issues" }, "homepage": "https://github.com/nowsecure/frida-trace#readme", "scripts": { "frida-trace-generate-boilerplate": "bin/generate-boilerplate.js", "frida-trace-parse-header": "bin/parse-header.js" }, "devDependencies": { "concat-stream": "^2.0.0", "frida-libclang": "0.0.12" } } ================================================ FILE: index.js ================================================ const IN = Symbol('in'); const OUT = Symbol('out'); const IN_OUT = Symbol('in-out'); const pointerSize = Process.pointerSize; export default function trace (spec) { const {module, vtable, functions} = spec; const {onError} = spec.callbacks; const listeners = []; const intercept = makeInterceptor(spec); if (module !== undefined) { const mod = Process.getModuleByName(module); functions.forEach(func => { const {name} = func; const impl = mod.findExportByName(name); if (impl === null) { onError(new Error(`Failed to resolve ${module}!${name}`)); return; } listeners.push(intercept(func, impl)); }); } else if (vtable !== undefined) { let offset = 0; for (let entry of functions) { const isPadding = Array.isArray(entry); if (isPadding) { const [n] = entry; offset += n * pointerSize; continue; } let impl; try { impl = vtable.add(offset).readPointer(); } catch (e) { onError(new Error(`Failed to read from vtable at offset ${offset}: ${e}`)); break; } listeners.push(intercept(entry, impl)); offset += pointerSize; } } else { throw new Error('Either a module or a vtable must be specified'); } return new Session(listeners); } trace.func = func; trace.argIn = argIn; trace.argOut = argOut; trace.argInOut = argInOut; trace.retval = retval; trace.padding = padding; trace.bind = bind; trace.when = when; trace.types = { BOOL: bool(), BYTE: byte(), SHORT: short(), INT: int(), POINTER: pointer(), BYTE_ARRAY: byteArray(), UTF8: utf8(), UTF16: utf16(), CSTRING: cstring(), bool: bool, byte: byte, short: short, int: int, pointer: pointer, byteArray: byteArray, utf8: utf8, utf16: utf16, cstring: cstring }; function makeInterceptor (spec) { const {shouldSkip, onEvent, onEnter, onLeave, onError} = spec.callbacks; return function (func, impl) { const name = func.name; const inputActions = []; const outputActions = []; if (!computeActions(func, inputActions, outputActions)) { onError(new Error(`Oops. It seems ${module}!${name} has circular dependencies.`)); return; } const numArgs = func.args.length; const numInputActions = inputActions.length; const numOutputActions = outputActions.length; return Interceptor.attach(impl, { onEnter (args) { if (shouldSkip?.(this)) { this._skip = true; return; } const values = []; for (let i = 0; i !== numArgs; i++) { values.push(args[i]); } const event = new Event(name); for (let i = 0; i !== numInputActions; i++) { const [action, params] = inputActions[i]; action(values, event, params); } if (onEnter !== undefined) { onEnter.call(this, event, args); } this.values = values; this.event = event; }, onLeave (retval) { if (this._skip === true) { return; } const values = this.values; const event = this.event; values.push(retval); for (let i = 0; i !== numOutputActions; i++) { const [action, params] = outputActions[i]; action(values, event, params); } if (onLeave !== undefined) { onLeave.call(this, event, retval); } onEvent(event); } }); }; } function computeActions (func, inputActions, outputActions) { const args = func.args.slice(); if (func.ret !== null) { args.push(func.ret); } const satisfied = new Set(); let previousSatisfiedSize; do { previousSatisfiedSize = satisfied.size; args.forEach(function (arg, index) { if (satisfied.has(arg.name)) { return; } const remaining = arg.requires.filter(dep => !satisfied.has(dep)); if (remaining.length === 0) { inputActions.push(computeAction(arg, index)); satisfied.add(arg.name); } }); } while (satisfied.size !== previousSatisfiedSize); satisfied.add('$out'); do { previousSatisfiedSize = satisfied.size; args.forEach(function (arg, index) { if (satisfied.has(arg.name)) { return; } const remaining = arg.requires.filter(dep => !satisfied.has(dep)); if (remaining.length === 0) { outputActions.push(computeAction(arg, index)); satisfied.add(arg.name); } }); } while (satisfied.size !== previousSatisfiedSize); return !args.some(arg => !satisfied.has(arg.name)); } function computeAction (arg, index) { const {name, type, condition} = arg; const hasDependentType = Array.isArray(type); const hasCondition = condition !== null; if (hasDependentType) { if (hasCondition) { return [readValueWithDependentTypeConditionally, [index, name, type[0].parse, type[1], condition]]; } return [readValueWithDependentType, [index, name, type[0].parse, type[1]]]; } if (hasCondition) { return [readValueConditionally, [index, name, type.parse, condition]]; } return [readValue, [index, name, type.parse]]; } function readValue (values, event, params) { const [index, name, parse] = params; event.set(name, parse(values[index])); } function readValueConditionally (values, event, params) { const [index, name, parse, condition] = params; if (condition.predicate(event.get(condition.value))) { event.set(name, parse(values[index])); } } function readValueWithDependentType (values, event, params) { const [index, name, parse, binding] = params; const typeParameters = {}; typeParameters[binding.property] = event.get(binding.value); event.set(name, parse(values[index], typeParameters)); } function readValueWithDependentTypeConditionally (values, event, params) { const [index, name, parse, binding, condition] = params; if (condition.predicate(event.get(condition.value))) { const typeParameters = {}; typeParameters[binding.property] = event.get(binding.value); event.set(name, parse(values[index], typeParameters)); } } function func (name, ret, args) { return { name: name, ret: ret, args: args }; } function argIn (name, type, condition) { return arg(IN, name, type, condition); } function argOut (name, type, condition) { return arg(OUT, name, type, condition); } function argInOut (name, type, condition) { return arg(IN_OUT, name, type, condition); } function arg (direction, name, type, condition) { condition = condition || null; return { direction: direction, name: name, type: type, condition: condition, requires: dependencies(direction, type, condition) }; } function retval (type, condition) { return argOut('result', type, condition); } function padding (n) { return [n]; } function bind (property, value) { return { property: property, value: value }; } function when (value, predicate) { return { value: value, predicate: predicate }; } function dependencies (direction, type, condition) { const result = []; if (direction === OUT) { result.push('$out'); } if (Array.isArray(type)) { result.push(type[1].value); } if (condition !== null) { result.push(condition.value); } return result; } function bool () { return { parse (rawValue) { return !!rawValue.toInt32(); }, read (ptr) { return !!ptr.readU8(); } }; } function byte () { return { parse (rawValue) { return rawValue.toInt32() & 0xff; }, read (ptr) { return ptr.readU8(); } }; } function short () { return { parse (rawValue) { return rawValue.toInt32() & 0xffff; }, read (ptr) { return ptr.readShort(); } }; } function int () { return { parse (rawValue) { return rawValue.toInt32(); }, read (ptr) { return ptr.readInt(); } }; } function pointer (pointee) { return { parse (rawValue, parameters) { if (pointee) { if (rawValue.isNull()) { return null; } else { return pointee.read(rawValue, parameters); } } else { return rawValue; } }, read (ptr) { return ptr.readPointer(); } }; } function byteArray () { return pointer({ read (ptr, parameters) { return ptr.readByteArray(parameters.length); } }); } function utf8 () { return pointer({ read (ptr, parameters) { const length = (parameters === undefined) ? -1 : parameters.length; return ptr.readUtf8String(length); } }); } function utf16 () { return pointer({ read (ptr, parameters) { const length = (parameters === undefined) ? -1 : parameters.length; return ptr.readUtf16String(length); } }); } function cstring () { return pointer({ read (ptr, parameters) { const length = (parameters === undefined) ? -1 : parameters.length; return ptr.readCString(length); } }); } class Session { constructor (listeners) { this._listeners = listeners; } stop () { this._listeners.forEach(listener => { listener.detach(); }); } } class Event { constructor (name) { this.name = name; this.args = {}; } get (key) { return (key === 'result') ? this.result : this.args[key]; } set (key, value) { if (key === 'result') { this.result = value; } else { this.args[key] = value; } } } ================================================ FILE: package.json ================================================ { "name": "frida-trace", "version": "4.0.1", "description": "Trace APIs declaratively", "keywords": [ "frida-gum" ], "main": "index.js", "type": "module", "files": [ "/index.js" ], "repository": { "type": "git", "url": "git+https://github.com/nowsecure/frida-trace.git" }, "license": "MIT", "bugs": { "url": "https://github.com/nowsecure/frida-trace/issues" }, "homepage": "https://github.com/nowsecure/frida-trace#readme" }