Ole André Vadla Ravnås PRO
Creator of Frida. Security Researcher at NowSecure. Polyglot hacker passionate about reverse-engineering and dynamic instrumentation.
Debuggee
Debugger
Debuggee
Debugger
bootstrapper
Debuggee
Debugger
bootstrapper
bootstrapper-thread
Debuggee
Debugger
bootstrapper
bootstrapper-thread
frida-agent.so
Debuggee
Debugger
bootstrapper
bootstrapper-thread
frida-agent.so
Comm. Channel
Debuggee
Debugger
bootstrapper
bootstrapper-thread
frida-agent.so
Comm. Channel
JavaScript
▼
1) Build and run a simple program that calls f(n) every second with n increasing with each call.
2) Let's figure out what n is.
Frida has a REPL. Let's use it.
It live-reloads!
3) Let's modify what n is. How about +9000?
4) Let's speed up time.
5) Let's call f() ourselves.
6) rpc, send() and recv().
'use strict';
Java.perform(function () {
var MainActivity = Java.use(
're.frida.helloworld.MainActivity');
MainActivity.isRegistered.implementation = function () {
console.log('isRegistered() w00t');
return true;
};
});
$ node app.js Spotify
connect() family=2 ip=78.31.9.101 port=80
blocking!
connect() family=2 ip=193.182.7.242 port=80
blocking!
connect() family=2 ip=194.132.162.4 port=443
blocking!
connect() family=2 ip=194.132.162.4 port=80
blocking!
connect() family=2 ip=194.132.162.212 port=80
blocking!
connect() family=2 ip=194.132.162.196 port=4070
blocking!
connect() family=2 ip=193.182.7.226 port=443
blocking!
'use strict';
const AF_INET = 2;
const AF_INET6 = 30;
const ECONNREFUSED = 61;
['connect', 'connect$NOCANCEL'].forEach(funcName => {
const connect = new NativeFunction(
Module.findExportByName('libsystem_kernel.dylib', funcName),
'int',
['int', 'pointer', 'int']);
Interceptor.replace(connect, new NativeCallback((socket, address, addressLen) => {
const family = Memory.readU8(address.add(1));
if (family == AF_INET || family == AF_INET6) {
const port = (Memory.readU8(address.add(2)) << 8) | Memory.readU8(address.add(3));
let ip = '';
if (family == AF_INET) {
for (let offset = 4; offset != 8; offset++) {
if (ip.length > 0)
ip += '.';
ip += Memory.readU8(address.add(offset));
}
} else {
for (let offset = 8; offset !== 24; offset += 2) {
if (ip.length > 0)
ip += ':';
ip += toHex(Memory.readU8(address.add(offset))) +
toHex(Memory.readU8(address.add(offset + 1)));
}
}
console.log('connect() family=' + family + ' ip=' + ip + ' port=' + port);
if (port === 80 || port === 443 || port === 4070) {
console.log(' blocking!');
this.errno = ECONNREFUSED;
return -1;
} else {
console.log(' accepting!');
return connect(socket, address, addressLen);
}
} else {
return connect(socket, address, addressLen);
}
}, 'int', ['int', 'pointer', 'int']));
send('ready');
});
function toHex(v) {
let result = v.toString(16);
if (result.length === 1)
result = '0' + result;
return result;
}
'use strict';
const co = require('co');
const frida = require('frida');
const load = require('frida-load');
let session, script;
co(function *() {
session = yield frida.attach(process.argv[2]);
const source = yield load(
require.resolve('./agent.js'));
script = yield session.createScript(source);
script.events.listen('message', message => {
if (message.type === 'send') {
const stanza = message.payload;
switch (stanza.name) {
case '+ready':
console.log('Waiting for application to call recv()...');
break;
case '+result': {
console.log('Results received:');
const events = stanza.payload.events;
events.forEach(ev => {
const location = ev[0];
const target = ev[1];
const depth = ev[2];
let indent = '';
for (let i = 0; i !== depth; i++)
indent += ' | ';
console.log('\t' + indent + location + '\tCALL ' + target);
});
session.detach();
break;
}
}
} else {
console.log(message);
}
});
yield script.load();
});
$ node app.js Spotify
Waiting for application to call recv()...
Results received:
0x119875dc7 CALL 0x119887527
0x119875e7 CALL 0x11989a1e6
0x1197f4df CALL 0x11992f934
0x1197f4f3 CALL 0x1197edd7d
0x7fff8acdf6ad CALL 0x7fff8ace32dc
| 0x7fff95355059 CALL 0x7fff9535c08b
0x7fff937774be CALL 0x7fff9375d5a0
| 0x7fff9376e76 CALL 0x7fff93788d6e
| 0x7fff9376e722 CALL 0x7fff93788d2c
| | 0x7fff8d1e9754 CALL 0x7fff8d1e721
| | 0x7fff8d1e9765 CALL 0x7fff8d1e721
| | 0x7fff8d1e9421 CALL 0x7fff8d1e955c
| | | 0x7fff8d1e95bf CALL 0x7fff8d1e747
| | | | 0x7fff8d1e7417 CALL 0x7fff8d203db4
| | | 0x7fff8d1e95eb CALL 0x7fff8d203db4
0x7fff9377752c CALL 0x7fff93788d9e
| 0x7fff8d1ed7c8 CALL 0x7fff8d1e721
0x7fff9377754e CALL 0x7fff93788b5e
| 0x7fff8acdfd10 CALL 0x7fff8acdec91
| | 0x7fff8acded53 CALL 0x7fff8ace32e2
| | | 0x7fff95352182 CALL 0x7fff95353620
| | | | 0x7fff95353663 CALL 0x14ce8580
| | | | | 0x14ce858a CALL 0x7fff9535bb30
| | | | | | 0x7fff9535bb4e CALL 0x7fff9535bb71
| | | | | | | 0x7fff9535bbe0 CALL 0x7fff9536a46
| 0x7fff8acdfd20 CALL 0x7fff8ace3348
| 0x7fff8acdfd48 CALL 0x7fff8acde877
| | 0x7fff8acde8ce CALL 0x7fff8ace32e2
| | | 0x7fff95352182 CALL 0x7fff95353620
| | | | 0x7fff95353663 CALL 0x14ce8580
| | | | | 0x14ce858a CALL 0x7fff9535bb30
| | | | | | 0x7fff9535bb4e CALL 0x7fff9535bb71
| | | | | | | 0x7fff9535bbe0 CALL 0x7fff9536a46
| | 0x7fff8acde8e1 CALL 0x7fff8ace32c4
| | 0x7fff8acde923 CALL 0x7fff8acdd68f
| | | 0x7fff8acdd6ad CALL 0x7fff8ace333c
| | | | 0x7fff968e0ef CALL 0x7fff969637a6
| | | 0x7fff8acdd6b5 CALL 0x7fff8ace3348
| 0x7fff8acdfd60 CALL 0x7fff8acdd5d4
| 0x7fff8acdfd6b CALL 0x7fff8acdd5d4
| 0x7fff8acdfd76 CALL 0x7fff8acdd5d4
| 0x7fff8acdfd81 CALL 0x7fff8acdd68f
| | 0x7fff8acdd6ad CALL 0x7fff8ace333c
| | | 0x7fff968e0ef CALL 0x7fff969637a6
| | 0x7fff8acdd6b5 CALL 0x7fff8ace3348
| 0x7fff8acdfd8d CALL 0x7fff8acdf12
| | 0x7fff8acdf1ac CALL 0x7fff8ace32b8
| | 0x7fff8acdf1ed CALL 0x7fff8ace32a6
| | 0x7fff8acdf3c CALL 0x7fff8acdd779
| | 0x7fff8acdf322 CALL 0x7fff8acde966
| | | 0x7fff8acde994 CALL 0x7fff8ace332a
| | 0x7fff8acdf4e5 CALL 0x7fff8ace32a0
| | 0x7fff8acdf4f2 CALL 0x7fff8ace3234
| | 0x7fff8acdf618 CALL 0x7fff8ace329a
| | 0x7fff8acdf639 CALL 0x7fff8acde2f3
| | | 0x7fff8acde33d CALL 0x7fff8ace3324
| | | | 0x7fff587ab51 CALL 0x1197f279
| | | | | 0x1197f29a CALL 0x11992f934
| | | | | 0x1197f2ae CALL 0x1197f145
| | | | | 0x1197f2ee CALL 0x1197edd7d
| | | | | 0x1197f3b5 CALL 0x11938fd9c
| | | | | | 0x1193982ee CALL 0x1197f884
| | | | | | 0x1193982f5 CALL 0x119397261
| | | | | | 0x11939835 CALL 0x1197f89f
| | | | | | 0x119398315 CALL 0x1197f890
| | | | | | 0x119398335 CALL 0x11939dc8a
| | | | | | 0x119398364 CALL 0x1193a7710
| | | | | | 0x119398380 CALL 0x119398482
| | | | | | 0x1193983a0 CALL 0x1193a7710
| | | | | | 0x1193983a8 CALL 0x1193aed20
| | | | | | 0x1193983b8 CALL 0x1193afea0
| | | | | | 0x1193983d4 CALL 0x1193af70
| | | | | | 0x1193983df CALL 0x1193984f2
| | | | | | 0x1193983f0 CALL 0x11992f8e0
| | | | | | 0x11939849 CALL 0x1193a7520
| | | | | | 0x119398419 CALL 0x1197f8ab
| | | | | | 0x119398428 CALL 0x11939def0
$
'use strict';
const WAITING = 1;
const STALKING = 2;
const COLLECTING = 3;
const DONE = 4;
let state = WAITING;
let stalkedThreadId = null;
let blobs = [];
['recv', 'recv$NOCANCEL'].forEach(funcName => {
Interceptor.attach(Module.findExportByName('libsystem_c.dylib', funcName), {
onEnter: args => {
if (state === STALKING && this.threadId === stalkedThreadId) {
state = COLLECTING;
Stalker.unfollow();
}
},
onLeave: retval => {
if (state === WAITING) {
state = STALKING;
stalkedThreadId = this.threadId;
Stalker.follow({
events: {
call: true
},
onReceive: events => {
blobs.push(events);
if (state === COLLECTING) {
sendResult();
state = DONE;
}
}
});
}
}
});
});
send({
name: '+ready'
});
function sendResult() {
const events = blobs.reduce((result, blob) => {
const cursor = {
data: blob,
offset: 0
};
let e;
while ((e = nextEvent(cursor))) {
result.push(e);
}
return result;
}, []);
send({
name: '+result',
payload: {
events: events
}
});
}
function nextEvent(cursor) {
// FIXME: 32-bit support
const data = cursor.data;
if (cursor.offset === data.length)
return null;
skipEventType(cursor);
const location = readPointer(cursor);
const target = readPointer(cursor);
const depth = readDepth(cursor);
return [location, target, depth];
}
function skipEventType(cursor) {
cursor.offset += 8;
}
function readPointer(cursor) {
const data = cursor.data;
const offset = cursor.offset;
cursor.offset += 8;
return ptr('0x' +
data[offset + 7].toString(16) +
data[offset + 6].toString(16) +
data[offset + 5].toString(16) +
data[offset + 4].toString(16) +
data[offset + 3].toString(16) +
data[offset + 2].toString(16) +
data[offset + 1].toString(16) +
data[offset + 0].toString(16));
}
function readDepth(cursor) {
const data = cursor.data;
const offset = cursor.offset;
cursor.offset += 8;
// FIXME: sign extend
return (data[offset + 3] << 24) |
(data[offset + 2] << 16) |
(data[offset + 1] << 8) |
(data[offset + 0] << 0);
}
Twitter: @oleavr @fridadotre
Please drop by https://t.me/fridadotre
(or #frida on FreeNode)
By Ole André Vadla Ravnås
Ever wanted to understand the internals of an application running on your desktop or phone? Want to know what data is passed to a particular crypto function? Then Frida is for you! This talk will introduce Frida and show how it can be used to aid in analysis of binary applications. It will be packed with demos.
Creator of Frida. Security Researcher at NowSecure. Polyglot hacker passionate about reverse-engineering and dynamic instrumentation.