diff --git a/.github/workflows/_github_api_helper.py b/.github/workflows/_github_api_helper.py new file mode 100644 index 0000000..b7f3d3d --- /dev/null +++ b/.github/workflows/_github_api_helper.py @@ -0,0 +1,28 @@ +import requests +import os +import time + +def github_api_get(url): + for i in range(3): + changes = None + hdrs = {} + if 'GITHUB_TOKEN' in os.environ: # Github will sometimes rate limit if we don't pass token + print("Using GITHUB_TOKEN!") + hdrs = { + "Authorization": os.environ['GITHUB_TOKEN'] + } + rjson = None + try: + r = requests.get(url, headers=hdrs) + rjson = r.json() + if r.status_code != 200: + raise Exception() + except: + print("Error occurred in GitHub API, Retrying. Server return: %s" % rjson) + time.sleep(2.0) + if i == 2: + raise + else: + return rjson + + # should never get here \ No newline at end of file diff --git a/.github/workflows/auto-update.yml b/.github/workflows/auto-update.yml new file mode 100644 index 0000000..ed78d42 --- /dev/null +++ b/.github/workflows/auto-update.yml @@ -0,0 +1,235 @@ +name: Github CI + +on: + push: + workflow_dispatch: + inputs: + debug_enabled: + description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' + required: false + default: false + repository_dispatch: + schedule: + - cron: "0 * */7 * *" # https://crontab.guru/ + +defaults: + run: + shell: bash -x -e -c ". $0" "{0}" + working-directory: Fermion + +jobs: + update: + runs-on: ubuntu-latest + outputs: + FRIDA_VER: ${{ steps.check.outputs.FRIDA_VER }} + FRIDA_GUM_VER: ${{ steps.check.outputs.FRIDA_GUM_VER }} + FRIDA_ELECTRON: ${{ steps.check.outputs.FRIDA_ELECTRON }} + FERMION_TAG: ${{ steps.check.outputs.FERMION_TAG }} + NO_BUILD: ${{ steps.checkTag.outputs.exists }} + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 2 + submodules: recursive + + - uses: gautamkrishnar/keepalive-workflow@master + + # Enable tmate debugging of manually-triggered workflows if the input option was provided + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 + if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }} + env: + SECRETS_CONTEXT: ${{ toJson(secrets) }} + + - name: Populate Fermion Variables + id: check + run: python3 ../.github/workflows/populate_variables.py + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # Check if we are building a new Fermion tag + - uses: mukunku/tag-exists-action@v1.1.0 + id: checkTag + with: + tag: v${{ steps.check.outputs.FERMION_TAG }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + build: + needs: + - update + if: ${{ needs.update.outputs.NO_BUILD == 'false' }} + + # Build Matrix + strategy: + matrix: + os: [macos-latest, ubuntu-latest, windows-latest] + + # Create tasks across our build matrix + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 2 + submodules: recursive + + - uses: actions/setup-node@v3 + with: + node-version: '16' + + # Update dependency versions in the package manifest & install + - name: Update Frida package + id: npmi + run: | + export npm_config_runtime=electron + export npm_config_target=${{needs.update.outputs.FRIDA_ELECTRON}} + npm install + npm i @types/frida-gum@${{needs.update.outputs.FRIDA_GUM_VER}} + npm i frida@${{needs.update.outputs.FRIDA_VER}} + echo "ELECTRON_VER=${{needs.update.outputs.FRIDA_ELECTRON}}" >> $GITHUB_OUTPUT + + - name: Prepare building.. + run: | + OUT='${{ github.workspace }}/build' + echo "OUT=$OUT" >> $GITHUB_ENV + rm -rf "$OUT" + mkdir "$OUT" + + # Package Fermion across our build matrix + - name: Build Windows + if: runner.os == 'Windows' + run: | + npx electron-packager . --icon ./src/images/fermion-ico.ico --out "$OUT" + - name: Build Linux + if: runner.os == 'Linux' + run: | + npx electron-packager . --icon ./src/images/fermion-ico.png --out "$OUT" + - name: Build macOS + if: runner.os == 'macOS' + run: | + npx electron-packager . --icon ./src/images/fermion-ico.icns --out "$OUT" + + # Archive fermion + - name: Package build asset + run: | + cd "$OUT" + find . -type d -name node_modules -prune -exec ls -ald "{}" \; + ZIPNAME="fermion-${{ matrix.os }}-v${{ needs.update.outputs.FERMION_TAG }}" + if command -v zip > /dev/null; then + (cd *; zip -qr "../$ZIPNAME.zip" .) + else + (cd *; python3 -c "import shutil; shutil.make_archive('../$ZIPNAME', 'zip', '.')") + fi + ls -al + + # Upload action artifact as-is + - name: Upload to GitHub Actions artifact + uses: NyaMisty/upload-artifact-as-is@master + with: + path: ${{ env.OUT }}/fermion-*.zip + + release: + runs-on: ubuntu-latest + needs: + - update + - build + name: "release" + + steps: + - name: "Create artifact directory" + run: | + mkdir -p build_output + working-directory: ${{ runner.temp }} + + - name: "Download all artifacts" + uses: actions/download-artifact@v2 + with: + path: ${{ runner.temp }}/build_output + + - name: "Rearrange artifacts" + run: | + find build_output + mkdir -p build_release + mv build_output/*/* build_release + ls build_release + if [ "$(ls -A build_release)" ]; then exit 0; else exit 1; fi + working-directory: ${{ runner.temp }} + + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 2 + submodules: recursive + + - name: Update package index + run: | + export npm_config_runtime=electron + export npm_config_target=${{needs.update.outputs.FRIDA_ELECTRON}} + npm install + npm i @types/frida-gum@${{needs.update.outputs.FRIDA_GUM_VER}} + npm i frida@${{needs.update.outputs.FRIDA_VER}} + + - name: Update badgen & compile instructions + run: | + # Set new frida version for badgen + perl -i -pe 's|badgen.net/badge/Frida-Node/v[\d.]+?/grey|badgen.net/badge/Frida-Node/v${{ needs.update.outputs.FRIDA_VER }}/grey|' ../README.md + + # Set new gum version for badgen + perl -i -pe 's|badgen.net/badge/Frida-Gum/v[\d.]+?/grey|badgen.net/badge/Frida-Gum/v${{ needs.update.outputs.FRIDA_GUM_VER }}/grey|' ../README.md + + # Set new electron version for README + perl -i -pe 's|npm_config_target=[\d.]+|npm_config_target=${{ needs.update.outputs.FRIDA_ELECTRON }}|' ../README.md + + # This is still a todo + - name: Update Frida assets + run: | + /bin/bash .github/workflows/update_fermion_asset.sh + working-directory: ${{ github.workspace }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # Commit + - name: Update Versions to Git + id: bumpver + run: | + if ! [ -z "$(git status --untracked-files=no --porcelain)" ]; then + AUTOUPDATE_PATTERN="^\[AutoUpdate\]" + preserve_branch=0 + if ! [[ "$(git log -1 --pretty=%B)" =~ $AUTOUPDATE_PATTERN ]]; then + preserve_branch=1 + git branch -f before_auto_update + fi + + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git commit -a -m "[AutoUpdate] v${{ needs.update.outputs.FERMION_TAG }}" + + git push origin master + + if [[ "$preserve_branch" == "1" ]]; then + git push -f origin before_auto_update + fi + fi + echo "CURRENT_SHA=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + + # Release + - name: Create New Release and Upload + if: ${{ !startsWith(github.ref, 'refs/tags/') }} + uses: ncipollo/release-action@v1 + with: + artifacts: "${{ runner.temp }}/build_release/*" + name: "${{ format('Fermion v{0}', needs.update.outputs.FERMION_TAG, needs.update.outputs.FRIDA_VER, needs.update.outputs.FRIDA_GUM_VER) }}" + body: "${{ format('Electron: {0}\nFrida: {1}\nGumJS: {2}\nBuild: Windows, Linux, Mac\n', needs.update.outputs.FRIDA_ELECTRON, needs.update.outputs.FRIDA_VER, needs.update.outputs.FRIDA_GUM_VER) }}" + tag: v${{ needs.update.outputs.FERMION_TAG }} + commit: ${{ steps.bumpver.outputs.CURRENT_SHA }} + prerelease: false + allowUpdates: true + + # NOTE: edit this to false & true if you want to preserve original artifact + removeArtifacts: true + replacesArtifacts: false + artifactErrorsFailBuild: true + token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/populate_variables.py b/.github/workflows/populate_variables.py new file mode 100644 index 0000000..b1bdb7c --- /dev/null +++ b/.github/workflows/populate_variables.py @@ -0,0 +1,38 @@ +import requests +import re +import json +import os + +from _github_api_helper import github_api_get + +# Get latests Frida version +fridaLatestRelease = github_api_get('https://api.github.com/repos/frida/frida/releases/latest') +fridaVer = fridaLatestRelease['tag_name'] + +# Get latests Frida Gum version +fridaGumVer = requests.get('https://registry.npmjs.org/@types/frida-gum').json()['dist-tags']['latest'] + +# Get Electron ver from package index +with open(os.path.dirname(os.path.abspath(__file__)) + "/../../" + "Fermion/package.json", 'r') as f: + fridaElectronVer = json.load(f)['dependencies']['electron'] + +# Get package ver from package index +with open(os.path.dirname(os.path.abspath(__file__)) + "/../../" + "Fermion/package.json", 'r') as f: + fermionVer = json.load(f)['version'] +fermionVer = fermionVer.split("-")[0] +fermionTag = "%s" % (fermionVer) + +import os, json +if 'GITHUB_OUTPUT' in os.environ: + f = open(os.environ['GITHUB_OUTPUT'], "a") + output = lambda x: f.write(x) +else: + output = print + +print("[?] Build on Electron: ", fridaElectronVer) +print("[?] Latest Frida version: ", fridaVer) + +output("FRIDA_VER=%s\n" % fridaVer) +output("FRIDA_GUM_VER=%s\n" % fridaGumVer) +output("FRIDA_ELECTRON=%s\n" % fridaElectronVer) +output("FERMION_TAG=%s\n" % fermionTag) diff --git a/.github/workflows/update_fermion_asset.sh b/.github/workflows/update_fermion_asset.sh new file mode 100644 index 0000000..9316619 --- /dev/null +++ b/.github/workflows/update_fermion_asset.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +####### +## This needs more work +####### + +## +# Auto-update JS docs in about.html +# |_ It may be best to integrate a markdown reader into about this way docs can +# be ingested from https://github.com/frida/frida-website/blob/master/_i18n/en/_docs/javascript-api.md +## + +# Get updated js docs +#docOutput=$(curl -s "https://frida.re/docs/javascript-api/"); +#copyStart=$(echo "$docOutput"| grep -n "Table of contents" |awk -F: '{print $1}'); +#copyEnd=$(echo "$docOutput"| grep -n "section-nav" |awk -F: '{print $1}'); +#newDoc=$(echo "$docOutput" | sed -n "$copyStart,$((copyEnd - 1))p"); + +## +# Auto-generate version image for about.html +## \ No newline at end of file diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 7483a52..a9ca54f 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -114,4 +114,22 @@ -= Fermion v1.8.1 =- -* Minor release to bugfix #15 \ No newline at end of file +* Minor release to bugfix #15 + +-= Fermion v1.9 =- + +* Upgrade Electron v13.3.0 -> v21.0.0 +* Upgrade Frida v15.1.3 -> v16.0.5 +* Some fixes added for changes in Electron v14+ +* Change Frida TypeScript language bindings to use the Frida Gum node package instead or a local resource +* Allow Fermion to dynamically display the current Frida version on load +* Allow Fermion to dynamically display it's own version on load +* Fix for incorrectly parsing application arguments in some cases +* Implemented GitHub CI for Fermion +* MacOS release packages reintroduced with CI +* A number of documentation, process, HTML changes +* Added scrollbar to "Process List" +* Make User, PID, PPID and Process columns sortable +* Closed most of the items related to #14 +* Update inline documentation +* NOTE: A big thanks to @MiscMisty for the great PR \ No newline at end of file diff --git a/Fermion/core.js b/Fermion/core.js index dd2cc84..3cd551f 100644 --- a/Fermion/core.js +++ b/Fermion/core.js @@ -26,6 +26,10 @@ function createWindow() { webviewTag: true } }); + + // needed after electron v14.0.1 + // https://stackoverflow.com/questions/69059668/enableremotemodule-is-missing-from-electron-v14-typescript-type-definitions/69059669#69059669 + require('@electron/remote/main').enable(bWin.webContents); // and load the index.html of the app. bWin.loadFile(path.join(__dirname, '/pages/index.html')); diff --git a/Fermion/package.json b/Fermion/package.json index 72cd772..bdfd49c 100644 --- a/Fermion/package.json +++ b/Fermion/package.json @@ -1,6 +1,6 @@ { "name": "fermion", - "version": "1.8.1", + "version": "1.9.0", "description": "Fermion is a stand-alone Frida electron tool.", "main": "core.js", "scripts": { @@ -11,8 +11,9 @@ "license": "BSD-3-Clause", "dependencies": { "@electron/remote": "^2.0.0", - "electron": "13.3.0", - "frida": "15.1.3", + "@types/frida-gum": "^18.3.0", + "electron": "21.0.0", + "frida": "^16.0.7", "jquery": "3.6.0", "monaco-editor": "0.30.0", "mutex-promise": "0.1.0", diff --git a/Fermion/pages/device.html b/Fermion/pages/device.html index 5664299..0a5de23 100644 --- a/Fermion/pages/device.html +++ b/Fermion/pages/device.html @@ -3,6 +3,7 @@ @@ -73,62 +74,63 @@

