Skip to content
Mobile Hacking: Using Frida to Monitor Encryption

Mobile Hacking: Using Frida to Monitor Encryption

July 8, 2019

Overview

This guide demonstrates creating a Frida script to monitor encryption operations and capture encryption details using Python bindings. We’ll walk through intercepting Cipher.init() calls, handling messages between Frida and Python, and extracting key information from Android KeyStore operations.

Writing the Initial Hook

Create cipher.js to intercept calls to Cipher.init():

'use strict;'

if (Java.available) {
  Java.perform(function() {

    //Cipher stuff
    const Cipher = Java.use('javax.crypto.Cipher');

    Cipher.init.overload('int', 'java.security.Key').implementation = function (opmode, key) {

      console.log('[+] Entering Cipher.init()');
      console.log('[ ] opmode: ' + opmode);
      console.log('[ ] key: ' + key.toString());
      console.log('[-] Leaving Cipher.init()');
      console.log('');

      // call original init method
      this.init.overload('int', 'java.security.Key').call(this, opmode, key);
    }

  })
}

Create test.py to use Frida Python bindings:

import frida
import os
import sys
import argparse


def parse_hook(filename):
    print('[*] Parsing hook: ' + filename)
    hook = open(filename, 'r')
    script = session.create_script(hook.read())
    script.load()


if __name__ == '__main__':
    try:
        parser = argparse.ArgumentParser()
        parser.add_argument('package', help='Spawn a new process and attach')
        parser.add_argument('script', help='Print stack trace for each hook')
        args = parser.parse_args()


        print('[*] Spawning ' + args.package)
        pid = frida.get_usb_device().spawn(args.package)
        session = frida.get_usb_device().attach(pid)
        parse_hook(args.script)
        frida.get_usb_device().resume(pid)
        print('')
        sys.stdin.read()

    except KeyboardInterrupt:
        sys.exit(0)

Execute with: python test.py sg.vp.owasp_mobile.omtg_android cipher.js

Handling Messages in Python

Modify cipher.js to use send() instead of console.log():

Cipher.init.overload('int', 'java.security.Key').implementation = function (opmode, key) {

  send('Entering Cipher.init()');
  send('opmode: ' + opmode);
  send('key: ' + key);
  send('Leaving Cipher.init()');
  //console.log('');

  // call original init method
  this.init.overload('int', 'java.security.Key').call(this, opmode, key);
}

Update parse_hook() in test.py:

def parse_hook(filename):
    print('[*] Parsing hook: ' + filename)
    hook = open(filename, 'r')
    script = session.create_script(hook.read())
    script.on('message', on_message)
    script.load()

Add callback function:

def on_message(message, data):
    try:
        if message:
            print(message)

    except Exception as e:
        print('exception: ' + e)

Packing Data Efficiently

Consolidate multiple messages into a single structured payload to prevent output interleaving in multithreaded applications:

Cipher.init.overload('int', 'java.security.Key').implementation = function (opmode, key) {

  var args = [];
  args.push({'name': 'opmode', 'value': opmode});
  args.push({'name': 'key', 'value': key.toString()});

  var send_message = {
    'method': 'javax.crypto.Cipher.init',
    'args': args
  };

  send(send_message);

  // call original init method
  this.init.overload('int', 'java.security.Key').call(this, opmode, key);
}

Cleaning Up Output and Adding Details

Call private methods to extract meaningful information from parameters:

Cipher.init.overload('int', 'java.security.Key').implementation = function (opmode, key) {

  var args = [];
  var details = [];

  var opmodeString = this.getOpmodeString(opmode);
  var algo = this.getAlgorithm();

  args.push({'name': 'opmode', 'value': opmodeString});
  args.push({'name': 'key', 'value': key.$className});

  details.push({'name': 'key', 'value': algo});

  var send_message = {
    'method': 'javax.crypto.Cipher.init',
    'args': args,
    'details': details
  };

  send(send_message);

  // call original init method
  this.init.overload('int', 'java.security.Key').call(this, opmode, key);
}

Update Python message handler:

def on_message(message, data):
    try:
        if message:
            if message['type'] == 'send':
                payload = message['payload']
                method = payload['method']
                args = payload['args']
                details = payload['details']
                # print('[ ] {0}'.format(message['payload']))
                print('[+] Method: {0}'.format(method))

                print('[ ] Arguments:')
                for item in args:
                    print('[ ]   {0}: {1}'.format(item['name'], item['value']))

                print('[ ] Additional Details:')
                for item in details:
                    print('[ ]   {0}: {1}'.format(item['name'], item['value']))

                print('')

    except Exception as e:
        print('exception: ' + e)

Casting for More Details

Add class definitions at the top of cipher.js:

const AndroidKeyStoreKey = Java.use('android.security.keystore.AndroidKeyStoreKey');
const AndroidKeyStoreRSAPublicKey = Java.use('android.security.keystore.AndroidKeyStoreRSAPublicKey');
const AndroidKeyStoreRSAPrivateKey = Java.use('android.security.keystore.AndroidKeyStoreRSAPrivateKey');

const KeyFactory = Java.use('java.security.KeyFactory');
const KeyInfo = Java.use('android.security.keystore.KeyInfo');

Handle AndroidKeyStoreRSAPublicKey:

if (key.$className === 'android.security.keystore.AndroidKeyStoreRSAPublicKey') {
    var pub_key = Java.cast(key, AndroidKeyStoreRSAPublicKey);
    var keystoreKey = Java.cast(key, AndroidKeyStoreKey);

    details.push({'name': 'AndroidKeyStoreKey.getAlias()', 'value': keystoreKey.getAlias()});
    details.push({'name': 'key.getPublicExponent()', 'value': pub_key.getPublicExponent().toString()});
    details.push({'name': 'key.getModulus()', 'value': pub_key.getModulus().toString()});
}

Handle AndroidKeyStoreRSAPrivateKey:

if (key.$className === 'android.security.keystore.AndroidKeyStoreRSAPrivateKey') {
    var priv_key = Java.cast(key, AndroidKeyStoreRSAPrivateKey);

    var factory = KeyFactory.getInstance(key.getAlgorithm(), "AndroidKeyStore");
    var keyInfo = Java.cast(factory.getKeySpec(key, KeyInfo.class), KeyInfo);

    details.push({'name': 'keyInfo.getKeystoreAlias()', 'value': keyInfo.getKeystoreAlias()});
    details.push({'name': 'keyInfo.getKeySize()', 'value': keyInfo.getKeySize().toString()});
    details.push({'name': 'keyInfo.isInsideSecureHardware()', 'value': keyInfo.isInsideSecureHardware().toString()});

    details.push({'name': 'key.getModulus()', 'value': priv_key.getModulus().toString()});
}

This post was originally written for TrustedSec and is reproduced with permission. Complete code is available on GitHub.