Manipulating AES Traffic using a Chain of Proxies and Hardcoded Keys
Intercepting and Manipulating client-side AES encrypted traffic in mobile applications having hardcoded Key and IV
Overview
Mobile applications are becoming more resilient to reverse engineering and tampering with all kinds of client and server-side protections, binary hardening, code obfuscations, SSL pinning, etc which makes it that much more difficult for good or bad hackers to dig deeper into these applications.
Let's say the attacker manages to bypass the SSL Pinning and Root detection and using Burp Suite or any intercepting proxy, they are able to monitor all the requests. What are the developers supposed to do in this case to hide their API implementation from end users?
This is where some recent applications have been found encrypting their traffic client-side and sending it to the server where it's decrypted and analyzed. This minimizes the possibility of injections given that proper client-side input validations are implemented in the application. The attacker won't be able to see plaintext traffic in their proxy, and thus, won't be able to modify the endpoints and parameters.
In this article, I'll be talking about one such android application which our team at CredShields was working on, that was using AES/CBC/PKCS5Padding
to encrypt its traffic, which was then decrypted on their server, making the traffic completely gibberish to an intercepting proxy, and the technique I used to decrypt and bypass the encryption.
Source Code Analysis
It was evident from the request that the application was encrypting the request body using some kind of encryption technique and sending it to the server. Here's how a sample request looks:
The POST body was in the following format - data=AESEncryptedData:IV
So the encryption part was happening somewhere inside the APK. It is generally a good idea to have the application decompiled at this stage to analyze the source code. I use jadx-gui
for this.
Let's call the app victim.apk
. While going through the source code I noticed that it was not obfuscated, making my job much easier. I thought about searching for the keyword encrypt
to look for any functions that are being used for the encryption. There were many results but a particular function caught my eye because it was inside the application's folder itself and the file name (AES) kind of gave it away.
I opened the file com/victim.app/utils/Java_AES_Cipher
which confirmed my assumptions. It was indeed the code for encrypting and decrypting strings using AES/CBC/PKCS5Padding
.
I won't be going into the details of how the AES encryption works (Google is your friend). From the code, it is clear that the function encrypt()
is taking 3 string type parameters as arguments. Now to find all the places where the function is used, the shortcut key x
can be used in jadx-gui
. This lists out all the instances where the function is called. From the function call, the parameters become clear:
So the encrypt()
function is using a key
and an iv
to encrypt the data inside jSONObject2
. This data is then sent to the server. The key
and iv
are being fetched from the BaseMethod
class. Now, if you are really lucky, 1/10 times you will find the key
and iv
values hardcoded in the APK, as I did inside com/victim.app/base/BaseMethod
By now, I had all the things I needed to decrypt the traffic. I could even do it manually on this website. But decrypting the traffic, modifying it on my side, and then encrypting it again to send to the server was too much of a hassle. The challenge was automating this part.
The Chained Proxies
If you're not already familiar with this powerful tool, let me introduce you to Mitmproxy. This thing can execute python scripts on the ongoing traffic and make changes to the requests dynamically.
For the complete flow to work, I'll be using 3 proxy servers - Mitmproxy-1, Burp, and Mitmproxy-2, which will then forward the request to its final destination.
The Flow
The victim app will send encrypted AES traffic to my Mitmproxy-1.
Mitmproxy-1 will listen to my requests in upstream mode, execute my python script to decrypt AES traffic, and send the plaintext JSON object to Burp.
Burp will see all the traffic as it should, in plaintext where I'll be able to tamper with everything, and once I submit the requests, it'll go to Mitmproxy-2 since Burp will be set to forward all requests to Mitmproxy-2.
Mitmproxy-2 will execute my python script to encrypt the traffic and send it to the application's server and the response returned will be seen in my Burp.
Python Scripts
Mitmproxy-1
Mitmproxy-1 will be using the following script to intercept the traffic, decrypt it, and send it to the Burp:
from mitmproxy import http
from urllib.parse import quote, unquote
from Crypto.Cipher import AES
from base64 import b64decode
from Crypto.Util.Padding import pad, unpad
iv = bytes("verysecretkey", 'utf-8')
key = bytes("verysecretiv", 'utf-8')
def request(flow: http.HTTPFlow) -> None:
#if host matches the victim
if flow.request.host == "victim.com":
post_body = (flow.request.content).decode('utf-8')
#do voodoo magic to decode the data=AESEncodeddata:IV
to_decrypt = unquote(post_body.split("=")[1]).split(":")[0].replace("\n","")
#send decrypted data
flow.request.content = decrypt(to_decrypt)
#function to decrypt AES
def decrypt(message):
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = unpad(cipher.decrypt(b64decode(message)), AES.block_size)
return decrypted
This script is intercepting the traffic for the domain victim.com
, splitting the POST data and extracting only the AES-encrypted POST body which is then set as the new content and sent to Burp Suite.
Mitmproxy-2
from mitmproxy import http
from urllib.parse import quote, unquote
from Crypto.Cipher import AES
from base64 import b64encode
from Crypto.Util.Padding import pad, unpad
#keys
iv = bytes("verysecretkey", 'utf-8')
key = bytes("verysecretiv", 'utf-8')
def request(flow: http.HTTPFlow) -> None:
#if host matches
if flow.request.host == "victim.com":
print(flow.request.host)
post_body = (flow.request.content).decode('utf8')
#encrypting data coming from Burp
edata = quote(encrypt(post_body))
#appending IV to the request as it was originally done
prefix = quote(":") + quote(b64encode(iv))
#final POST body
to_send = "data=" + edata + prefix
#send to server
flow.request.content = bytes(to_send, 'utf-8')
#function to encrypt AES
def encrypt(message):
cipher = AES.new(key, AES.MODE_CBC, iv)
encrypted = cipher.encrypt(pad(message.encode("UTF-8"), AES.block_size))
return (b64encode(encrypted).decode('utf-8'))
This script intercepts the plaintext traffic coming from Burp, extracts the POST body, encrypts it, appends IV, base64 encodes the whole thing, and sends it to the victim server.
Everything in Action
I started by installing Mitmproxy's SSL certificate on my android device. This process is similar to what you do with Burp Suite. A detailed guide for mitmproxy is available here.
I configured a proxy on my android device to forward all traffic to my device's IP (192.168.xx.xx
) and port 8080
.
Once that was done, I started my first proxy listener in upstream mode which listens on port 8080
and forwards all traffic to http://127.0.0.1:7070
using the following command:
mitmproxy --mode upstream:http://127.0.0.1:7070 --ssl-insecure -s mitm_to_burp.py
I configured Burp to listen on port 7070
on all interfaces. I also enabled upstream proxy in Burp to forward all traffic to port 8082
:
I started another Mitmproxy instance to listen on port 8082
for the traffic coming from Burp with the following command:
mitmproxy --listen-port 8082 -s burp_to_mitm.py
Now that everything was set up, it was time to initiate a request in the application and verify the flow:
And it worked. Burp was able to intercept and manipulate the requests successfully and show the response returned by the server.
The whole process could have been done in an efficient way using Burp Suite's Extender API or Frida hooks. Maybe that's a topic for the next part?
Mitigations
DO NOT hardcode sensitive data inside your application as it can easily be decompiled and obtained. Use Android Keystore API for storing sensitive keys and tokens.
Obfuscate the source code using Proguard or similar tools which will make it difficult for attackers to reverse engineer the code.
Have Root detection and SSL Pinning in place
The Python scripts can also be found at: