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
Creator of Frida. Security Researcher at NowSecure. Polyglot hacker passionate about reverse-engineering and dynamic instrumentation.