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

Motivation

  • 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
  • Highly modular, JavaScript is optional
  • 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

Let's explore the basics

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().

Let's see what files Twitter open()s on macOS

Let's try interacting with Objective-C

Let's take that to iOS.

Let's figure out who is calling open().

Let's inspect registers.

Let's explore a bit with frida-trace on SnapChat.

Android instrumentation

'use strict';

Java.perform(function () {
  var MainActivity = Java.use(
      're.frida.helloworld.MainActivity');
  MainActivity.isRegistered.implementation = function () {
    console.log('isRegistered() w00t');
    return true;
  };
});

Injecting errors

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

All calls between two recv() calls

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

Questions?


Twitter: @oleavr @fridadotre

Thanks!

Please drop by https://t.me/fridadotre

(or #frida on FreeNode)

Unlocking secrets of proprietary software using Frida

By Ole André Vadla Ravnås

Unlocking secrets of proprietary software using Frida

Ever wanted to understand the internals of an application running on your desktop or phone? Want to know what data is passed to a particular crypto function? Then Frida is for you! This talk will introduce Frida and show how it can be used to aid in analysis of binary applications. It will be packed with demos.

  • 9,176