Cloning RSA tokens with Frida

At work we are using RSA SecurID for login to corporate services. RSA provides software tokens which are mobile applications that generate one-time passwords that change every minute or so. These applications are closed source and they use proprietary protocol to provision the token. This basically means that you are vendor locked-in with something that relies on security through obscurity.

I wanted to have an open-source implementation of RSA SecurID and until now I have been using stoken together with rsa_ct_kip for token provisioning. This has been working great until my employer decided to switch to the new RSA SecurID Authenticate app which uses some different kind of token provisioning that doesn’t work with rsa_ct_kip. At this point I have two options:

  1. Reverse engineer the mobile app and find how the provisioning is done. I already did this for the old app and this was my contribution to the rsa_ct_kip project.
  2. Provision a token with the new app on a rooted phone and then “export” it from there.

Reversing the provisioning protocol will take time, so I decided to go with option 2 for the short term. I already know that RSA tokens need two things to be provisioned – serial number and root seed (16 bytes). Once I get those, I can continue using stoken on all of my devices.

My plan was to install the RSA app on my rooted Nexus 5, provision a token and then analyze the application DB. The application DB has a SERIALNUMBER and ROOTSEED columns. The serial number was written in plain text but the root seed was a huge binary blob. Turns out we need some reversing after all …

This is where I decided to give Frida a try. Frida is a dynamic instrumentation toolkit that supports all major platforms, including Android. It allows you to install hooks into a target process that can trace or change execution flows. It’s pretty amazing!

I managed to extract the serial number and the root seed from RSA SecurID Authenticate ver.3.1.0 with the following script:

function bytes2hex(array) {
    var result = '';
    for (var i = 0; i < array.length; ++i) {
        result += ('0' + (array[i] & 0xFF).toString(16)).slice(-2);
    result += ' (' + array.length + ' bytes)'
    return result;

Java.perform(function() {
    Java.use('').$init.overload('[B').implementation = function(byteArr) {
        if (byteArr.length == 16) {
            console.log("Seed: " + bytes2hex(byteArr));
        return this.$init(byteArr);
    Java.use('').getNextOtpWithDataProtection.implementation = function(str, byteArr, map) {
        console.log("Serial number: " + str);
        return this.getNextOtpWithDataProtection(str, byteArr, map);

Once you have the serial number and the seed, you can use this script to get the token into a CTF format (expiration doesn’t matter):

$ SEED=<seed> SN=<serial_number> EXPIRATION=2030/12/31 ./

and then import it into stoken:

$ stoken import --token=<ctf_token>

Cheers :)