Notes are mostly based on the frida-boot labs.

Quick Start

Frida project quick start:

git clone https://github.com/trib0r3/frida-scripts

Default stuff

  • remote port: 27042

Code snippets:

Useful API calls

Debugging

// print local environment (variables)
console.log(JSON.stringify(this.context, null, 4))

Enumerate binary

// list loaded libraries
Process.enumerateModulesSync();

// list functions of the library
Process.getModuleByName("libc-2.30.so").enumerateExports();

Resolving libs/funcs location

// get reference to library
Process.getModuleByName("libc-2.30.so");

// Retrieve from symbols
Module.getExportByName(/* libname or */ null, "sleep");
DebugSymbol.getFunctionByName("sleep");

// Calculate based on offset
Module.getBaseAddress("libc-2.30.so").add("0xcad90");
"0x7fc2edc42d90"

Script Communication

  1. via recv and send
  2. via RPC
    • you can provide arguments and receive the return values
    • whole logic can be held in python, only binary operations are in JS

RPC

  • TypeScript
    1. Get reference to the function
    2. Create it prototype
    3. Make them python-accessible by adding them to the rpc.exports dictionary.
  • Python
    1. Reference functions with script.exports.<func_name>()
var testPinPtr = DebugSymbol.getFunctionByName("test_pin");
var testPin = new NativeFunction(testPinPtr, "int", ["pointer"]);

rpc.exports = { // defince accessible funcs from python
    testPin: function(p) {
        var pin = Memory.allocUtf8String(p);
        return testPin(pin);
    }
}
import frida
import sys

with open("index.js", "r") as f:
    agent = f.read()

session = frida.attach("crypt")
script = session.create_script(agent)
script.load()

api = script.exports # ref to funcs from js

for x in range(0, 9999):
    res = api.test_pin(str(x))
    if res == 0:
        continue

    print(f"Pin: {x}")
    break

Reuse the binary code

// get addr
var testPinPtr = DebugSymbol.getFunctionByName("test_pin");

// create JS wrapper
var testPin = new NativeFunction(testPinPtr, "int", ["pointer"]);


for (var i = 0; i < 9999; i++) {
    console.log("Trying: " + i.toString());
    var pin = Memory.allocUtf8String(i.toString());
    var r = testPin(pin);

    if (r == 1) {
        console.log("Pin is: " + i.toString());
        break;
    }
}

Function Hooking

By symbol

from __future__ import print_function
import frida
import sys

session = frida.attach("hello")
script = session.create_script("""
Interceptor.attach(ptr("%s"), {
    onEnter: function(args) {
        send(args[0].toInt32());
    }
});
""" % int(sys.argv[1], 16))
def on_message(message, data):
    print(message)
script.on(message, on_message)
script.load()
sys.stdin.read()

By symbol

var rand_range = DebugSymbol.getFunctionByName("rand_range");

Interceptor.attach(rand_range, {
    onEnter: function(args) {
        // Replace with string
        args[0] = Memory.allocUtf8String("Frida sleep! :D\n");
        // Replace with int
        args[0] = new NativePointer("0x1") // or ptr("0x1")
        
        // Update the value
        this.arg1 = args[0];
    },
    onLeave: function(retval) {
        console.log(retval);
        retval.replace(this.arg1);
    }
});

Remote execution (debugging)

Proptip: Just add -H or -R if you want to use remote mode with default settings or you want to provide remote connection details

Tools

frida CLI

Open binary with frida <exec> command and play with it in interactive shell session.

frida-server (remote)

# on victim
frida-server -l 0.0.0.0:1337

# on local client
frida-ps -H $IP:$PORT -R
frida -H $IP:$PORT $proc_name

frida-gadget (remote)

Loaded apps are paused (can be changed), common ways of injecting gadget:

  • load with LD_PRELOAD
  • load with patchelf
# on victim
LD_PRELOAD=./frida-gadget.so ./crypt

# on analysis machine
frida-ps -R # should yield gadget process
frida -R Gadget
# on victim
$ patchelf --add-needed ../frida-gadget.so crypt

# on analysis machine
$ ./crypt
[Frida INFO] Listening on 127.0.0.1 TCP port 27042

Configure with frida-gadget.config (default):

{
  "interaction": {
    "type": "listen",
    "address": "127.0.0.1",
    "port": 27042,
    "on_load": "resume"
  }
}

Scripting:

{
  "interaction": {
    "type": "script",
    "path": "/root/code/embedded-agent.js"
  }
}
var testPinPtr = DebugSymbol.getFunctionByName("test_pin");
var testPin = new NativeFunction(testPinPtr, "int", ["pointer"]);

for (var i = 0; i < 9999; i++) {
    console.log("Trying: " + i.toString());
    var pin = Memory.allocUtf8String(i.toString());
    var r = testPin(pin);

    if (r == 1) {
        console.log("Pin is: " + i.toString());
        break;
    }
}

frida-trace

  1. Select function to trace
  2. Edit desired function behaviour
    1. Open __hooks__ directory
    2. edit scripts there
  3. Run & watch new injected behaviour
  onEnter: function (log, args, state) {
    args[1] = ptr('1337');
    log('printf(' +
      'format="' + args[0].readUtf8String() + '"' +
    ')');
  },

Examples

Spawn web server

import { log } from "./logger";
import * as http from "http";

const testPinPtr = DebugSymbol.getFunctionByName("test_pin");
const testPin = new NativeFunction(testPinPtr, "int", ["pointer"]);

rpc.exports = {
    testPin: function(p: string) {
        const pin = Memory.allocUtf8String(p);
        return testPin(pin);
    },
    httpServer: function() {
        http.createServer((req, res) => {
            const pin = req.url? req.url.replace('/', '') : '';
            const check = this.testPin(pin);

            log(`Request to check ${req.url} returned ${check}`);

            if (check == 1) {
                res.writeHead(200, {'Content-Type': 'text/plain'});
                res.write(`Welcome!\n`);
            } else {
                res.writeHead(401, {'Content-Type': 'text/plain'});
                res.write(`Wrong PIN\n`);
            }

            res.end();
        }).listen(1337);
    }
}
import frida
import sys

with open("frida-agent-example/_agent.js", "r") as f:
    agent = f.read()

session = frida.attach("crypt")
script = session.create_script(agent)
script.load()

api = script.exports
print("starting HTTP server...")
api.http_server()

# keep the server alive now
sys.stdin.read()
npm run build # or watch

References