Remote Socket + \ No newline at end of file diff --git a/Fermion/pages/docs.html b/Fermion/pages/docs.html index d1245b6..30e9625 100644 --- a/Fermion/pages/docs.html +++ b/Fermion/pages/docs.html @@ -25,25 +25,26 @@ -webkit-app-region:no-drag; position:fixed; top:40px; - margin-top: 8px; - padding:4px; - overflow-x: hidden; - overflow-x: auto; - text-align:justify; + margin-top: 8px; + padding:4px; + overflow-x: hidden; + overflow-x: auto; + text-align:justify; overflow-y:scroll; height: 94vh; width: 100%; } .item_found { - background: yellow; - color: black; - } + background: yellow; + color: black; + } - .highlighted + .highlighted { background-color: yellow; } + .highlight { background-color: #fff34d; @@ -54,6 +55,7 @@ -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.7); /* Saf3.0+, Chrome */ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.7); /* Opera 10.5+, IE 9.0 */ } + .highlight { padding: 1px 4px; @@ -92,5015 +94,5021 @@
- -

Table of contents

- -
    -
  1. Runtime information -
      -
    1. Frida
    2. -
    3. Script
    4. -
    -
  2. -
  3. Process, Thread, Module and Memory -
      -
    1. Thread
    2. -
    3. Process
    4. -
    5. Module
    6. -
    7. ModuleMap
    8. -
    9. Memory
    10. -
    11. MemoryAccessMonitor
    12. -
    13. CModule
    14. -
    15. ApiResolver
    16. -
    17. DebugSymbol
    18. -
    19. Kernel
    20. -
    -
  4. -
  5. Data Types, Function and Callback -
      -
    1. Int64
    2. -
    3. UInt64
    4. -
    5. NativePointer
    6. -
    7. ArrayBuffer
    8. -
    9. NativeFunction
    10. -
    11. NativeCallback
    12. -
    13. SystemFunction
    14. -
    -
  6. -
  7. Network -
      -
    1. Socket
    2. -
    3. SocketListener
    4. -
    5. SocketConnection
    6. -
    -
  8. -
  9. File and Stream -
      -
    1. File
    2. -
    3. IOStream
    4. -
    5. InputStream
    6. -
    7. OutputStream
    8. -
    9. UnixInputStream
    10. -
    11. UnixOutputStream
    12. -
    13. Win32InputStream
    14. -
    15. Win32OutputStream
    16. -
    -
  10. -
  11. Database -
      -
    1. SqliteDatabase
    2. -
    3. SqliteStatement
    4. -
    -
  12. -
  13. Instrumentation -
      -
    1. Interceptor
    2. -
    3. Stalker
    4. -
    5. ObjC
    6. -
    7. Java
    8. -
    -
  14. -
  15. CPU Instruction -
      -
    1. Instruction
    2. -
    3. X86Writer
    4. -
    5. X86Relocator
    6. -
    7. x86 enum types
    8. -
    9. ArmWriter
    10. -
    11. ArmRelocator
    12. -
    13. ThumbWriter
    14. -
    15. ThumbRelocator
    16. -
    17. ARM enum types
    18. -
    19. Arm64Writer
    20. -
    21. Arm64Relocator
    22. -
    23. AArch64 enum types
    24. -
    25. MipsWriter
    26. -
    27. MipsRelocator
    28. -
    29. MIPS enum types
    30. -
    -
  16. -
  17. Other -
      -
    1. Console
    2. -
    3. Hexdump
    4. -
    5. Shorthand
    6. -
    7. Communication between host and injected process
    8. -
    9. Timing events
    10. -
    11. Garbage collection
    12. -
    -
  18. -
- -
- -

Runtime information

- -

Frida

- - - -

Script

- - - -
- -

Process, Thread, Module and Memory

- -

Thread

- - - -
const f = Module.getExportByName('libcommonCrypto.dylib',
-    'CCCryptorCreate');
-Interceptor.attach(f, {
-  onEnter(args) {
-    console.log('CCCryptorCreate called from:\n' +
-        Thread.backtrace(this.context, Backtracer.ACCURATE)
-        .map(DebugSymbol.fromAddress).join('\n') + '\n');
-  }
-});
- - - -

Process

- - - -

Module

- -

Objects returned by e.g. Module.load() and Process.enumerateModules().

- - - -
-
enumerateSymbols() is only available on i/macOS and Linux-based OSes
-

- We would love to support this on the other platforms too, so if you find - this useful and would like to help out, please get in touch. You may also - find the DebugSymbol API adequate, depending on your use-case. -

-
- - - -

ModuleMap

- - - -

Memory

- - - -
// Find the module for the program itself, always at index 0:
-const m = Process.enumerateModules()[0];
-
-// Or load a module by name:
-//const m = Module.load('win32u.dll');
-
-// Print its properties:
-console.log(JSON.stringify(m));
-
-// Dump it from its base address:
-console.log(hexdump(m.base));
-
-// The pattern that you are interested in:
-const pattern = '00 00 00 00 ?? 13 37 ?? 42';
-
-Memory.scan(m.base, m.size, pattern, {
-  onMatch(address, size) {
-    console.log('Memory.scan() found match at', address,
-        'with size', size);
-
-    // Optionally stop scanning early:
-    return 'stop';
-  },
-  onComplete() {
-    console.log('Memory.scan() complete');
-  }
-});
-
-const results = Memory.scanSync(m.base, m.size, pattern);
-console.log('Memory.scanSync() result:\n' +
-    JSON.stringify(results));
- - - -
Memory.protect(ptr('0x1234'), 4096, 'rw-');
- - - -
const getLivesLeft = Module.getExportByName('game-engine.so', 'get_lives_left');
-const maxPatchSize = 64; // Do not write out of bounds, may be a temporary buffer!
-Memory.patchCode(getLivesLeft, maxPatchSize, code => {
-  const cw = new X86Writer(code, { pc: getLivesLeft });
-  cw.putMovRegU32('eax', 9000);
-  cw.putRet();
-  cw.flush();
-});
- - - -

MemoryAccessMonitor

- - - -

CModule

- - - -

Examples

- -
const cm = new CModule(`
-#include <stdio.h>
-
-void hello(void) {
-  printf("Hello World from CModule\\n");
-}
-`);
-
-console.log(JSON.stringify(cm));
-
-const hello = new NativeFunction(cm.hello, 'void', []);
-hello();
- -

Which you might load using Frida’s REPL:

- -
$ frida -p 0 -l example.js
- -

(The REPL monitors the file on disk and reloads the script on change.)

- -

You can then type hello() in the REPL to call the C function.

- -

For prototyping we recommend using the Frida REPL’s built-in CModule support:

- -
$ frida -p 0 -C example.c
- -

You may also add -l example.js to load some JavaScript next to it. -The JavaScript code may use the global variable named cm to access -the CModule object, but only after rpc.exports.init() has been -called, so perform any initialization depending on the CModule there. You may -also inject symbols by assigning to the global object named cs, but this -must be done before rpc.exports.init() gets called.

- -

More details on CModule can be found in the Frida 12.7 release notes.

- -

ApiResolver

- - - -
const resolver = new ApiResolver('module');
-const matches = resolver.enumerateMatches('exports:*!open*');
-const first = matches[0];
-/*
- * Where `first` is an object similar to:
- *
- * {
- *   name: '/usr/lib/libSystem.B.dylib!opendir$INODE64',
- *   address: ptr('0x7fff870135c9')
- * }
- */
- -
const resolver = new ApiResolver('objc');
-const matches = resolver.enumerateMatches('-[NSURL* *HTTP*]');
-const first = matches[0];
-/*
- * Where `first` contains an object like this one:
- *
- * {
- *   name: '-[NSURLRequest valueForHTTPHeaderField:]',
- *   address: ptr('0x7fff94183e22')
- * }
- */
- -

DebugSymbol

- - - -
const f = Module.getExportByName('libcommonCrypto.dylib',
-    'CCCryptorCreate');
-Interceptor.attach(f, {
-  onEnter(args) {
-    console.log('CCCryptorCreate called from:\n' +
-        Thread.backtrace(this.context, Backtracer.ACCURATE)
-        .map(DebugSymbol.fromAddress).join('\n') + '\n');
-  }
-});
- - - -

Kernel

- - - -
Kernel.protect(UInt64('0x1234'), 4096, 'rw-');
- - - -
- -

Data Types, Function and Callback

- -

Int64

- - - -

UInt64

- - - -

NativePointer

- - - -

ArrayBuffer

- - - -

NativeFunction

- - - -

NativeCallback

- - - -

SystemFunction

- - - -
- -

Network

- -

Socket

- - - -

SocketListener

- -

All methods are fully asynchronous and return Promise objects.

- - - -

SocketConnection

- -

Inherits from IOStream. -All methods are fully asynchronous and return Promise objects.

- - - -
- -

File and Stream

- -

File

- - - -

IOStream

- -

All methods are fully asynchronous and return Promise objects.

- - - -

InputStream

- -

All methods are fully asynchronous and return Promise objects.

- - - -

OutputStream

- -

All methods are fully asynchronous and return Promise objects.

- - - -

UnixInputStream

- -

(Only available on UNIX-like OSes.)

- - - -

UnixOutputStream

- -

(Only available on UNIX-like OSes.)

- - - -

Win32InputStream

- -

(Only available on Windows.)

- - - -

Win32OutputStream

- -

(Only available on Windows.)

- - - -

Database

- -

SqliteDatabase

- - - -
const db = SqliteDatabase.open('/path/to/people.db');
-
-const smt = db.prepare('SELECT name, bio FROM people WHERE age = ?');
-
-console.log('People whose age is 42:');
-smt.bindInteger(1, 42);
-let row;
-while ((row = smt.step()) !== null) {
-  const [name, bio] = row;
-  console.log('Name:', name);
-  console.log('Bio:', bio);
-}
-smt.reset();
- - -

SqliteStatement

- - - -
- -

Instrumentation

- -

Interceptor

- - - -
Interceptor.attach(Module.getExportByName('libc.so', 'read'), {
-  onEnter(args) {
-    this.fileDescriptor = args[0].toInt32();
-  },
-  onLeave(retval) {
-    if (retval.toInt32() > 0) {
-      /* do something with this.fileDescriptor */
-    }
-  }
-});
- - - -
Interceptor.attach(Module.getExportByName(null, 'read'), {
-  onEnter(args) {
-    console.log('Context information:');
-    console.log('Context  : ' + JSON.stringify(this.context));
-    console.log('Return   : ' + this.returnAddress);
-    console.log('ThreadId : ' + this.threadId);
-    console.log('Depth    : ' + this.depth);
-    console.log('Errornr  : ' + this.err);
-
-    // Save arguments for processing in onLeave.
-    this.fd = args[0].toInt32();
-    this.buf = args[1];
-    this.count = args[2].toInt32();
-  },
-  onLeave(result) {
-    console.log('----------')
-    // Show argument 1 (buf), saved during onEnter.
-    const numBytes = result.toInt32();
-    if (numBytes > 0) {
-      console.log(hexdump(this.buf, { length: numBytes, ansi: true }));
-    }
-    console.log('Result   : ' + numBytes);
-  }
-})
- -
-
Performance considerations
-

- The callbacks provided have a significant impact on performance. If you only - need to inspect arguments but do not care about the return value, or the - other way around, make sure you omit the callback that you don't need; i.e. - avoid putting your logic in onEnter and leaving onLeave in - there as an empty callback. -

-

- On an iPhone 5S the base overhead when providing just onEnter might be - something like 6 microseconds, and 11 microseconds with both onEnter - and onLeave provided. -

-

- Also be careful about intercepting calls to functions that are called a - bazillion times per second; while send() is - asynchronous, the total overhead of sending a single message is not optimized for - high frequencies, so that means Frida leaves it up to you to batch multiple values - into a single send()-call, based on whether low delay - or high throughput is desired. -

-

- However when hooking hot functions you may use Interceptor in conjunction - with CModule to implement the callbacks in C. -

-
- - - -
const openPtr = Module.getExportByName('libc.so', 'open');
-const open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);
-Interceptor.replace(openPtr, new NativeCallback((pathPtr, flags) => {
-  const path = pathPtr.readUtf8String();
-  log('Opening "' + path + '"');
-  const fd = open(pathPtr, flags);
-  log('Got fd: ' + fd);
-  return fd;
-}, 'int', ['pointer', 'int']));
- - - -

