In this post, I’ll be discussing how to decrypt the mobile app AES Encrypted traffic on the fly using AES Killer.

Pre-requisites


  • Familiarity with Frida and hooking
  • Familiarity with Burpsuite and Genymotion

Requirements and Setup


  • 11x256 demo app and server

  • Frida

    • Frida on Local machine, can be installed using pip install frida
    • Frida Server on Android device, download latest version from GitHub Releases
  • Burpsuite

  • AES Killer latest jar from GitHub Releases

  • Genymotion Emulator

The required scripts and the applictaion can also be found on AES_Killer - Mobile App Demo.

Setting up API Server


Downloaded the nodejs-server.js and run it using node

$ node nodejs-server.js 
NodeJS server started on port 127.0.0.1:1337 .......

Now the API server is running on our localhost port 1337

Capturing the Application Traffic


Install the application using ADB or by drag and drop. Once installed, launch the application and you’ll see the following screen.

This application is compiled with URL http://192.168.18.134 so we need to make a little change in our burp proxy to redirect all requests to our 127.0.0.1:1337.

Now type any username and press the Button to send a request. The application will send an encrypted request to our local server as shown below. Also, in response getting an encrypted string.

Now let’s move on to getting the encryption keys and understanding the encryption & decryption mechanism.

Getting Encryption Keys using Frida


One way of getting the encryption keys is to Reverse Engineering the application and analyze its source code but if the application is obfuscated then understanding the source code becomes relatively hard. So we’ll be skipping the Reverse Engineering part as we already have application source code and below is the code segment from the encryption method.

String pre_shared_key = "aaaaaaaaaaaaaaaa"; //assume that this key was not hardcoded
String generated_iv = "bbbbbbbbbbbbbbbb";
Cipher my_cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
my_cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(pre_shared_key.getBytes("UTF-8"), "AES"), new IvParameterSpec(generated_iv.getBytes("UTF-8")));
byte[] x = my_cipher.doFinal(data.getBytes());

And we can see that the application is using AES/CBC/PKCS5PADDING encryption using Secret Key aaaaaaaaaaaaaaaa and IV Parameter bbbbbbbbbbbbbbbb but we’ll also retrieve all these values by hooking java native crypto methods using Frida.

We’ll be using frida-hook,py python script to load and execute our JS code using frida

import time
import sys
import frida

def on_message(a, b):
    print("on message ....... ")

device = frida.get_usb_device()
pid = device.spawn(["com.example.a11x256.frida_test"])
device.resume(pid)
time.sleep(1) 
session = device.attach(pid)

# session = frida.get_usb_device().attach('com.example.a11x256.frida_test')
with open("aes-hook.js") as f:
    script = session.create_script(f.read())
script.on("message", on_message)
script.load()

sys.stdin.read()

We can use this aes-hook.js to hook all related methods. We can use the below code snippet to directly hook the Secret Key and IV Parameter constructors so whenever the application initializes the object, we’ll get our Secret Key and IV.

var secretKeySpec = Java.use('javax.crypto.spec.SecretKeySpec');
secretKeySpec.$init.overload('[B', 'java.lang.String').implementation = function (a, b) {
    var result = this.$init(a, b);
    console.log("================= SecretKeySpec =====================");
    console.log("SecretKeySpec :: bytesToString :: " + bytesToString(a));
    console.log("SecretKeySpec :: bytesToBase64 :: " + bytesToBase64(a));
    console.log("SecretKeySpec :: bytesToBase64 :: " + bytesToHex(a));
    return result;
}


var ivParameterSpec = Java.use('javax.crypto.spec.IvParameterSpec');
ivParameterSpec.$init.overload('[B').implementation = function (a) {
    var result = this.$init(a);
    console.log("\n================== IvParameterSpec ====================");
    console.log("IvParameterSpec :: bytesToString :: " + bytesToString(a));
    console.log("IvParameterSpec :: bytesToBase64 :: " + bytesToBase64(a));
    console.log("IvParameterSpec :: bytesToBase64 :: " + bytesToHex(a));
    return result;
}

Or we can hook Cipher.init() method too to get the Secret Key and IV Parameter using this code segment.

var cipher = Java.use('javax.crypto.Cipher');
cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function (a, b, c) {
    var result = this.init(a, b, c);
    console.log("\n================ cipher.init() ======================");

    if (N_ENCRYPT_MODE == '1') 
    {
        console.log("init :: Encrypt Mode");    
    }
    else if(N_DECRYPT_MODE == '2')
    {
        console.log("init :: Decrypt Mode");    
    }

    console.log("Mode :: " + a);
    console.log("Secret Key :: " + bytesToHex(b));
    console.log("Secret Key :: " + bytesToBase64(b));
    console.log("IV Param :: " + bytesToHex(c));
    console.log("IV Param :: " + bytesToBase64(c));

    return result;
}

And if we want to observe data encryption and decryption on runtime, we have to hook Cipher.doFinal() method for that.

var cipher = Java.use('javax.crypto.Cipher');
cipher.doFinal.overload("[B").implementation = function (x) {
    console.log("\n================ doFinal() ======================");
    var ret = cipher.doFinal.overload("[B").call(this, x);
    console.log("doFinal :: data to encrypt/decrypt - base64 :: " + bytesToBase64(x));
    console.log("doFinal :: data ro encrypt/decrypt - string :: " + bytesToString(x));

    console.log("doFinal :: data ro encrypt/decrypt - return value :: " + ret);
    return ret;
}

Hooking with Frida


Now run the python script to launch the application and execute our JS code.

$ python frida-hook.py 
Script loaded successfully ..... 

Our JS Code is successfully loaded now, lets send a request by pressing the Button.

And if we only want to observe runtime data encrypted and decryption, comment the rest of the code except cipher.doFinal method from our js file.

For iOS please use Frida iOS Hook to get AES Secret Key and IV

Configuring AES Killer


Now that we have our Secret Key, IV Parameter and Encryption method from Frida hooking too

  • Cipher - AES/CBC/PKCS5PADDING

  • Secret Key - YWFhYWFhYWFhYWFhYWFhYQ== which is base64 of aaaaaaaaaaaaaaaa

  • IV Parameter - YmJiYmJiYmJiYmJiYmJiYg== which is base64 of bbbbbbbbbbbbbbbb

  • As the application is sending complete request body encrypted, so we’ll select Complete Request Body from Request Options

  • The application is getting encrypted string in response, so we’ll select Complete Response Body from Response Options

  • Host - http://127.0.0.1:1337

  • Now Press Start AES Killer

Decrypting the Application Traffic using AES Killer


Upon starting AES Killer, we can observe that the Burp has started to show us decrypted traffic while Burp sending encrypted traffic to the application and server.

AES Killer with Repeater, Intruder and Scanner


Once we start AES Killer, it takes control of Burp IHttpListener.processHttpMessage which is responsible for handling all outgoing and incoming traffic and AES Killer do the following

  • Before sending the final request to a server, ProcessHttpMessage encrypt the request
  • Upon receiving a response, ProcessHttpMessage decrypt the response first before showing it to us

So we’ll only be getting the Plain Text Response and can play with Plain Text request.

Manual Encryption and Decryption


We can also manually encrypt and decrypt strings using AES Killer. Let’s take an encrypted string from the request TYROd49FWJjYBfv02oiUzwRQgxWMWiw4W3oCqvNf8h3bnb7X0bobypFzMt797CYU and decrypt it using AES Killer. Similarly, we can perform the encryption too.