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 Swift, 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.post({ magic: 21 });
yield script.post({ 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.post({ 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
A short introduction to Frida.
Creator of Frida. Security Researcher at NowSecure. Polyglot hacker passionate about reverse-engineering and dynamic instrumentation.