Stalker

- - - -
const mainThread = Process.enumerateThreads()[0];
-
-Stalker.follow(mainThread.id, {
-  events: {
-    call: true, // CALL instructions: yes please
-
-    // Other events:
-    ret: false, // RET instructions
-    exec: false, // all instructions: not recommended as it's
-                 //                   a lot of data
-    block: false, // block executed: coarse execution trace
-    compile: false // block compiled: useful for coverage
-  },
-
-  //
-  // Only specify one of the two following callbacks.
-  // (See note below.)
-  //
-
-  //
-  // onReceive: Called with `events` containing a binary blob
-  //            comprised of one or more GumEvent structs.
-  //            See `gumevent.h` for details about the
-  //            format. Use `Stalker.parse()` to examine the
-  //            data.
-  //
-  //onReceive(events) {
-  //},
-  //
-
-  //
-  // onCallSummary: Called with `summary` being a key-value
-  //                mapping of call target to number of
-  //                calls, in the current time window. You
-  //                would typically implement this instead of
-  //                `onReceive()` for efficiency, i.e. when
-  //                you only want to know which targets were
-  //                called and how many times, but don't care
-  //                about the order that the calls happened
-  //                in.
-  //
-  onCallSummary(summary) {
-  },
-
-  //
-  // Advanced users: This is how you can plug in your own
-  //                 StalkerTransformer, where the provided
-  //                 function is called synchronously
-  //                 whenever Stalker wants to recompile
-  //                 a basic block of the code that's about
-  //                 to be executed by the stalked thread.
-  //
-  //transform(iterator) {
-  //  let instruction = iterator.next();
-  //
-  //  const startAddress = instruction.address;
-  //  const isAppCode = startAddress.compare(appStart) >= 0 &&
-  //      startAddress.compare(appEnd) === -1;
-  //
-  //  do {
-  //    if (isAppCode && instruction.mnemonic === 'ret') {
-  //      iterator.putCmpRegI32('eax', 60);
-  //      iterator.putJccShortLabel('jb', 'nope', 'no-hint');
-  //
-  //      iterator.putCmpRegI32('eax', 90);
-  //      iterator.putJccShortLabel('ja', 'nope', 'no-hint');
-  //
-  //      iterator.putCallout(onMatch);
-  //
-  //      iterator.putLabel('nope');
-  //    }
-  //
-  //    iterator.keep();
-  //  } while ((instruction = iterator.next()) !== null);
-  //},
-  //
-  // The default implementation is just:
-  //
-  //   while (iterator.next() !== null)
-  //     iterator.keep();
-  //
-  // The example above shows how you can insert your own code
-  // just before every `ret` instruction across any code
-  // executed by the stalked thread inside the app's own
-  // memory range. It inserts code that checks if the `eax`
-  // register contains a value between 60 and 90, and inserts
-  // a synchronous callout back into JavaScript whenever that
-  // is the case. The callback receives a single argument
-  // that gives it access to the CPU registers, and it is
-  // also able to modify them.
-  //
-  // function onMatch (context) {
-  //   console.log('Match! pc=' + context.pc +
-  //       ' rax=' + context.rax.toInt32());
-  // }
-  //
-  // Note that not calling keep() will result in the
-  // instruction getting dropped, which makes it possible
-  // for your transform to fully replace certain instructions
-  // when this is desirable.
-  //
-
-  //
-  // Want better performance? Write the callbacks in C:
-  //
-  // /*
-  //  * const cm = new CModule(\`
-  //  *
-  //  * #include <gum/gumstalker.h>
-  //  *
-  //  * static void on_ret (GumCpuContext * cpu_context,
-  //  *     gpointer user_data);
-  //  *
-  //  * void
-  //  * transform (GumStalkerIterator * iterator,
-  //  *            GumStalkerOutput * output,
-  //  *            gpointer user_data)
-  //  * {
-  //  *   cs_insn * insn;
-  //  *
-  //  *   while (gum_stalker_iterator_next (iterator, &insn))
-  //  *   {
-  //  *     if (insn->id == X86_INS_RET)
-  //  *     {
-  //  *       gum_x86_writer_put_nop (output->writer.x86);
-  //  *       gum_stalker_iterator_put_callout (iterator,
-  //  *           on_ret, NULL, NULL);
-  //  *     }
-  //  *
-  //  *     gum_stalker_iterator_keep (iterator);
-  //  *   }
-  //  * }
-  //  *
-  //  * static void
-  //  * on_ret (GumCpuContext * cpu_context,
-  //  *         gpointer user_data)
-  //  * {
-  //  *   printf ("on_ret!\n");
-  //  * }
-  //  *
-  //  * void
-  //  * process (const GumEvent * event,
-  //  *          GumCpuContext * cpu_context,
-  //  *          gpointer user_data)
-  //  * {
-  //  *   switch (event->type)
-  //  *   {
-  //  *     case GUM_CALL:
-  //  *       break;
-  //  *     case GUM_RET:
-  //  *       break;
-  //  *     case GUM_EXEC:
-  //  *       break;
-  //  *     case GUM_BLOCK:
-  //  *       break;
-  //  *     case GUM_COMPILE:
-  //  *       break;
-  //  *     default:
-  //  *       break;
-  //  *   }
-  //  * }
-  //  * `);
-  //  */
-  //
-  //transform: cm.transform,
-  //onEvent: cm.process,
-  //data: ptr(1337) /* user_data */
-  //
-  // You may also use a hybrid approach and only write
-  // some of the callouts in C.
-  //
-});
- -
-
Performance considerations
-

