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 -
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
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:
encrypt() function is using a
key and an
iv to encrypt the data inside
jSONObject2. This data is then sent to the server. The
iv are being fetched from the
BaseMethod class. Now, if you are really lucky, 1/10 times you will find the
iv values hardcoded in the APK, as I did inside
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 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.
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("=")).split(":").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.
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
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
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?
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: