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
- via
recv
andsend
- 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
- Get reference to the function
- Create it prototype
- Make them python-accessible by adding them to the
rpc.exports
dictionary.
- Python
- Reference functions with
script.exports.<func_name>()
- Reference functions with
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
- Select function to trace
- Edit desired function behaviour
- Open
__hooks__
directory - edit scripts there
- Open
- 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