- The callbacks provided have a significant impact on performance. If you only - need periodic call summaries but do not care about the raw events, or the - other way around, make sure you omit the callback that you don't need; i.e. - avoid putting your logic in onCallSummary and leaving - onReceive in there as an empty callback. -

-

- Also note that Stalker may be used in conjunction with CModule, - which means the callbacks may be implemented in C. -

-
- - - -
  onReceive(events) {
-    console.log(Stalker.parse(events, {
-      annotate: true, // to display the type of event
-      stringify: true
-        // to format pointer values as strings instead of `NativePointer`
-        // values, i.e. less overhead if you're just going to `send()` the
-        // thing not actually parse the data agent-side
-    }));
-  },
- - - -

ObjC

- - - -
const { NSSound } = ObjC.classes; /* macOS */
-ObjC.schedule(ObjC.mainQueue, function () {
-    const sound = NSSound.alloc().initWithContentsOfFile_byReference_("/Users/oleavr/.Trash/test.mp3", true);
-    sound.play();
-});
- - - -
Interceptor.attach(myFunction.implementation, {
-  onEnter(args) {
-    // ObjC: args[0] = self, args[1] = selector, args[2-n] = arguments
-    const myString = new ObjC.Object(args[2]);
-    console.log("String argument: " + myString.toString());
-  }
-});
- -
-

This object has some special properties:

- -
    -
  • $kind: string specifying either instance, class or meta-class
  • -
  • $super: an ObjC.Object instance used for chaining up to -super-class method implementations
  • -
  • $superClass: super-class as an ObjC.Object instance
  • -
  • $class: class of this object as an ObjC.Object instance
  • -
  • $className: string containing the class name of this object
  • -
  • $moduleName: string containing the module path of this object
  • -
  • $protocols: object mapping protocol name to ObjC.Protocol -instance for each of the protocols that this object conforms to
  • -
  • $methods: array containing native method names exposed by this object’s -class and parent classes
  • -
  • $ownMethods: array containing native method names exposed by this object’s -class, not including parent classes
  • -
  • $ivars: object mapping each instance variable name to its current -value, allowing you to read and write each through access and assignment
  • -
- -

There is also an equals(other) method for checking whether two instances - refer to the same underlying object.

- -

Note that all method wrappers provide a clone(options) API to create a new - method wrapper with custom NativeFunction options.

-
- - - -
const pendingBlocks = new Set();
-
-Interceptor.attach(..., {
-  onEnter(args) {
-    const block = new ObjC.Block(args[4]);
-    pendingBlocks.add(block); // Keep it alive
-    const appCallback = block.implementation;
-    block.implementation = (error, value) => {
-      // Do your logging here
-      const result = appCallback(error, value);
-      pendingBlocks.delete(block);
-      return result;
-    };
-  }
-});
- - - -
const NSSound = ObjC.classes.NSSound; /* macOS */
-const oldImpl = NSSound.play.implementation;
-NSSound.play.implementation = ObjC.implement(NSSound.play, (handle, selector) => {
-  return oldImpl(handle, selector);
-});
-
-const NSView = ObjC.classes.NSView; /* macOS */
-const drawRect = NSView['- drawRect:'];
-const oldImpl = drawRect.implementation;
-drawRect.implementation = ObjC.implement(drawRect, (handle, selector) => {
-  oldImpl(handle, selector);
-});
- -
-

As the implementation property is a NativeFunction and thus also a - NativePointer, you may also use Interceptor to hook functions:

-
- -
const { NSSound } = ObjC.classes; /* macOS */
-Interceptor.attach(NSSound.play.implementation, {
-  onEnter() {
-    send("[NSSound play]");
-  }
-});
- - - -
const MyConnectionDelegateProxy = ObjC.registerProxy({
-  protocols: [ObjC.protocols.NSURLConnectionDataDelegate],
-  methods: {
-    '- connection:didReceiveResponse:': function (conn, resp) {
-      /* fancy logging code here */
-      /* this.data.foo === 1234 */
-      this.data.target
-          .connection_didReceiveResponse_(conn, resp);
-    },
-    '- connection:didReceiveData:': function (conn, data) {
-      /* other logging code here */
-      this.data.target
-          .connection_didReceiveData_(conn, data);
-    }
-  },
-  events: {
-    forward(name) {
-      console.log('*** forwarding: ' + name);
-    }
-  }
-});
-
-const method = ObjC.classes.NSURLConnection[
-    '- initWithRequest:delegate:startImmediately:'];
-Interceptor.attach(method.implementation, {
-  onEnter(args) {
-    args[3] = new MyConnectionDelegateProxy(args[3], {
-      foo: 1234
-    });
-  }
-});
- - - -
const MyConnectionDelegateProxy = ObjC.registerClass({
-  name: 'MyConnectionDelegateProxy',
-  super: ObjC.classes.NSObject,
-  protocols: [ObjC.protocols.NSURLConnectionDataDelegate],
-  methods: {
-    '- init': function () {
-      const self = this.super.init();
-      if (self !== null) {
-        ObjC.bind(self, {
-          foo: 1234
-        });
-      }
-      return self;
-    },
-    '- dealloc': function () {
-      ObjC.unbind(this.self);
-      this.super.dealloc();
-    },
-    '- connection:didReceiveResponse:': function (conn, resp) {
-      /* this.data.foo === 1234 */
-    },
-    /*
-     * But those previous methods are declared assuming that
-     * either the super-class or a protocol we conform to has
-     * the same method so we can grab its type information.
-     * However, if that's not the case, you would write it
-     * like this:
-     */
-    '- connection:didReceiveResponse:': {
-      retType: 'void',
-      argTypes: ['object', 'object'],
-      implementation: function (conn, resp) {
-      }
-    },
-    /* Or grab it from an existing class: */
-    '- connection:didReceiveResponse:': {
-      types: ObjC.classes
-          .Foo['- connection:didReceiveResponse:'].types,
-      implementation: function (conn, resp) {
-      }
-    },
-    /* Or from an existing protocol: */
-    '- connection:didReceiveResponse:': {
-      types: ObjC.protocols.NSURLConnectionDataDelegate
-          .methods['- connection:didReceiveResponse:'].types,
-      implementation: function (conn, resp) {
-      }
-    },
-    /* Or write the signature by hand if you really want to: */
-    '- connection:didReceiveResponse:': {
-      types: '[email protected]:[email protected]@24',
-      implementation: function (conn, resp) {
-      }
-    }
-  }
-});
-
-const proxy = MyConnectionDelegateProxy.alloc().init();
-/* use `proxy`, and later: */
-proxy.release();
- - - -
const MyDataDelegate = ObjC.registerProtocol({
-  name: 'MyDataDelegate',
-  protocols: [ObjC.protocols.NSURLConnectionDataDelegate],
-  methods: {
-    /* You must specify the signature: */
-    '- connection:didStuff:': {
-      retType: 'void',
-      argTypes: ['object', 'object']
-    },
-    /* Or grab it from a method of an existing class: */
-    '- connection:didStuff:': {
-      types: ObjC.classes
-          .Foo['- connection:didReceiveResponse:'].types
-    },
-    /* Or from an existing protocol method: */
-    '- connection:didStuff:': {
-      types: ObjC.protocols.NSURLConnectionDataDelegate
-          .methods['- connection:didReceiveResponse:'].types
-    },
-    /* Or write the signature by hand if you really want to: */
-    '- connection:didStuff:': {
-      types: '[email protected]:[email protected]@24'
-    },
-    /* You can also make a method optional (default is required): */
-    '- connection:didStuff:': {
-      retType: 'void',
-      argTypes: ['object', 'object'],
-      optional: true
-    }
-  }
-});
- - - -
ObjC.enumerateLoadedClasses({
-  onMatch(name, owner) {
-    console.log('onMatch:', name, owner);
-  },
-  onComplete() {
-  }
-});
- -

The optional options argument is an object where you may specify the -ownedBy property to limit enumeration to modules in a given ModuleMap.

- -

For example:

- -
const appModules = new ModuleMap(isAppModule);
-ObjC.enumerateLoadedClasses({ ownedBy: appModules }, {
-  onMatch(name, owner) {
-    console.log('onMatch:', name, owner);
-  },
-  onComplete() {
-  }
-});
-
-function isAppModule(m) {
-  return !/^\/(usr\/lib|System|Developer)\//.test(m.path);
-}
- - - -
const appModules = new ModuleMap(isAppModule);
-const appClasses = ObjC.enumerateLoadedClassesSync({ ownedBy: appModules });
-console.log('appClasses:', JSON.stringify(appClasses));
-
-function isAppModule(m) {
-  return !/^\/(usr\/lib|System|Developer)\//.test(m.path);
-}
- - - -

Java

- - - -
Java.perform(() => {
-  const groups = Java.enumerateMethods('*youtube*!on*')
-  console.log(JSON.stringify(groups, null, 2));
-});
- -
[
-  {
-    "loader": "<instance: java.lang.ClassLoader, $className: dalvik.system.PathClassLoader>",
-    "classes": [
-      {
-        "name": "com.google.android.apps.youtube.app.watch.nextgenwatch.ui.NextGenWatchLayout",
-        "methods": [
-          "onAttachedToWindow",
-          "onDetachedFromWindow",
-          "onFinishInflate",
-          "onInterceptTouchEvent",
-          "onLayout",
-          "onMeasure",
-          "onSizeChanged",
-          "onTouchEvent",
-          "onViewRemoved"
-        ]
-      },
-      {
-        "name": "com.google.android.apps.youtube.app.search.suggest.YouTubeSuggestionProvider",
-        "methods": [
-          "onCreate"
-        ]
-      },
-      {
-        "name": "com.google.android.libraries.youtube.common.ui.YouTubeButton",
-        "methods": [
-          "onInitializeAccessibilityNodeInfo"
-        ]
-      },
-      
-    ]
-  }
-]
- - - -
Java.perform(() => {
-  const Activity = Java.use('android.app.Activity');
-  Activity.onResume.implementation = function () {
-    send('onResume() got called! Let\'s call the original implementation');
-    this.onResume();
-  };
-});
- - - -
Java.perform(() => {
-  const Activity = Java.use('android.app.Activity');
-  const Exception = Java.use('java.lang.Exception');
-  Activity.onResume.implementation = function () {
-    throw Exception.$new('Oh noes!');
-  };
-});
- -
-

