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

Motivation behind Frida

  • Existing tools often not a good fit for the task at hand
  • Creating a new tool usually takes too much effort
  • Short feedback loop: reversing is an iterative process
  • Use one toolkit for multi-platform instrumentation
  • Future remake of oSpy (see below)

What is Frida?

  • Dynamic instrumentation toolkit
    • Debug live processes
  • Scriptable
    • Execute your own debug scripts inside another process
  • Multi-platform
    • Windows, Mac, Linux, iOS, Android, QNX
  • Open Source

Why would you need Frida?

  • For reverse-engineering
  • For programmable debugging
  • For dynamic instrumentation
  • But ultimately: To enable rapid development of new tools for the task at hand

Architecture

  • Highly modular and decoupled
  • Instrumentation core written in C (frida-gum)
    • C++ and JavaScript language bindings
  • Easy to use high-level API: "run JS in that process"
    • Packages instrumentation core in a library
    • Injects that using a per OS injector component
    • Communicates with it over a per OS transport
    • Bindings: C, Node.js, Python, .NET, Swift, Qt
  • Philosophy: only bare metal building blocks, community provides use-case-specific modules in npm, e.g. frida-fs, frida-screenshot, frida-uikit, frida-uiwebview, etc.

Let's explore the basics

#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.

Hooking f() from Node.js

$ # 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());
  }
});

Hooking f() from Python

$ 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.

Modifying function arguments

$ 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");
  }
});

Calling functions

$ 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);

Sending messages

$ 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;

Receiving messages

$ 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);

Blocking receives

$ 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();
  }
});

Launch and spy on iOS app

'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' });
  }
});

But there's an app for that

$ sudo easy_install frida
$ frida-trace -U -f com.apple.AppStore -I libcommonCrypto.dylib

io plugin code walkthrough

Questions?

 

Twitter: @oleavr

Thanks!

Code is at:

https://github.com/nowsecure/r2frida

Soon also available in r2pm.

r2frida: the ultimate static analysis on dynamic steroids

By Ole André Vadla Ravnås

r2frida: the ultimate static analysis on dynamic steroids

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.

  • 3,390
Loading comments...

More from Ole André Vadla Ravnås