Ole André Vadla Ravnås PRO
Creator of Frida. Security Researcher at NowSecure. Polyglot hacker passionate about reverse-engineering and dynamic instrumentation.
the ultimate static analysis on dynamic steroids
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
Demos
#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 fs = require('mz/fs');
let session, script;
co(function *() {
session = yield frida.attach('hello');
const source = yield fs.readFile(
require.resolve('./agent.js'), 'utf-8');
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 fs = require('mz/fs');
let session, script;
co(function *() {
session = yield frida.attach('hello');
const source = yield fs.readFile(
require.resolve('./agent.js'), 'utf-8');
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 fs = require('mz/fs');
let session, script;
co(function *() {
session = yield frida.attach('hello');
const source = yield fs.readFile(
require.resolve('./agent.js'), 'utf-8');
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 fs = require('mz/fs');
let session, script;
co(function *() {
session = yield frida.attach('hello');
const source = yield fs.readFile(
require.resolve('./agent.js'), 'utf-8');
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 fs = require('mz/fs');
let session, script;
co(function *() {
session = yield frida.attach('hello');
const source = yield fs.readFile(
require.resolve('./agent.js'), 'utf-8');
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 fs = require('mz/fs');
let session, script;
co(function *() {
session = yield frida.attach('hello');
const source = yield fs.readFile(
require.resolve('./agent.js'), 'utf-8');
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
io plugin code walkthrough
Twitter: @oleavr
Code is at:
https://github.com/nowsecure/r2frida
Soon also available in r2pm.
By Ole André Vadla Ravnås
Have you ever wanted to enhance your static analysis with live telemetry from a running instance of the software that you're analyzing? In this talk I will show you how you can do this by combining r2 with Frida.
Creator of Frida. Security Researcher at NowSecure. Polyglot hacker passionate about reverse-engineering and dynamic instrumentation.