Uses the app’s class loader by default, but you may customize this by - assigning a different loader instance to Java.classFactory.loader.

- -

Note that all method wrappers provide a clone(options) API to create a new - method wrapper with custom NativeFunction options.

-
- - - -
Java.perform(() => {
-  const Activity = Java.use('android.app.Activity');
-  let lastActivity = null;
-  Activity.onResume.implementation = function () {
-    lastActivity = Java.retain(this);
-    this.onResume();
-  };
-});
- - - -
const Activity = Java.use('android.app.Activity');
-const activity = Java.cast(ptr('0x1234'), Activity);
- - - -
const values = Java.array('int', [ 1003, 1005, 1007 ]);
-
-const JString = Java.use('java.lang.String');
-const str = JString.$new(Java.array('byte', [ 0x48, 0x65, 0x69 ]));
- - - -
const SomeBaseClass = Java.use('com.example.SomeBaseClass');
-const X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
-
-const MyTrustManager = Java.registerClass({
-  name: 'com.example.MyTrustManager',
-  implements: [X509TrustManager],
-  methods: {
-    checkClientTrusted(chain, authType) {
-    },
-    checkServerTrusted(chain, authType) {
-    },
-    getAcceptedIssuers() {
-      return [];
-    },
-  }
-});
-
-const MyWeirdTrustManager = Java.registerClass({
-  name: 'com.example.MyWeirdTrustManager',
-  superClass: SomeBaseClass,
-  implements: [X509TrustManager],
-  fields: {
-    description: 'java.lang.String',
-    limit: 'int',
-  },
-  methods: {
-    $init() {
-      console.log('Constructor called');
-    },
-    checkClientTrusted(chain, authType) {
-      console.log('checkClientTrusted');
-    },
-    checkServerTrusted: [{
-      returnType: 'void',
-      argumentTypes: ['[Ljava.security.cert.X509Certificate;', 'java.lang.String'],
-      implementation(chain, authType) {
-        console.log('checkServerTrusted A');
-      }
-    }, {
-      returnType: 'java.util.List',
-      argumentTypes: ['[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'java.lang.String'],
-      implementation(chain, authType, host) {
-        console.log('checkServerTrusted B');
-        return null;
-      }
-    }],
-    getAcceptedIssuers() {
-      console.log('getAcceptedIssuers');
-      return [];
-    },
-  }
-});
- - - -
- -

CPU Instruction

- -

Instruction

- - - -

X86Writer

- - - -

X86Relocator

- - - -

x86 enum types

- - - -

ArmWriter

- - - -

ArmRelocator

- - - -

ThumbWriter

- - - -

ThumbRelocator

- - - -

ARM enum types

- - - -

Arm64Writer

- - - -

Arm64Relocator

- - - -

AArch64 enum types

- - - -

MipsWriter

- - - -

MipsRelocator

- - - -

MIPS enum types

- - - -
- -

Others

- -

Console

- - - -

Hexdump

- - - -
const libc = Module.findBaseAddress('libc.so');
-console.log(hexdump(libc, {
-  offset: 0,
-  length: 64,
-  header: true,
-  ansi: true
-}));
- -
           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
-00000000  7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00  .ELF............
-00000010  03 00 28 00 01 00 00 00 00 00 00 00 34 00 00 00  ..(.........4...
-00000020  34 a8 04 00 00 00 00 05 34 00 20 00 08 00 28 00  4.......4. ...(.
-00000030  1e 00 1d 00 06 00 00 00 34 00 00 00 34 00 00 00  ........4...4...
- -

Shorthand

- - - -

Communication between host and injected process

- - - -
-

For example:

-
- -
rpc.exports = {
-  add(a, b) {
-    return a + b;
-  },
-  sub(a, b) {
-    return new Promise(resolve => {
-      setTimeout(() => {
-        resolve(a - b);
-      }, 100);
-    });
-  }
-};
- -
-

From an application using the Node.js bindings this API would be consumed - like this:

-
- -
const frida = require('frida');
-const fs = require('fs');
-const path = require('path');
-const util = require('util');
-
-const readFile = util.promisify(fs.readFile);
-
-let session, script;
-async function run() {
-  const source = await readFile(path.join(__dirname, '_agent.js'), 'utf8');
-  session = await frida.attach('iTunes');
-  script = await session.createScript(source);
-  script.message.connect(onMessage);
-  await script.load();
-  console.log(await script.exports.add(2, 3));
-  console.log(await script.exports.sub(5, 3));
-}
-
-run().catch(onError);
-
-function onError(error) {
-  console.error(error.stack);
-}
-
-function onMessage(message, data) {
-  if (message.type === 'send') {
-    console.log(message.payload);
-  } else if (message.type === 'error') {
-    console.error(message.stack);
-  }
-}
- -
-

The Python version would be very similar:

-
- -
import codecs
-import frida
-
-def on_message(message, data):
-    if message['type'] == 'send':
-        print(message['payload'])
-    elif message['type'] == 'error':
-        print(message['stack'])
-
-session = frida.attach('iTunes')
-with codecs.open('./agent.js', 'r', 'utf-8') as f:
-    source = f.read()
-script = session.create_script(source)
-script.on('message', on_message)
-script.load()
-print(script.exports.add(2, 3))
-print(script.exports.sub(5, 3))
-session.detach()
- -

In the example above we used script.on('message', on_message) to monitor for -any messages from the injected process, JavaScript side. There are other -notifications that you can watch for as well on both the script and session. -If you want to be notified when the target process exits, use -session.on('detached', your_function).

- -

Timing events

- - - -

Garbage collection

+ - +

Table of contents

+ +
    +
  1. Runtime information +
      +
    1. Frida
    2. +
    3. Script
    4. +
    +
  2. +
  3. Process, Thread, Module and Memory +
      +
    1. Thread
    2. +
    3. Process
    4. +
    5. Module
    6. +
    7. ModuleMap
    8. +
    9. Memory
    10. +
    11. MemoryAccessMonitor
    12. +
    13. CModule
    14. +
    15. ApiResolver
    16. +
    17. DebugSymbol
    18. +
    19. Kernel
    20. +
    +
  4. +
  5. Data Types, Function and Callback +
      +
    1. Int64
    2. +
    3. UInt64
    4. +
    5. NativePointer
    6. +
    7. ArrayBuffer
    8. +
    9. NativeFunction
    10. +
    11. NativeCallback
    12. +
    13. SystemFunction
    14. +
    +
  6. +
  7. Network +
      +
    1. Socket
    2. +
    3. SocketListener
    4. +
    5. SocketConnection
    6. +
    +
  8. +
  9. File and Stream +
      +
    1. File
    2. +
    3. IOStream
    4. +
    5. InputStream
    6. +
    7. OutputStream
    8. +
    9. UnixInputStream
    10. +
    11. UnixOutputStream
    12. +
    13. Win32InputStream
    14. +
    15. Win32OutputStream
    16. +
    +
  10. +
  11. Database +
      +
    1. SqliteDatabase
    2. +
    3. SqliteStatement
    4. +
    +
  12. +
  13. Instrumentation +
      +
    1. Interceptor
    2. +
    3. Stalker
    4. +
    5. ObjC
    6. +
    7. Java
    8. +
    +
  14. +
  15. CPU Instruction +
      +
    1. Instruction
    2. +
    3. X86Writer
    4. +
    5. X86Relocator
    6. +
    7. x86 enum types
    8. +
    9. ArmWriter
    10. +
    11. ArmRelocator
    12. +
    13. ThumbWriter
    14. +
    15. ThumbRelocator
    16. +
    17. ARM enum types
    18. +
    19. Arm64Writer
    20. +
    21. Arm64Relocator
    22. +
    23. AArch64 enum types
    24. +
    25. MipsWriter
    26. +
    27. MipsRelocator
    28. +
    29. MIPS enum types
    30. +
    +
  16. +
  17. Other +
      +
    1. Console
    2. +
    3. Hexdump
    4. +
    5. Shorthand
    6. +
    7. Communication between host and injected process
    8. +
    9. Timing events
    10. +
    11. Garbage collection
    12. +
    +
  18. +
+ +
+ +

Runtime information

+ +

Frida

+ + + +

Script

+ + + +
+ +

Process, Thread, Module and Memory

+ +

Thread

+ + + +
const f = Module.getExportByName('libcommonCrypto.dylib',
+				'CCCryptorCreate');
+			Interceptor.attach(f, {
+			  onEnter(args) {
+				console.log('CCCryptorCreate called from:\n' +
+					Thread.backtrace(this.context, Backtracer.ACCURATE)
+					.map(DebugSymbol.fromAddress).join('\n') + '\n');
+			  }
+			});
+ + + +

Process

+ + + +

Module

+ +

Objects returned by e.g. Module.load() and Process.enumerateModules().

+ + + +
+
enumerateSymbols() is only available on i/macOS and Linux-based OSes
+

+ We would love to support this on the other platforms too, so if you find + this useful and would like to help out, please get in touch. You may also + find the DebugSymbol API adequate, depending on your use-case. +

+
+ + + +

ModuleMap

+ + + +

Memory

+ + + +
// Find the module for the program itself, always at index 0:
+			const m = Process.enumerateModules()[0];
+			
+			// Or load a module by name:
+			//const m = Module.load('win32u.dll');
+			
+			// Print its properties:
+			console.log(JSON.stringify(m));
+			
+			// Dump it from its base address:
+			console.log(hexdump(m.base));
+			
+			// The pattern that you are interested in:
+			const pattern = '00 00 00 00 ?? 13 37 ?? 42';
+			
+			Memory.scan(m.base, m.size, pattern, {
+			  onMatch(address, size) {
+				console.log('Memory.scan() found match at', address,
+					'with size', size);
+			
+				// Optionally stop scanning early:
+				return 'stop';
+			  },
+			  onComplete() {
+				console.log('Memory.scan() complete');
+			  }
+			});
+			
+			const results = Memory.scanSync(m.base, m.size, pattern);
+			console.log('Memory.scanSync() result:\n' +
+				JSON.stringify(results));
+ + + +
Memory.protect(ptr('0x1234'), 4096, 'rw-');
+ + + +
const getLivesLeft = Module.getExportByName('game-engine.so', 'get_lives_left');
+			const maxPatchSize = 64; // Do not write out of bounds, may be a temporary buffer!
+			Memory.patchCode(getLivesLeft, maxPatchSize, code => {
+			  const cw = new X86Writer(code, { pc: getLivesLeft });
+			  cw.putMovRegU32('eax', 9000);
+			  cw.putRet();
+			  cw.flush();
+			});
+ + + +

MemoryAccessMonitor

+ + + +

CModule

+ + + +

Examples

+ +
const cm = new CModule(`
+			#include <stdio.h>
+			
+			void hello(void) {
+			  printf("Hello World from CModule\\n");
+			}
+			`);
+			
+			console.log(JSON.stringify(cm));
+			
+			const hello = new NativeFunction(cm.hello, 'void', []);
+			hello();
+ +

Which you might load using Frida’s REPL:

+ +
$ frida -p 0 -l example.js
+ +

(The REPL monitors the file on disk and reloads the script on change.)

+ +

You can then type hello() in the REPL to call the C function.

+ +

For prototyping we recommend using the Frida REPL’s built-in CModule support:

+ +
$ frida -p 0 -C example.c
+ +

You may also add -l example.js to load some JavaScript next to it. + The JavaScript code may use the global variable named cm to access + the CModule object, but only after rpc.exports.init() has been + called, so perform any initialization depending on the CModule there. You may + also inject symbols by assigning to the global object named cs, but this + must be done before rpc.exports.init() gets called.

+ +

Here’s an example:

+ +

CModule REPL example

+ +

More details on CModule can be found in the Frida 12.7 release notes.

+ +

ApiResolver

+ + + +
const resolver = new ApiResolver('module');
+			const matches = resolver.enumerateMatches('exports:*!open*');
+			const first = matches[0];
+			/*
+			 * Where `first` is an object similar to:
+			 *
+			 * {
+			 *   name: '/usr/lib/libSystem.B.dylib!opendir$INODE64',
+			 *   address: ptr('0x7fff870135c9')
+			 * }
+			 */
+ +
const resolver = new ApiResolver('objc');
+			const matches = resolver.enumerateMatches('-[NSURL* *HTTP*]');
+			const first = matches[0];
+			/*
+			 * Where `first` contains an object like this one:
+			 *
+			 * {
+			 *   name: '-[NSURLRequest valueForHTTPHeaderField:]',
+			 *   address: ptr('0x7fff94183e22')
+			 * }
+			 */
+ +

DebugSymbol

+ + + +
const f = Module.getExportByName('libcommonCrypto.dylib',
+				'CCCryptorCreate');
+			Interceptor.attach(f, {
+			  onEnter(args) {
+				console.log('CCCryptorCreate called from:\n' +
+					Thread.backtrace(this.context, Backtracer.ACCURATE)
+					.map(DebugSymbol.fromAddress).join('\n') + '\n');
+			  }
+			});
+ + + +

Kernel

+ + + +
Kernel.protect(UInt64('0x1234'), 4096, 'rw-');
+ + + +
+ +

Data Types, Function and Callback

+ +

Int64

+ + + +

UInt64

+ + + +

NativePointer

+ + + +

ArrayBuffer

+ + + +

NativeFunction

+ + + +

NativeCallback

+ + + +

SystemFunction

+ + + +
+ +

Network

+ +

Socket

+ + + +

SocketListener

+ +

All methods are fully asynchronous and return Promise objects.

+ + + +

SocketConnection

+ +

Inherits from IOStream. + All methods are fully asynchronous and return Promise objects.

+ + + +
+ +

File and Stream

+ +

File

+ + + +

IOStream

+ +

All methods are fully asynchronous and return Promise objects.

+ + + +

InputStream

+ +

All methods are fully asynchronous and return Promise objects.

+ + + +

OutputStream

+ +

All methods are fully asynchronous and return Promise objects.

+ + + +

UnixInputStream

+ +

(Only available on UNIX-like OSes.)

+ + + +

UnixOutputStream

+ +

(Only available on UNIX-like OSes.)

+ + + +

Win32InputStream

+ +

(Only available on Windows.)

+ + + +

Win32OutputStream

+ +

(Only available on Windows.)

+ + + +

Database

+ +

SqliteDatabase

+ + + +
const db = SqliteDatabase.open('/path/to/people.db');
+			
+			const smt = db.prepare('SELECT name, bio FROM people WHERE age = ?');
+			
+			console.log('People whose age is 42:');
+			smt.bindInteger(1, 42);
+			let row;
+			while ((row = smt.step()) !== null) {
+			  const [name, bio] = row;
+			  console.log('Name:', name);
+			  console.log('Bio:', bio);
+			}
+			smt.reset();
+ + + +

SqliteStatement

+ + + +
+ +

Instrumentation

+ +

Interceptor

+ + + +
Interceptor.attach(Module.getExportByName('libc.so', 'read'), {
+			  onEnter(args) {
+				this.fileDescriptor = args[0].toInt32();
+			  },
+			  onLeave(retval) {
+				if (retval.toInt32() > 0) {
+				  /* do something with this.fileDescriptor */
+				}
+			  }
+			});
+ + + +
Interceptor.attach(Module.getExportByName(null, 'read'), {
+			  onEnter(args) {
+				console.log('Context information:');
+				console.log('Context  : ' + JSON.stringify(this.context));
+				console.log('Return   : ' + this.returnAddress);
+				console.log('ThreadId : ' + this.threadId);
+				console.log('Depth    : ' + this.depth);
+				console.log('Errornr  : ' + this.err);
+			
+				// Save arguments for processing in onLeave.
+				this.fd = args[0].toInt32();
+				this.buf = args[1];
+				this.count = args[2].toInt32();
+			  },
+			  onLeave(result) {
+				console.log('----------')
+				// Show argument 1 (buf), saved during onEnter.
+				const numBytes = result.toInt32();
+				if (numBytes > 0) {
+				  console.log(hexdump(this.buf, { length: numBytes, ansi: true }));
+				}
+				console.log('Result   : ' + numBytes);
+			  }
+			})
+ +
+
Performance considerations
+

+ The callbacks provided have a significant impact on performance. If you only + need to inspect arguments but do not care about the return value, or the + other way around, make sure you omit the callback that you don't need; i.e. + avoid putting your logic in onEnter and leaving onLeave in + there as an empty callback. +

+

+ On an iPhone 5S the base overhead when providing just onEnter might be + something like 6 microseconds, and 11 microseconds with both onEnter + and onLeave provided. +

+

+ Also be careful about intercepting calls to functions that are called a + bazillion times per second; while send() is + asynchronous, the total overhead of sending a single message is not optimized for + high frequencies, so that means Frida leaves it up to you to batch multiple values + into a single send()-call, based on whether low delay + or high throughput is desired. +

+

+ However when hooking hot functions you may use Interceptor in conjunction + with CModule to implement the callbacks in C. +

+
+ + + +
const openPtr = Module.getExportByName('libc.so', 'open');
+			const open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);
+			Interceptor.replace(openPtr, new NativeCallback((pathPtr, flags) => {
+			  const path = pathPtr.readUtf8String();
+			  log('Opening "' + path + '"');
+			  const fd = open(pathPtr, flags);
+			  log('Got fd: ' + fd);
+			  return fd;
+			}, 'int', ['pointer', 'int']));
+ + + +

Stalker

+ + + +
const mainThread = Process.enumerateThreads()[0];
+			
+			Stalker.follow(mainThread.id, {
+			  events: {
+				call: true, // CALL instructions: yes please
+			
+				// Other events:
+				ret: false, // RET instructions
+				exec: false, // all instructions: not recommended as it's
+							 //                   a lot of data
+				block: false, // block executed: coarse execution trace
+				compile: false // block compiled: useful for coverage
+			  },
+			
+			  //
+			  // Only specify one of the two following callbacks.
+			  // (See note below.)
+			  //
+			
+			  //
+			  // onReceive: Called with `events` containing a binary blob
+			  //            comprised of one or more GumEvent structs.
+			  //            See `gumevent.h` for details about the
+			  //            format. Use `Stalker.parse()` to examine the
+			  //            data.
+			  //
+			  //onReceive(events) {
+			  //},
+			  //
+			
+			  //
+			  // onCallSummary: Called with `summary` being a key-value
+			  //                mapping of call target to number of
+			  //                calls, in the current time window. You
+			  //                would typically implement this instead of
+			  //                `onReceive()` for efficiency, i.e. when
+			  //                you only want to know which targets were
+			  //                called and how many times, but don't care
+			  //                about the order that the calls happened
+			  //                in.
+			  //
+			  onCallSummary(summary) {
+			  },
+			
+			  //
+			  // Advanced users: This is how you can plug in your own
+			  //                 StalkerTransformer, where the provided
+			  //                 function is called synchronously
+			  //                 whenever Stalker wants to recompile
+			  //                 a basic block of the code that's about
+			  //                 to be executed by the stalked thread.
+			  //
+			  //transform(iterator) {
+			  //  let instruction = iterator.next();
+			  //
+			  //  const startAddress = instruction.address;
+			  //  const isAppCode = startAddress.compare(appStart) >= 0 &&
+			  //      startAddress.compare(appEnd) === -1;
+			  //
+			  //  do {
+			  //    if (isAppCode && instruction.mnemonic === 'ret') {
+			  //      iterator.putCmpRegI32('eax', 60);
+			  //      iterator.putJccShortLabel('jb', 'nope', 'no-hint');
+			  //
+			  //      iterator.putCmpRegI32('eax', 90);
+			  //      iterator.putJccShortLabel('ja', 'nope', 'no-hint');
+			  //
+			  //      iterator.putCallout(onMatch);
+			  //
+			  //      iterator.putLabel('nope');
+			  //    }
+			  //
+			  //    iterator.keep();
+			  //  } while ((instruction = iterator.next()) !== null);
+			  //},
+			  //
+			  // The default implementation is just:
+			  //
+			  //   while (iterator.next() !== null)
+			  //     iterator.keep();
+			  //
+			  // The example above shows how you can insert your own code
+			  // just before every `ret` instruction across any code
+			  // executed by the stalked thread inside the app's own
+			  // memory range. It inserts code that checks if the `eax`
+			  // register contains a value between 60 and 90, and inserts
+			  // a synchronous callout back into JavaScript whenever that
+			  // is the case. The callback receives a single argument
+			  // that gives it access to the CPU registers, and it is
+			  // also able to modify them.
+			  //
+			  // function onMatch (context) {
+			  //   console.log('Match! pc=' + context.pc +
+			  //       ' rax=' + context.rax.toInt32());
+			  // }
+			  //
+			  // Note that not calling keep() will result in the
+			  // instruction getting dropped, which makes it possible
+			  // for your transform to fully replace certain instructions
+			  // when this is desirable.
+			  //
+			
+			  //
+			  // Want better performance? Write the callbacks in C:
+			  //
+			  // /*
+			  //  * const cm = new CModule(\`
+			  //  *
+			  //  * #include <gum/gumstalker.h>
+			  //  *
+			  //  * static void on_ret (GumCpuContext * cpu_context,
+			  //  *     gpointer user_data);
+			  //  *
+			  //  * void
+			  //  * transform (GumStalkerIterator * iterator,
+			  //  *            GumStalkerOutput * output,
+			  //  *            gpointer user_data)
+			  //  * {
+			  //  *   cs_insn * insn;
+			  //  *
+			  //  *   while (gum_stalker_iterator_next (iterator, &insn))
+			  //  *   {
+			  //  *     if (insn->id == X86_INS_RET)
+			  //  *     {
+			  //  *       gum_x86_writer_put_nop (output->writer.x86);
+			  //  *       gum_stalker_iterator_put_callout (iterator,
+			  //  *           on_ret, NULL, NULL);
+			  //  *     }
+			  //  *
+			  //  *     gum_stalker_iterator_keep (iterator);
+			  //  *   }
+			  //  * }
+			  //  *
+			  //  * static void
+			  //  * on_ret (GumCpuContext * cpu_context,
+			  //  *         gpointer user_data)
+			  //  * {
+			  //  *   printf ("on_ret!\n");
+			  //  * }
+			  //  *
+			  //  * void
+			  //  * process (const GumEvent * event,
+			  //  *          GumCpuContext * cpu_context,
+			  //  *          gpointer user_data)
+			  //  * {
+			  //  *   switch (event->type)
+			  //  *   {
+			  //  *     case GUM_CALL:
+			  //  *       break;
+			  //  *     case GUM_RET:
+			  //  *       break;
+			  //  *     case GUM_EXEC:
+			  //  *       break;
+			  //  *     case GUM_BLOCK:
+			  //  *       break;
+			  //  *     case GUM_COMPILE:
+			  //  *       break;
+			  //  *     default:
+			  //  *       break;
+			  //  *   }
+			  //  * }
+			  //  * `);
+			  //  */
+			  //
+			  //transform: cm.transform,
+			  //onEvent: cm.process,
+			  //data: ptr(1337) /* user_data */
+			  //
+			  // You may also use a hybrid approach and only write
+			  // some of the callouts in C.
+			  //
+			});
+ +
+
Performance considerations
+

+ The callbacks provided have a significant impact on performance. If you only + need periodic call summaries but do not care about the raw events, or the + other way around, make sure you omit the callback that you don't need; i.e. + avoid putting your logic in onCallSummary and leaving + onReceive in there as an empty callback. +

+

+ Also note that Stalker may be used in conjunction with CModule, + which means the callbacks may be implemented in C. +

+
+ + + +
  onReceive(events) {
+				console.log(Stalker.parse(events, {
+				  annotate: true, // to display the type of event
+				  stringify: true
+					// to format pointer values as strings instead of `NativePointer`
+					// values, i.e. less overhead if you're just going to `send()` the
+					// thing not actually parse the data agent-side
+				}));
+			  },
+ + + +

ObjC

+ + + +
const { NSSound } = ObjC.classes; /* macOS */
+			ObjC.schedule(ObjC.mainQueue, () => {
+				const sound = NSSound.alloc().initWithContentsOfFile_byReference_("/Users/oleavr/.Trash/test.mp3", true);
+				sound.play();
+			});
+ + + +
Interceptor.attach(myFunction.implementation, {
+			  onEnter(args) {
+				// ObjC: args[0] = self, args[1] = selector, args[2-n] = arguments
+				const myString = new ObjC.Object(args[2]);
+				console.log("String argument: " + myString.toString());
+			  }
+			});
+ +
+

This object has some special properties:

+ +
    +
  • $kind: string specifying either instance, class or meta-class
  • +
  • $super: an ObjC.Object instance used for chaining up to + super-class method implementations
  • +
  • $superClass: super-class as an ObjC.Object instance
  • +
  • $class: class of this object as an ObjC.Object instance
  • +
  • $className: string containing the class name of this object
  • +
  • $moduleName: string containing the module path of this object
  • +
  • $protocols: object mapping protocol name to ObjC.Protocol + instance for each of the protocols that this object conforms to
  • +
  • $methods: array containing native method names exposed by this object’s + class and parent classes
  • +
  • $ownMethods: array containing native method names exposed by this object’s + class, not including parent classes
  • +
  • $ivars: object mapping each instance variable name to its current + value, allowing you to read and write each through access and assignment
  • +
+ +

There is also an equals(other) method for checking whether two instances + refer to the same underlying object.

+ +

Note that all method wrappers provide a clone(options) API to create a new + method wrapper with custom NativeFunction options.

+
+ + + +
const pendingBlocks = new Set();
+			
+			Interceptor.attach(..., {
+			  onEnter(args) {
+				const block = new ObjC.Block(args[4]);
+				pendingBlocks.add(block); // Keep it alive
+				const appCallback = block.implementation;
+				block.implementation = (error, value) => {
+				  // Do your logging here
+				  const result = appCallback(error, value);
+				  pendingBlocks.delete(block);
+				  return result;
+				};
+			  }
+			});
+ + + +
const NSSound = ObjC.classes.NSSound; /* macOS */
+			const oldImpl = NSSound.play.implementation;
+			NSSound.play.implementation = ObjC.implement(NSSound.play, (handle, selector) => {
+			  return oldImpl(handle, selector);
+			});
+			
+			const NSView = ObjC.classes.NSView; /* macOS */
+			const drawRect = NSView['- drawRect:'];
+			const oldImpl = drawRect.implementation;
+			drawRect.implementation = ObjC.implement(drawRect, (handle, selector) => {
+			  oldImpl(handle, selector);
+			});
+ +
+

As the implementation property is a NativeFunction and thus also a + NativePointer, you may also use Interceptor to hook functions:

+
+ +
const { NSSound } = ObjC.classes; /* macOS */
+			Interceptor.attach(NSSound.play.implementation, {
+			  onEnter() {
+				send("[NSSound play]");
+			  }
+			});
+ + + +
const MyConnectionDelegateProxy = ObjC.registerProxy({
+			  protocols: [ObjC.protocols.NSURLConnectionDataDelegate],
+			  methods: {
+				'- connection:didReceiveResponse:': function (conn, resp) {
+				  /* fancy logging code here */
+				  /* this.data.foo === 1234 */
+				  this.data.target
+					  .connection_didReceiveResponse_(conn, resp);
+				},
+				'- connection:didReceiveData:': function (conn, data) {
+				  /* other logging code here */
+				  this.data.target
+					  .connection_didReceiveData_(conn, data);
+				}
+			  },
+			  events: {
+				forward(name) {
+				  console.log('*** forwarding: ' + name);
+				}
+			  }
+			});
+			
+			const method = ObjC.classes.NSURLConnection[
+				'- initWithRequest:delegate:startImmediately:'];
+			Interceptor.attach(method.implementation, {
+			  onEnter(args) {
+				args[3] = new MyConnectionDelegateProxy(args[3], {
+				  foo: 1234
+				});
+			  }
+			});
+ + + +
const MyConnectionDelegateProxy = ObjC.registerClass({
+			  name: 'MyConnectionDelegateProxy',
+			  super: ObjC.classes.NSObject,
+			  protocols: [ObjC.protocols.NSURLConnectionDataDelegate],
+			  methods: {
+				'- init': function () {
+				  const self = this.super.init();
+				  if (self !== null) {
+					ObjC.bind(self, {
+					  foo: 1234
+					});
+				  }
+				  return self;
+				},
+				'- dealloc': function () {
+				  ObjC.unbind(this.self);
+				  this.super.dealloc();
+				},
+				'- connection:didReceiveResponse:': function (conn, resp) {
+				  /* this.data.foo === 1234 */
+				},
+				/*
+				 * But those previous methods are declared assuming that
+				 * either the super-class or a protocol we conform to has
+				 * the same method so we can grab its type information.
+				 * However, if that's not the case, you would write it
+				 * like this:
+				 */
+				'- connection:didReceiveResponse:': {
+				  retType: 'void',
+				  argTypes: ['object', 'object'],
+				  implementation(conn, resp) {
+				  }
+				},
+				/* Or grab it from an existing class: */
+				'- connection:didReceiveResponse:': {
+				  types: ObjC.classes
+					  .Foo['- connection:didReceiveResponse:'].types,
+				  implementation(conn, resp) {
+				  }
+				},
+				/* Or from an existing protocol: */
+				'- connection:didReceiveResponse:': {
+				  types: ObjC.protocols.NSURLConnectionDataDelegate
+					  .methods['- connection:didReceiveResponse:'].types,
+				  implementation(conn, resp) {
+				  }
+				},
+				/* Or write the signature by hand if you really want to: */
+				'- connection:didReceiveResponse:': {
+				  types: '[email protected]:[email protected]@24',
+				  implementation(conn, resp) {
+				  }
+				}
+			  }
+			});
+			
+			const proxy = MyConnectionDelegateProxy.alloc().init();
+			/* use `proxy`, and later: */
+			proxy.release();
+ + + +
const MyDataDelegate = ObjC.registerProtocol({
+			  name: 'MyDataDelegate',
+			  protocols: [ObjC.protocols.NSURLConnectionDataDelegate],
+			  methods: {
+				/* You must specify the signature: */
+				'- connection:didStuff:': {
+				  retType: 'void',
+				  argTypes: ['object', 'object']
+				},
+				/* Or grab it from a method of an existing class: */
+				'- connection:didStuff:': {
+				  types: ObjC.classes
+					  .Foo['- connection:didReceiveResponse:'].types
+				},
+				/* Or from an existing protocol method: */
+				'- connection:didStuff:': {
+				  types: ObjC.protocols.NSURLConnectionDataDelegate
+					  .methods['- connection:didReceiveResponse:'].types
+				},
+				/* Or write the signature by hand if you really want to: */
+				'- connection:didStuff:': {
+				  types: '[email protected]:[email protected]@24'
+				},
+				/* You can also make a method optional (default is required): */
+				'- connection:didStuff:': {
+				  retType: 'void',
+				  argTypes: ['object', 'object'],
+				  optional: true
+				}
+			  }
+			});
+ + + +
ObjC.enumerateLoadedClasses({
+			  onMatch(name, owner) {
+				console.log('onMatch:', name, owner);
+			  },
+			  onComplete() {
+			  }
+			});
+ +

The optional options argument is an object where you may specify the + ownedBy property to limit enumeration to modules in a given ModuleMap.

+ +

For example:

+ +
const appModules = new ModuleMap(isAppModule);
+			ObjC.enumerateLoadedClasses({ ownedBy: appModules }, {
+			  onMatch(name, owner) {
+				console.log('onMatch:', name, owner);
+			  },
+			  onComplete() {
+			  }
+			});
+			
+			function isAppModule(m) {
+			  return !/^\/(usr\/lib|System|Developer)\//.test(m.path);
+			}
+ + + +
const appModules = new ModuleMap(isAppModule);
+			const appClasses = ObjC.enumerateLoadedClassesSync({ ownedBy: appModules });
+			console.log('appClasses:', JSON.stringify(appClasses));
+			
+			function isAppModule(m) {
+			  return !/^\/(usr\/lib|System|Developer)\//.test(m.path);
+			}
+ + + +

Java

+ + + +
Java.perform(() => {
+			  const groups = Java.enumerateMethods('*youtube*!on*')
+			  console.log(JSON.stringify(groups, null, 2));
+			});
+ +
[
+			  {
+				"loader": "<instance: java.lang.ClassLoader, $className: dalvik.system.PathClassLoader>",
+				"classes": [
+				  {
+					"name": "com.google.android.apps.youtube.app.watch.nextgenwatch.ui.NextGenWatchLayout",
+					"methods": [
+					  "onAttachedToWindow",
+					  "onDetachedFromWindow",
+					  "onFinishInflate",
+					  "onInterceptTouchEvent",
+					  "onLayout",
+					  "onMeasure",
+					  "onSizeChanged",
+					  "onTouchEvent",
+					  "onViewRemoved"
+					]
+				  },
+				  {
+					"name": "com.google.android.apps.youtube.app.search.suggest.YouTubeSuggestionProvider",
+					"methods": [
+					  "onCreate"
+					]
+				  },
+				  {
+					"name": "com.google.android.libraries.youtube.common.ui.YouTubeButton",
+					"methods": [
+					  "onInitializeAccessibilityNodeInfo"
+					]
+				  },
+				  
+				]
+			  }
+			]
+ + + +
Java.perform(() => {
+			  const Activity = Java.use('android.app.Activity');
+			  Activity.onResume.implementation = function () {
+				send('onResume() got called! Let\'s call the original implementation');
+				this.onResume();
+			  };
+			});
+ + + +
Java.perform(() => {
+			  const Activity = Java.use('android.app.Activity');
+			  const Exception = Java.use('java.lang.Exception');
+			  Activity.onResume.implementation = function () {
+				throw Exception.$new('Oh noes!');
+			  };
+			});
+ +
+

Uses the app’s class loader by default, but you may customize this by + assigning a different loader instance to Java.classFactory.loader.

+ +

Note that all method wrappers provide a clone(options) API to create a new + method wrapper with custom NativeFunction options.

+
+ + + +
Java.perform(() => {
+			  const Activity = Java.use('android.app.Activity');
+			  let lastActivity = null;
+			  Activity.onResume.implementation = function () {
+				lastActivity = Java.retain(this);
+				this.onResume();
+			  };
+			});
+ + + +
const Activity = Java.use('android.app.Activity');
+			const activity = Java.cast(ptr('0x1234'), Activity);
+ + + +
const values = Java.array('int', [ 1003, 1005, 1007 ]);
+			
+			const JString = Java.use('java.lang.String');
+			const str = JString.$new(Java.array('byte', [ 0x48, 0x65, 0x69 ]));
+ + + +
const SomeBaseClass = Java.use('com.example.SomeBaseClass');
+			const X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
+			
+			const MyTrustManager = Java.registerClass({
+			  name: 'com.example.MyTrustManager',
+			  implements: [X509TrustManager],
+			  methods: {
+				checkClientTrusted(chain, authType) {
+				},
+				checkServerTrusted(chain, authType) {
+				},
+				getAcceptedIssuers() {
+				  return [];
+				},
+			  }
+			});
+			
+			const MyWeirdTrustManager = Java.registerClass({
+			  name: 'com.example.MyWeirdTrustManager',
+			  superClass: SomeBaseClass,
+			  implements: [X509TrustManager],
+			  fields: {
+				description: 'java.lang.String',
+				limit: 'int',
+			  },
+			  methods: {
+				$init() {
+				  console.log('Constructor called');
+				},
+				checkClientTrusted(chain, authType) {
+				  console.log('checkClientTrusted');
+				},
+				checkServerTrusted: [{
+				  returnType: 'void',
+				  argumentTypes: ['[Ljava.security.cert.X509Certificate;', 'java.lang.String'],
+				  implementation(chain, authType) {
+					console.log('checkServerTrusted A');
+				  }
+				}, {
+				  returnType: 'java.util.List',
+				  argumentTypes: ['[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'java.lang.String'],
+				  implementation(chain, authType, host) {
+					console.log('checkServerTrusted B');
+					return null;
+				  }
+				}],
+				getAcceptedIssuers() {
+				  console.log('getAcceptedIssuers');
+				  return [];
+				},
+			  }
+			});
+ + + +
+ +

CPU Instruction

+ +

Instruction

+ + + +

X86Writer

+ + + +

X86Relocator

+ + + +

x86 enum types

+ + + +

ArmWriter

+ + + +

ArmRelocator

+ + + +

ThumbWriter

+ + + +

ThumbRelocator

+ + + +

ARM enum types

+ + + +

Arm64Writer

+ + + +

Arm64Relocator

+ + + +

AArch64 enum types

+ + + +

MipsWriter

+ + + +

MipsRelocator

+ + + +

MIPS enum types

+ + + +
+ +

Others

+ +

Console

+ + + +

Hexdump

+ + + +
const libc = Module.findBaseAddress('libc.so');
+			console.log(hexdump(libc, {
+			  offset: 0,
+			  length: 64,
+			  header: true,
+			  ansi: true
+			}));
+ +
           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
+			00000000  7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00  .ELF............
+			00000010  03 00 28 00 01 00 00 00 00 00 00 00 34 00 00 00  ..(.........4...
+			00000020  34 a8 04 00 00 00 00 05 34 00 20 00 08 00 28 00  4.......4. ...(.
+			00000030  1e 00 1d 00 06 00 00 00 34 00 00 00 34 00 00 00  ........4...4...
+ +

Shorthand

+ + + +

Communication between host and injected process

+ + + +
+

For example:

+
+ +
rpc.exports = {
+			  add(a, b) {
+				return a + b;
+			  },
+			  sub(a, b) {
+				return new Promise(resolve => {
+				  setTimeout(() => {
+					resolve(a - b);
+				  }, 100);
+				});
+			  }
+			};
+ +
+

From an application using the Node.js bindings this API would be consumed + like this:

+
+ +
const frida = require('frida');
+			const fs = require('fs');
+			const path = require('path');
+			const util = require('util');
+			
+			const readFile = util.promisify(fs.readFile);
+			
+			let session, script;
+			async function run() {
+			  const source = await readFile(path.join(__dirname, '_agent.js'), 'utf8');
+			  session = await frida.attach('iTunes');
+			  script = await session.createScript(source);
+			  script.message.connect(onMessage);
+			  await script.load();
+			  console.log(await script.exports.add(2, 3));
+			  console.log(await script.exports.sub(5, 3));
+			}
+			
+			run().catch(onError);
+			
+			function onError(error) {
+			  console.error(error.stack);
+			}
+			
+			function onMessage(message, data) {
+			  if (message.type === 'send') {
+				console.log(message.payload);
+			  } else if (message.type === 'error') {
+				console.error(message.stack);
+			  }
+			}
+ +
+

The Python version would be very similar:

+
+ +
import codecs
+			import frida
+			
+			def on_message(message, data):
+				if message['type'] == 'send':
+					print(message['payload'])
+				elif message['type'] == 'error':
+					print(message['stack'])
+			
+			session = frida.attach('iTunes')
+			with codecs.open('./agent.js', 'r', 'utf-8') as f:
+				source = f.read()
+			script = session.create_script(source)
+			script.on('message', on_message)
+			script.load()
+			print(script.exports.add(2, 3))
+			print(script.exports.sub(5, 3))
+			session.detach()
+ +

In the example above we used script.on('message', on_message) to monitor for + any messages from the injected process, JavaScript side. There are other + notifications that you can watch for as well on both the script and session. + If you want to be notified when the target process exits, use + session.on('detached', your_function).

+ +

Timing events

+ + + +

Garbage collection

+ + +
diff --git a/Fermion/pages/index.html b/Fermion/pages/index.html index 5e06dcd..f3e3d52 100644 --- a/Fermion/pages/index.html +++ b/Fermion/pages/index.html @@ -1,6 +1,11 @@ + @@ -175,7 +180,7 @@

Frida Tools

- + diff --git a/Fermion/pages/instrument.html b/Fermion/pages/instrument.html index 5f58939..b69c79f 100644 --- a/Fermion/pages/instrument.html +++ b/Fermion/pages/instrument.html @@ -3,6 +3,7 @@ diff --git a/Fermion/pages/proc.html b/Fermion/pages/proc.html index 8886f04..94a6be8 100644 --- a/Fermion/pages/proc.html +++ b/Fermion/pages/proc.html @@ -7,10 +7,6 @@ -webkit-app-region:drag !important; } - ::-webkit-scrollbar { - width: 0px !important; - } - th { background: #767676 !important; color: white !important; @@ -29,11 +25,11 @@ -webkit-app-region:no-drag; position:fixed; top:40px; - margin-top: 8px; - padding:4px; - overflow-x: hidden; - overflow-x: auto; - text-align:justify; + margin-top: 8px; + padding:4px; + overflow-x: hidden; + overflow-x: auto; + text-align:justify; overflow-y:scroll; height: 94vh; width: 100%; @@ -42,6 +38,135 @@ @@ -67,15 +192,15 @@
- +
- - - - - - + + + + + + @@ -93,12 +218,13 @@
+
IconUserPIDPPIDProcessAttachIconUserPIDPPIDProcessAttach