Android Pentest: How I Decrypted Mobile API

First of all, I would like to thank you for being here to listen to me share my story. Have a great day!
Overview
I am writing this blog to share my recent experience when I pentested an android application. It was easy to capture the application request but the entire request body and response were encrypted. This was bad because without decrypting the data above, I would not be able to modify the request or read the response data.
In this article I will cover bunble files in React Native as well as AES encryption and finally I will write a tool to automate the encryption and decryption process.
Next I will share how I decrypted Mobile API.
Intercept request
First, by analyzing the network-security-config.xml
file, I noticed that the application is configured to trust certificates from both the system store and /raw/my_ca
. This means that to intercept the application's requests, we only need to add Burp Suite's certificate to the system certificate store on the device.

We are now able to intercept the application's requests, but unfortunately, the data in the request body is encrypted.

Next, we will work together to decrypt them
Decrypted Mobile API
When checking Logcat, I noticed that the logged data includes both encrypted and plaintext.

From there, I attempted to locate the encryption and decryption functions in the code by decompiling the APK using JADX. My approach was to look for logging points and functions related to HTTP requests. However, everything seemed to hit a dead end—I spent a considerable amount of time on this without any success.

It wasn’t over yet. A colleague pointed out that the app was built using React Native JS, meaning its source code couldn’t be analyzed with JADX. Instead, I had to extract the APK and inspect the bundle file located in the assets
directory.


index.android.bundle
is a package file that contains all the JavaScript code of the application developed in React Native. This file is generated during the application's compilation/packaging process. It includes not only the code that has been written, but also all the code from the libraries and dependencies that the application uses.
Next, I will decompile the bundle file to analyze the application's JavaScript source code.
npx react-native-decompiler -i ./index.android.bundle -o ./output
I was able to locate where the encrypt and decrypt functions were called, but they were obfuscated. Fortunately, we can make the code more readable using this tool.

Both the encrypt and decrypt functions require KEY and IV as parameters, leading me to suspect that the encryption algorithm used is AES. The next step is to locate the KEY and IV to proceed with decryption. BOOM!!

Based on the gathered information, I wrote a Python script to encrypt and decrypt the data in the request and response bodies.

Now I can encrypt and decrypt data, but as you know, it's quite manual. I want to modify the plaintext data directly and receive the data in plaintext as well. The idea is to write a proxy that sits in the middle, automatically decrypting and encrypting the data. To achieve this, I will use Python, MITM proxy, and Burp Suite.
First, I will configure Burp Suite as follows.

This is the Python script combining MITM proxy and AES encryption:
from mitmproxy import http
from hashlib import md5
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64
# AES Key & IV
KEY = b"xxxx"
IV = b"xxxx"
# AES Encryption
def encrypt_aes_cbc(plain_text, key, iv):
cipher = AES.new(key, AES.MODE_CBC, iv)
encrypted_bytes = cipher.encrypt(pad(plain_text.encode(), AES.block_size))
return base64.b64encode(encrypted_bytes).decode()
# AES Decryption
def decrypt_aes_cbc(encrypted_text, key, iv):
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted_bytes = unpad(cipher.decrypt(base64.b64decode(encrypted_text)), AES.block_size)
return decrypted_bytes.decode()
# MITM Request Hook
def request(flow: http.HTTPFlow) -> None:
if flow.request.headers.get("Test") == "1":
body = flow.request.text
# Encrypt request body
encrypted_body = encrypt_aes_cbc(body, KEY, IV)
flow.request.text = '"'+encrypted_body+'"'
# MITM Response Hook
def response(flow: http.HTTPFlow) -> None:
try:
encrypted_body = flow.response.text
decrypted_body = decrypt_aes_cbc(encrypted_body, KEY, IV)
flow.response.text = decrypted_body
except Exception as e:
flow.response.text = f"Decryption failed: {str(e)}"
mitmdump -p 8088 -s .\aes_proxy.py -k
And finally, the result.

Final Thought
Try harder!!!