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
#include <stdio.h>
#include <unistd.h>
void
f (int n)
{
printf ("Number: %d\n", n);
}
int
main ()
{
int i = 0;
printf ("f() is at %p\n", f);
while (1)
{
f (i++);
sleep (1);
}
}
1) Build and run the test app that we will instrument:
$ clang hello.c -o hello
$ ./hello
f() is at 0x106a81ec0
Number: 0
Number: 1
Number: 2
…
2) Make note of the address of f(), which is 0x106a81ec0 here.
$ # install Node.js 5.1
$ npm install co frida frida-load
$ node app.js
{ type: 'send', payload: 531 }
{ type: 'send', payload: 532 }
…
'use strict';
const co = require('co');
const frida = require('frida');
const load = require('frida-load');
let session, script;
co(function *() {
session = yield frida.attach('hello');
const source = yield load(
require.resolve('./agent.js'));
script = yield session.createScript(source);
script.events.listen('message', message => {
console.log(message);
});
yield script.load();
});
Address of f() goes here
'use strict';
Interceptor.attach(ptr('0x106a81ec0'), {
onEnter(args) {
send(args[0].toInt32());
}
});
$ pip install frida
$ python app.py
{'type': 'send', 'payload': 531}
{'type': 'send', 'payload': 532}
…
import frida
import sys
session = frida.attach("hello")
script = session.create_script("""
Interceptor.attach(ptr("0x106a81ec0"), {
onEnter: function(args) {
send(args[0].toInt32());
}
});
""")
def on_message(message, data):
print(message)
script.on('message', on_message)
script.load()
sys.stdin.read()
Address of f() goes here
There are also language-bindings for QML, .NET, etc. The API is the same except for local conventions like create_script() vs createScript().
We will stick to the Node.js bindings for the remainder of this presentation.
$ node app.js
'use strict';
const co = require('co');
const frida = require('frida');
const load = require('frida-load');
let session, script;
co(function *() {
session = yield frida.attach('hello');
const source = yield load(
require.resolve('./agent.js'));
script = yield session.createScript(source);
yield script.load();
});
Address of f() goes here
Number: 1281
Number: 1282
Number: 1337
Number: 1337
Number: 1337
Number: 1337
Number: 1296
Number: 1297
Number: 1298
…
Once we stop it the target is back to normal
'use strict';
Interceptor.attach(ptr('0x106a81ec0'), {
onEnter(args) {
args[0] = ptr("1337");
}
});
$ node app.js
'use strict';
const co = require('co');
const frida = require('frida');
const load = require('frida-load');
let session, script;
co(function *() {
session = yield frida.attach('hello');
const source = yield load(
require.resolve('./agent.js'));
script = yield session.createScript(source);
yield script.load();
});
Address of f() goes here
Number: 1879
Number: 1911
Number: 1911
Number: 1911
Number: 1880
…
'use strict';
const f = new NativeFunction(
ptr('0x106a81ec0'), 'void', ['int']);
f(1911);
f(1911);
f(1911);
$ node app.js
'use strict';
const co = require('co');
const frida = require('frida');
const load = require('frida-load');
let session, script;
co(function *() {
session = yield frida.attach('hello');
const source = yield load(
require.resolve('./agent.js'));
script = yield session.createScript(source);
script.events.listen('message', message => {
console.log(message);
});
yield script.load();
});
{ type: 'send',
payload: { user: { name: 'john.doe' }, key: '1234' } }
{ type: 'error',
description: 'ReferenceError: oops is not defined',
stack: 'ReferenceError: oops is not defined\n at Object.1 (agent.js:10:1)\n at s (../../node_modules/browser-pack/_prelude.js:1:1)\n at e (../../node_modules/browser-pack/_prelude.js:1:1)\n at ../../node_modules/browser-pack/_prelude.js:1:1',
fileName: 'agent.js',
lineNumber: 10,
columnNumber: 1 }
'use strict';
send({
user: {
name: 'john.doe'
},
key: '1234'
});
oops;
$ node app.js
'use strict';
const co = require('co');
const frida = require('frida');
const load = require('frida-load');
let session, script;
co(function *() {
session = yield frida.attach('hello');
const source = yield load(
require.resolve('./agent.js'));
script = yield session.createScript(source);
script.events.listen('message', message => {
console.log(message);
});
yield script.load();
yield script.postMessage({ magic: 21 });
yield script.postMessage({ magic: 12 });
});
{ type: 'send', payload: 42 }
{ type: 'send', payload: 36 }
'use strict';
let i = 2;
function handleMessage(message) {
send(message.magic * i);
i++;
recv(handleMessage);
}
recv(handleMessage);
$ node app.js
'use strict';
const co = require('co');
const frida = require('frida');
const load = require('frida-load');
let session, script;
co(function *() {
session = yield frida.attach('hello');
const source = yield load(
require.resolve('./agent.js'));
script = yield session.createScript(source);
script.events.listen('message', message => {
const number = message.payload.number;
script.postMessage({ number: number * 2 });
});
yield script.load();
});
Number: 2183
Number: 2184
Number: 4370
Number: 4372
Number: 4374
Number: 4376
Number: 4378
Number: 2190
Number: 2191
Number: 2192
…
Address of f() goes here
Once we stop it the target is back to normal
'use strict';
Interceptor.attach(ptr('0x106a81ec0'), {
onEnter: args => {
send({ number: args[0].toInt32() });
const op = recv(reply => {
args[0] = ptr(reply.number);
});
op.wait();
}
});
'use strict';
const co = require('co');
const frida = require('frida');
const load = require('frida-load');
let session, script;
co(function *() {
const device = yield frida.getUsbDevice();
const pid = yield device.spawn(['com.apple.AppStore']);
session = yield device.attach(pid);
const source = yield load(
require.resolve('./agent.js'));
script = yield session.createScript(source);
script.events.listen('message', message => {
if (message.type === 'send' && message.payload.event === 'ready')
device.resume(pid);
else
console.log(message);
});
yield script.load();
})
.catch(console.error);
$ node app.js
{ type: 'send', payload: { event: 'call', name: 'CC_MD5' } }
{ type: 'send', payload: { event: 'call', name: 'CCDigest' } }
{ type: 'send', payload: { event: 'call', name: 'CNEncode' } }
…
'use strict';
Module.enumerateExports('libcommonCrypto.dylib', {
onMatch: e => {
if (e.type === 'function') {
try {
Interceptor.attach(e.address, {
onEnter: args => {
send({ event: 'call', name: e.name });
}
});
} catch (error) {
console.log('Ignoring ' + e.name + ': ' + error.message);
}
}
},
onComplete: () => {
send({ event: 'ready' });
}
});
$ sudo easy_install frida
$ frida-trace -U -f com.apple.AppStore -I libcommonCrypto.dylib
'use strict';
const co = require('co');
const frida = require('frida');
const load = require('frida-load');
let session, script;
co(function *() {
const device = yield frida.getUsbDevice();
const app = yield device.getFrontmostApplication();
if (app === null)
throw new Error("No app in foreground");
session = yield device.attach(app.pid);
const source = yield load(
require.resolve('./agent.js'));
script = yield session.createScript(source);
script.events.listen('message', message => {
console.log(message.payload.ui);
session.detach();
});
yield script.load();
});
$ node --harmony dump-ui.js
<UIWindow: 0x15fe3ca40; frame = (0 0; 375 667); autoresize = W+H; gestureRecognizers = <NSArray: 0x17424c1e0>; layer = <UIWindowLayer: 0x17023dcc0>>
| <UIView: 0x15fd2dbd0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x174432320>>
| | <UIView: 0x15fe64250; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x170235340>>
| | | <UIView: 0x15fd506e0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x174222320>>
| | | | <UILayoutContainerView: 0x15fe65380; frame = (0 0; 375 667); autoresize = W+H; gestureRecognizers = <NSArray: 0x170646b70>; layer = <CALayer: 0x170237160>>
| | | | | <UINavigationTransitionView: 0x15fe66390; frame = (0 0; 375 667); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x1702347a0>>
| | | | | | <UIViewControllerWrapperView: 0x15fe7e3d0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x1706326a0>>
| | | | | | | <UIView: 0x15fe75cd0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x170632580>>
| | | | | | | | <UIView: 0x15fe16b40; frame = (0 0; 375 667); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x170632540>>
| | | | | | | | | <TFNSwipeScrollView: 0x15fd36250; baseClass = UIScrollView; frame = (0 0; 385 667); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x17425f530>; layer = <CALayer: 0x174424860>; contentOffset: {0, 0}; contentSize: {375, 0}>
| | | | | | | | | | <UIActivityIndicatorView: 0x15fe54230; frame = (177.5 76.5; 20 20); hidden = YES; layer = <CALayer: 0x170237100>>
| | | | | | | | | | | <UIImageView: 0x15fe55260; frame = (0 0; 20 20); opaque = NO; userInteractionEnabled = NO; animations = { contents=<CAKeyframeAnimation: 0x170238440>; }; layer = <CALayer: 0x170233900>>
| | | | | | | | | | <TFNCustomHitTestView: 0x15fe7e050; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x170632740>>
| | | | | | | | | | | <TFNPullToRefreshControl: 0x15fe78c80; baseClass = UIControl; frame = (0 14; 375 50); alpha = 0; layer = <CALayer: 0x170628c20>>
| | | | | | | | | | | | <UIImageView: 0x15fe77ed0; frame = (176 1; 22 48); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x17062de40>>
| | | | | | | | | | | | <UIActivityIndicatorView: 0x15fe87850; frame = (177 15; 20 20); alpha = 0; hidden = YES; layer = <CALayer: 0x17062de20>>
| | | | | | | | | | | | | <UIImageView: 0x15fe879f0; frame = (0 0; 20 20); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x17062e000>>
| | | | | | | | | | | <TFNTableView: 0x1608db000; baseClass = UITableView; frame = (0 0; 375 667); gestureRecognizers = <NSArray: 0x17444d500>; layer = <CALayer: 0x170232a00>; contentOffset: {0, 909.5}; contentSize: {375, 43735.5}>
| | | | | | | | | | | | <UITableViewWrapperView: 0x15fd53070; frame = (0 0; 375 667); gestureRecognizers = <NSArray: 0x17444f480>; layer = <CALayer: 0x174426200>; contentOffset: {0, 0}; contentSize: {375, 667}>
| | | | | | | | | | | | | <T1StatusCell: 0x16013ac00; baseClass = UITableViewCell; frame = (0 1228; 375 428); autoresize = W; layer = <CALayer: 0x174a30880>>
| | | | | | | | | | | | | | <UITableViewCellContentView: 0x15fdb0df0; frame = (0 0; 375 428); gestureRecognizers = <NSArray: 0x174650f50>; layer = <CALayer: 0x174a30a60>>
| | | | | | | | | | | | | | | <T1StatusView: 0x15fdd0440; frame = (0 0; 375 428); layer = <CALayer: 0x174a309c0>>
| | | | | | | | | | | | | | | | <T1StatusBodyTextView: 0x15fdc5750; frame = (69.5 46.5; 295.5 50); layer = <CALayer: 0x174a30800>>
| | | | | | | | | | | | | | | | | <TFNAttributedTextView: 0x15fdd06b0; frame = (0 0; 295.5 50); text = 'When will ENOUGH BE ENOUG...'; opaque = NO; layer = <CALayer: 0x174a30380>>
| | | | | | | | | | | | | | | | <T1StatusAuthorView: 0x15fdcf150; frame = (71.5 29.5; 291.5 18); layer = <CALayer: 0x174a30740>>
| | | | | | | | | | | | | | | | | <UILabel: 0x15fdcf2e0; frame = (0 0; 99 18); text = 'Hilary $wank .'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x17428d5c0>>
| | | | | | | | | | | | | | | | | <UILabel: 0x15fdcf490; frame = (103 2; 60 16); text = '@XXXlLex'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x17428c8a0>>
| | | | | | | | | | | | | | | | | <UILabel: 0x15fdcf640; frame = (268.5 2; 23 16); text = '15h'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x17428c300>>
| | | | | | | | | | | | | | | | | <UIImageView: 0x15fdcf7f0; frame = (0 0; 0 0); hidden = YES; userInteractionEnabled = NO; layer = <CALayer: 0x174a30620>>
| | | | | | | | | | | | | | | | | <TFNPaddedButton: 0x16115df00; baseClass = UIButton; frame = (280.5 3.5; 11 11); hidden = YES; opaque = NO; layer = <CALayer: 0x1716201a0>>
| | | | | | | | | | | | | | | | | | <UIImageView: 0x16123a990; frame = (0 0; 11 11); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x174430480>>
| | | | | | | | | | | | | | | | <T1StatusInlineActionsView: 0x15fdcf930; frame = (71.5 401.5; 211.5 14); layer = <CALayer: 0x174a305c0>>
| | | | | | | | | | | | | | | | | <T1StatusInlineReplyButton: 0x15fdcbc30; baseClass = UIButton; frame = (-6 -5; 35 24); opaque = NO; layer = <CALayer: 0x174a2fea0>>
| | | | | | | | | | | | | | | | | | <UIImageView: 0x15fdc8ef0; frame = (6 2; 23 20); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x174a2c7e0>>
| | | | | | | | | | | | | | | | | <T1StatusInlineRetweetButton: 0x15fdcc540; baseClass = UIButton; frame = (64 -5; 58 24); opaque = NO; layer = <CALayer: 0x174a301c0>>
| | | | | | | | | | | | | | | | | | <UILabel: 0x15fdd1850; frame = (36 4.5; 31.5 14.5); text = '1 612'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x17428e920>>
| | | | | | | | | | | | | | | | | | <UIImageView: 0x15fdc6bd0; frame = (6 2; 27.5 20); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x174a2fbc0>>
| | | | | | | | | | | | | | | | | <T1StatusInlineFavoriteButton: 0x15fdc64e0; baseClass = UIButton; frame = (137 -5; 40 24); opaque = NO; layer = <CALayer: 0x174a2f360>>
| | | | | | | | | | | | | | | | | | <UILabel: 0x15fdc8170; frame = (29 4.5; 21 14.5); text = '582'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x17428e7e0>>
| | | | | | | | | | | | | | | | | | <TFNAnimatableImageView: 0x15fdc8320; baseClass = UIImageView; frame = (6 2; 20 20); opaque = NO; userInteractionEnabled = NO; layer = <TFNAnimatableImageViewLayer: 0x174a2dd40>>
| | | | | | | | | | | | | | | | <TFNTwitterFollowControl: 0x15fedfff0; frame = (319.5 396.5; 43.5 24); layer = <CALayer: 0x170824e00>>
| | | | | | | | | | | | | | | | | <TFNButton: 0x161171f10; baseClass = UIButton; frame = (0 0; 43.5 24); opaque = NO; layer = <CALayer: 0x170835860>>
| | | | | | | | | | | | | | | | | | <UIImageView: 0x16115ba50; frame = (0 0; 43.5 24); userInteractionEnabled = NO; layer = <CALayer: 0x171820500>>
| | | | | | | | | | | | | | | | | | <UIImageView: 0x15fdc8960; frame = (6 2; 31.5 20); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x174a2d1a0>>
| | | | | | | | | | | | | | | | <TFNTwitterAvatarImageView: 0x1611725e0; baseClass = UIImageView; frame = (12 32.5; 51.5 51.5); opaque = NO; layer = <CALayer: 0x1714385e0>>
| | | | | | | | | | | | | | | | <UIImageView: 0x15fdc8670; frame = (47.5 9.5; 16 16); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x174a2f380>>
| | | | | | | | | | | | | | | | <UILabel: 0x15fdc87b0; frame = (71.5 9.5; 291.5 15.5); text = 'LM retweeted'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x17428ee70>>
| | | | | | | | | | | | | | | | <T1NativeCardContainerView: 0x16114f3b0; frame = (71.5 115.5; 291.5 220); hidden = YES; layer = <CALayer: 0x17083e4c0>>
| | | | | | | | | | | | | | | | | <T1NativeWebCardView: 0x16123aaf0; frame = (0 0; 291.5 220); clipsToBounds = YES; layer = <CALayer: 0x174c23f60>>
| | | | | | | | | | | | | | | | | | <TFNHighlightControl: 0x16123b100; baseClass = UIControl; frame = (0 0; 291.5 220); gestureRecognizers = <NSArray: 0x174658360>; layer = <CALayer: 0x174c23d80>>
| | | | | | | | | | | | | | | | | | | <UIImageView: 0x16123b2a0; frame = (0 0; 291.5 146); clipsToBounds = YES; layer = <CALayer: 0x174c23ec0>>
| | | | | | | | | | | | | | | | | | | | <TFNHighlightControl: 0x16123b3e0; baseClass = UIControl; frame = (0 0; 291.5 146); gestureRecognizers = <NSArray: 0x174a498a0>; layer = <CALayer: 0x174c23fc0>>
| | | | | | | | | | | | | | | | | | | <TFNAttributedTextView: 0x16123b580; frame = (11 154; 269.5 19); opaque = NO; layer = <CALayer: 0x174c24100>>
| | | | | | | | | | | | | | | | | | | | <TFNHighlightControl: 0x16123b6e0; baseClass = UIControl; frame = (0 0; 269.5 19); gestureRecognizers = <NSArray: 0x174a522a0>; layer = <CALayer: 0x174c23f20>>
| | | | | | | | | | | | | | | | | | | <TFNAttributedTextView: 0x16123b880; frame = (11 174; 267 19); opaque = NO; layer = <CALayer: 0x174c24060>>
| | | | | | | | | | | | | | | | | | | | <TFNHighlightControl: 0x16123b9e0; baseClass = UIControl; frame = (0 0; 267 19); gestureRecognizers = <NSArray: 0x174a52090>; layer = <CALayer: 0x174c242a0>>
| | | | | | | | | | | | | | | | | | | <TFNAttributedTextView: 0x16123bb80; frame = (11 194; 116 18); opaque = NO; layer = <CALayer: 0x174c24000>>
| | | | | | | | | | | | | | | | | | | | <TFNHighlightControl: 0x16123bce0; baseClass = UIControl; frame = (0 0; 116 18); gestureRecognizers = <NSArray: 0x174a452b0>; layer = <CALayer: 0x174c24420>>
…
'use strict';
ObjC.schedule(ObjC.mainQueue, () => {
const window = ObjC.classes.UIWindow.keyWindow();
const ui = window.recursiveDescription().toString();
send({ ui: ui });
});
'use strict';
const co = require('co');
const frida = require('frida');
const load = require('frida-load');
let session, script;
co(function *() {
const device = yield frida.getUsbDevice();
session = yield device.attach('re.frida.helloworld');
const source = yield load(
require.resolve('./agent.js'));
script = yield session.createScript(source);
script.events.listen('message', message => {
console.log(message);
});
yield script.load();
});
'use strict';
Dalvik.perform(() => {
const MainActivity = Dalvik.use(
're.frida.helloworld.MainActivity');
MainActivity.isRegistered.implementation = () => {
console.log('isRegistered() w00t');
return true;
};
});
'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 => {
console.log(message);
});
yield script.load();
})
.catch(console.error);
$ 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
Please drop by #frida on FreeNode, and don't forget to join our mailing list:
By Ole André Vadla Ravnås
Have this black box process that you're just dying to peek inside of? Is this process perhaps running on your cell phone, or on a closed-source OS, and you just got to interoperate with it? Is the company behind this proprietary software being less than forthcoming with APIs and docs? Well, if you know a little JavaScript and have a little persistence, perhaps we can help... In this talk, we show what you can do with Frida, a scriptable dynamic binary instrumentation toolkit for Windows, Mac, Linux, iOS, Android, and QNX. We show by example how to write snippets of custom debugging code in JavaScript, and then dynamically insert these scripts into running processes. Hook any function, spy on crypto APIs or trace private application code. No source code, no permission needed!
Creator of Frida. Security Researcher at NowSecure. Polyglot hacker passionate about reverse-engineering and dynamic instrumentation.