Full Code of nowsecure/frida-trace for AI

main 9b92c1762373 cached
12 files
28.3 KB
7.8k tokens
77 symbols
1 requests
Download .txt
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] + ' <device-id> 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"
}
Download .txt
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
Download .txt
SYMBOL INDEX (77 symbols across 6 files)

FILE: example/bin/trace.js
  class ConsoleUI (line 6) | class ConsoleUI {
    method onEvents (line 7) | onEvents(events) {
    method onOutput (line 31) | onOutput(pid, fd, data) {
    method onError (line 40) | onError(error) {
  function annotate (line 46) | function annotate(value) {
  function usage (line 53) | function usage() {

FILE: example/lib/agent/index.js
  constant INT (line 10) | const INT = types.INT;
  constant POINTER (line 11) | const POINTER = types.POINTER;
  constant UTF8 (line 12) | const UTF8 = types.UTF8;
  constant BYTE_ARRAY (line 13) | const BYTE_ARRAY = types.BYTE_ARRAY;
  method dispose (line 19) | dispose() {
  method onEvent (line 37) | onEvent(event) {
  method onError (line 40) | onError(e) {
  function scheduleEvent (line 49) | function scheduleEvent(event) {
  function flushEvents (line 59) | function flushEvents() {
  function isGreaterThanZero (line 100) | function isGreaterThanZero(value) {

FILE: example/lib/application.js
  class Application (line 8) | class Application {
    method constructor (line 9) | constructor(ui) {
    method run (line 21) | async run(target) {
    method _waitUntilDone (line 64) | _waitUntilDone() {
    method _onOutput (line 68) | _onOutput(pid, fd, data) {
    method _onMessage (line 72) | _onMessage(message, data) {

FILE: frida-trace-tools/bin/generate-boilerplate.js
  function writeCode (line 11) | function writeCode (data) {
  function apiDescriptionToCode (line 17) | function apiDescriptionToCode (api) {
  function functionDescriptionsToCode (line 26) | function functionDescriptionsToCode (funcs) {
  function structDescriptionsToCode (line 63) | function structDescriptionsToCode (structs) {
  function strippedName (line 101) | function strippedName (name) {
  function structDescriptionToCode (line 105) | function structDescriptionToCode (funcs) {
  function funcDescriptionToCode (line 131) | function funcDescriptionToCode (func, indentLevel) {
  function argDescriptionToCode (line 144) | function argDescriptionToCode (arg) {
  function retTypeDescriptionToCode (line 158) | function retTypeDescriptionToCode (type) {
  function typeDescriptionToCode (line 166) | function typeDescriptionToCode (type) {
  function argDirection (line 184) | function argDirection (arg) {
  function isPointer (line 196) | function isPointer (type) {
  function makeIndentation (line 200) | function makeIndentation (level) {

FILE: frida-trace-tools/bin/parse-header.js
  function parseHeader (line 21) | function parseHeader (path) {
  function parseFunction (line 53) | function parseFunction (cursor) {
  function parseStruct (line 76) | function parseStruct (cursor) {
  function isFunctionPointer (line 119) | function isFunctionPointer (type) {
  function parseType (line 130) | function parseType (type) {
  function parseQualifiers (line 151) | function parseQualifiers (type) {

FILE: index.js
  constant OUT (line 2) | const OUT = Symbol('out');
  constant IN_OUT (line 3) | const IN_OUT = Symbol('in-out');
  function trace (line 7) | function trace (spec) {
  function makeInterceptor (line 89) | function makeInterceptor (spec) {
  function computeActions (line 152) | function computeActions (func, inputActions, outputActions) {
  function computeAction (line 196) | function computeAction (arg, index) {
  function readValue (line 214) | function readValue (values, event, params) {
  function readValueConditionally (line 220) | function readValueConditionally (values, event, params) {
  function readValueWithDependentType (line 228) | function readValueWithDependentType (values, event, params) {
  function readValueWithDependentTypeConditionally (line 236) | function readValueWithDependentTypeConditionally (values, event, params) {
  function func (line 246) | function func (name, ret, args) {
  function argIn (line 254) | function argIn (name, type, condition) {
  function argOut (line 258) | function argOut (name, type, condition) {
  function argInOut (line 262) | function argInOut (name, type, condition) {
  function arg (line 266) | function arg (direction, name, type, condition) {
  function retval (line 278) | function retval (type, condition) {
  function padding (line 282) | function padding (n) {
  function bind (line 286) | function bind (property, value) {
  function when (line 293) | function when (value, predicate) {
  function dependencies (line 300) | function dependencies (direction, type, condition) {
  function bool (line 318) | function bool () {
  function byte (line 329) | function byte () {
  function short (line 340) | function short () {
  function int (line 351) | function int () {
  function pointer (line 362) | function pointer (pointee) {
  function byteArray (line 381) | function byteArray () {
  function utf8 (line 389) | function utf8 () {
  function utf16 (line 398) | function utf16 () {
  function cstring (line 407) | function cstring () {
  class Session (line 416) | class Session {
    method constructor (line 417) | constructor (listeners) {
    method stop (line 421) | stop () {
  class Event (line 428) | class Event {
    method constructor (line 429) | constructor (name) {
    method get (line 434) | get (key) {
    method set (line 438) | set (key, value) {
Condensed preview — 12 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (31K chars).
[
  {
    "path": ".gitignore",
    "chars": 73,
    "preview": "/example/lib/_agent.js\n/example/node_modules\n/node_modules\nnpm-debug.log\n"
  },
  {
    "path": "LICENSE.md",
    "chars": 1081,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2016 NowSecure, Inc\n\nPermission is hereby granted, free of charge, to any person ob"
  },
  {
    "path": "README.md",
    "chars": 2134,
    "preview": "# frida-trace\n\nTrace APIs declaratively through [Frida](https://www.frida.re).\n\n## Example\n\n```js\nimport trace from 'fri"
  },
  {
    "path": "example/bin/trace.js",
    "chars": 2009,
    "preview": "#!/usr/bin/env node\nimport Application from '../lib/application';\nimport chalk from 'chalk';\nimport { hexy } from 'hexy'"
  },
  {
    "path": "example/lib/agent/index.js",
    "chars": 2075,
    "preview": "import trace from 'frida-trace';\n\nconst func = trace.func;\nconst argIn = trace.argIn;\nconst argOut = trace.argOut;\nconst"
  },
  {
    "path": "example/lib/application.js",
    "chars": 2337,
    "preview": "import frida from 'frida';\nimport fs from 'fs';\nimport path from 'path';\nimport util from 'util';\n\nconst readFile = util"
  },
  {
    "path": "example/package.json",
    "chars": 536,
    "preview": "{\n  \"name\": \"frida-trace-example\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Example showing how to use frida-trace\",\n  \"m"
  },
  {
    "path": "frida-trace-tools/bin/generate-boilerplate.js",
    "chars": 4412,
    "preview": "#!/usr/bin/env node\nimport concat from 'concat-stream';\n\nconst input = process.stdin;\ninput.setEncoding('utf-8');\ninput."
  },
  {
    "path": "frida-trace-tools/bin/parse-header.js",
    "chars": 3653,
    "preview": "#!/usr/bin/env node\nimport clang from 'frida-libclang';\nimport dynamicClang from 'frida-libclang/lib/dynamic_clang';\n\nco"
  },
  {
    "path": "frida-trace-tools/package.json",
    "chars": 650,
    "preview": "{\n  \"name\": \"frida-trace-tools\",\n  \"version\": \"3.3.0\",\n  \"description\": \"Generate trace declarations from docs\",\n  \"file"
  },
  {
    "path": "index.js",
    "chars": 9559,
    "preview": "const IN = Symbol('in');\nconst OUT = Symbol('out');\nconst IN_OUT = Symbol('in-out');\n\nconst pointerSize = Process.pointe"
  },
  {
    "path": "package.json",
    "chars": 475,
    "preview": "{\n  \"name\": \"frida-trace\",\n  \"version\": \"4.0.1\",\n  \"description\": \"Trace APIs declaratively\",\n  \"keywords\": [\n    \"frida"
  }
]

About this extraction

This page contains the full source code of the nowsecure/frida-trace GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 12 files (28.3 KB), approximately 7.8k tokens, and a symbol index with 77 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!