How to Use Frida RPC Forwarding to Bypass Mobile App Encryption with Python
This article demonstrates how to employ Frida's RPC forwarding to intercept and bypass encryption in a mobile app, providing step‑by‑step environment setup, hook scripts, and a FastAPI service that enables Python‑based crawling of protected API endpoints.
Frida RPC Algorithm Forwarding
Introduction
The article introduces a tool for crawling mobile app APIs using Frida RPC forwarding, which avoids the need to analyse complex Java and native code.
Why Use RPC Forwarding
Most Android apps are built with Java and native C++ libraries compiled to .so files, making reverse engineering difficult. By using RPC forwarding, you can call Java or native methods directly via Frida, retrieve encrypted data, and avoid deep analysis.
Environment
pixel2 v10 (rooted)
Magisk v23.0
Charles v4.6.2
Drony v1.3.154
Python v3.8.6
frida v14.2.18RPC Forwarding Example
The target app is "嘟嘟牛". Using packet capture we find the login API endpoint http://api.dodovip.com/api/user/login. The request payload is encrypted with a field like Encrypt:xxxx.
Analysis
Decompiled the APK with jadx and searched for the keyword Encrypt. The encryption logic resides in the following class:
Hooking the relevant methods with Frida yields the script below:
Java.perform(function () {
function printMap2(map) {
return Java.cast(map, Java.use("java.util.HashMap"));
}
// hook encodeDesMap
Java.use("com.dodonew.online.http.RequestUtil").encodeDesMap.overload('java.lang.String','java.lang.String','java.lang.String').implementation = function (data, desKey, desIV) {
console.log("RequestUtil encodeDesMap is call");
console.log("data:", data);
console.log("desKey:", desKey);
console.log("desIV:", desIV);
let result = this.encodeDesMap(data, desKey, desIV);
console.log("RequestUtil encodeDesMap result:", result);
return result;
};
// hook paraMap
Java.use("com.dodonew.online.http.RequestUtil").paraMap.overload('java.util.Map','java.lang.String','java.lang.String').implementation = function (addMap, append, sign) {
console.log("RequestUtil paraMap is call");
console.log("addMap:", addMap);
console.log("addMap:", printMap2(addMap));
console.log("append:", append);
console.log("sign:", sign);
let result = this.paraMap(addMap, append, sign);
console.log("RequestUtil paraMap result:", result);
return result;
};
// hook decodeDesJson
Java.use("com.dodonew.online.http.RequestUtil").decodeDesJson.implementation = function (json, desKey, desIV) {
console.log("RequestUtil decodeDesJson is call");
console.log("json:", json);
console.log("desKey:", desKey);
console.log("desIV:", desIV);
let result = this.decodeDesJson(json, desKey, desIV);
console.log("RequestUtil decodeDesJson result:", result);
return result;
};
});Organizing Calls
Based on the hooks, the active calls are:
// Request encryption
function callparaMap(username, userPwd, timeStamp) {
let result = "";
Java.perform(function () {
let map = Java.use("java.util.HashMap").$new();
map.put("timeStamp", timeStamp);
map.put("loginImei", "Androidnull");
map.put("equtype", "ANDROID");
map.put("userPwd", userPwd);
map.put("username", username);
let r1 = Java.use("com.dodonew.online.http.RequestUtil").paraMap(map, "sdlkjsdljf0j2fsjk", "sign");
result = Java.use("com.dodonew.online.http.RequestUtil").encodeDesMap(r1, "65102933", "32028092");
});
return result;
}
// Response decryption
function calldecodedesjson(data) {
let result = "";
Java.perform(function () {
result = Java.use("com.dodonew.online.http.RequestUtil").decodeDesJson(data, "65102933", "32028092");
});
return result;
}Building the Service
Combine the Frida script with a FastAPI server so that the crawler can request encryption/decryption via HTTP.
from fastapi import FastAPI
import uvicorn
import frida
jsCode = """
Java.perform(function () {
// (the Frida script shown above)
});
"""
process = frida.get_usb_device().attach('com.dodonew.online')
script = process.create_script(jsCode)
script.load()
app = FastAPI()
@app.get("/getencrypt")
async def getencrypt(username: str, password: str, timestamp: str):
result = script.exports.encrypt(username, password, timestamp)
return {"data": result}
class Item(BaseModel):
data: str
@app.post("/getdecode")
async def getdecode(item: Item):
result = script.exports.decode(item.data)
return {"data": result}
if __name__ == "__main__":
uvicorn.run(app, port=8080)Running the service produces the following output:
Constructing Requests
Use the service to obtain encrypted parameters, send them to the real API, then decrypt the response:
import requests, time, json
dt = int(time.time() * 1000)
enc = requests.get(f"http://127.0.0.1:8080/getencrypt?username=18903916120&password=1111×tamp={dt}").json()
payload = {"Encrypt": enc["data"]}
login_resp = requests.post("http://api.dodovip.com/api/user/login", headers={"Content-Type": "application/json;charset=utf-8"}, data=json.dumps(payload))
decoded = requests.post("http://127.0.0.1:8080/getdecode", headers={"Content-Type": "application/json;charset=utf-8"}, data=json.dumps({"data": login_resp.text})).text
print(decoded)Conclusion
The app uses two layers of encryption, but with RPC forwarding only a few lines of code are needed to encrypt requests and decrypt responses. The main limitation is the dependency on a rooted device and a PC, which is acceptable for data‑collection tasks.
Python Crawling & Data Mining
Life's short, I code in Python. This channel shares Python web crawling, data mining, analysis, processing, visualization, automated testing, DevOps, big data, AI, cloud computing, machine learning tools, resources, news, technical articles, tutorial videos and learning materials. Join us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
