本文为 Android 逆向学习笔记,仅用于技术研究与交流。
一、抓包分析
打开 APP 点击登录,抓取数据包

发现三个需要逆向的参数:
参数 | 用途 | |
| 密码加密 | |
| 签名校验 | |
| 设备标识 |
二、密码加密逆向(MD5)
1、在 jadx 中全局搜索接口 URL 路径,找到调用位置

2、右键 → 查看用例,追踪到加密调用

3、确认密码通过 SecurityUtil.encodeMD5() 进行 MD5 加密

Frida Hook 验证:
import fridaimport sysrdev = frida.get_remote_device()session = rdev.attach("xx赢+")scr = """Java.perform(function () {var SecurityUtil = Java.use("com.example.utils.SecurityUtil");SecurityUtil.encodeMD5.implementation = function(str){console.log("传入的参数:", str);var res = this.encodeMD5(str);console.log("返回值:", res);return str;}});"""script = session.create_script(scr)def on_message(message, data):print(message, data)script.on("message", on_message)script.load()sys.stdin.read()
运行流程:打开 APP → 运行 Python 脚本 → 点击登录 → 查看控制台输出


✅ 确认:
pwd参数就是SecurityUtil.encodeMD5(明文密码)的结果。
三、签名 sign 逆向
回顾请求数据包中有 _sign 参数:

1、在 jadx 中全局搜索"_sign"

2、发现 toSign() 方法

3、第一次 Hook 没有输出,说明未走此路径

4、换个思路:搜索 KEY_SIGN 常量

5、查看用例,发现 SignManager.INSTANCE.signByType() 方法


6、构造 Frida 脚本 Hook 此方法
import fridaimport sysrdev = frida.get_remote_device()session = rdev.attach("xx赢+")scr = """Java.perform(function () {var SignManager = Java.use("com.example.base.SignManager");SignManager.signByType.implementation = function(signType, paramMap){console.log("传入参数 : ");console.log(signType);console.log(paramMap);var res = this.signByType(signType, paramMap);console.log("sign签名结果 : ", res);return res;}});"""script = session.create_script(scr)def on_message(message, data):print(message, data)script.on("message", on_message)script.load()sys.stdin.read()


✅ 确认:sign 一致!生成方法就是
SignManager.signByType()。
算法还原:
public final String signByType(int signType, TreeMap<String, String> paramMap) {StringBuilder sb = new StringBuilder();String str = KEY_V1;if (signType != 0) {if (signType == 1) {str = KEY_V2; // ← 签名密钥} else if (signType == 2) {str = KEY_SHARE;} else if (signType == 3) {str = KEY_OTHER;}}sb.append(str);for (String str2 : paramMap.keySet()) {sb.append(str2);sb.append(paramMap.get(str2));}sb.append(str);String strEncodeMD5 = SecurityUtil.encodeMD5(sb.toString());return strEncodeMD5.toUpperCase(); // 转大写}
加密逻辑:
签名原文 = KEY + (key1+value1) + (key2+value2) + ... + KEYsign = MD5(签名原文).toUpperCase()
示例输入 TreeMap:
{"_appid": "example.android","appbuildcode": "512","appchannelid": "test","appversion": "5.6.1","channelid": "test","pwd": "e10adc3949ba59abbe56e057f20f883e","signkey": "","type": "","udid": "b4WGOFLDuw2JZhJVayDt6bhHTCuC5HWTBX71PpssDLzYBiAIOrx2mnTOglOd n8eaG8vIDFfhpNo=","username": "13888888888"}
验证结果与 Frida 输出一致:


四、udid 逆向(3DES + JNI)
Step 1:定位 Java 层加密方法
全局搜索 "udid":

选择 AppUtils.getUDID() 方法:

Step 2:Frida Hook 验证
import fridaimport sysrdev = frida.get_remote_device()session = rdev.attach("xx赢+")scr = """Java.perform(function () {var AppUtils = Java.use("com.example.util.AppUtils");AppUtils.getUDID.implementation = function(context){console.log("传入参数 : ");console.log(context);var res = this.getUDID(context);console.log("udid结果 : ", res);return res;}});"""script = session.create_script(scr)def on_message(message, data):print(message, data)script.on("message", on_message)script.load()sys.stdin.read()
结果对比:
b4WGOFLDuw2JZhJVayDt6bhHTCuC5HWTBX71PpssDLz9PKzbxJxICuxxYm9e Kr9e+DRuHBq6bM9LwqKSlbHi4Q== | |
b4WGOFLDuw2JZhJVayDt6bhHTCuC5HWTBX71PpssDLz9PKzbxJxICuxxYm9e%20Kr9e%2BDRuHBq6bM9LwqKSlbHi4Q%3D%3D |
✅ URL 编码后一致,确认加密方式。
Step 3:追踪加密算法
public static String getUDID(Context context) {return SecurityUtil.encode3Des(context,getIMEI(context)+ "|"+ System.nanoTime()+ "|"+ SPUtils.getDeviceId());}
加密参数配置:
private static final String iv = "******"; // 加密向量// 加密算法: DESede/CBC/PKCS5Padding
Step 4:Hook 3DES 方法获取明文
import fridaimport sysrdev = frida.get_remote_device()session = rdev.attach("xx赢+")scr = """Java.perform(function () {var SecurityUtil = Java.use("com.example.utils.SecurityUtil");SecurityUtil.encode3Des.implementation = function(context, str){console.log("传入参数 : ");console.log(context);console.log("3DES明文:", str);var res = this.encode3Des(context, str);console.log("3DES密文:", res);return res;}});"""script = session.create_script(scr)def on_message(message, data):print(message, data)script.on("message", on_message)script.load()sys.stdin.read()
多次触发输出:
3DES明文: 5f16248c-6aa8-3215-b582-32c39930b603|25623394644804|5087153DES密文: b4WGOFLDuw2JZhJVayDt6bhHTCuC5HWTBX71PpssDLym0FRcrI3OuK3+GRfm 6Q2KLw4sb0Y5JgrKXJnaPfGERg==3DES明文: 5f16248c-6aa8-3215-b582-32c39930b603|25639844499464|5087153DES密文: b4WGOFLDuw2JZhJVayDt6bhHTCuC5HWTBX71PpssDLym0FRcrI3OuBJT3U+H 6fEP7scjf0jTdWKMcDtAo8tNMA==
明文结构分析:
{IMEI}|{nanoTime}|{deviceId}UUID | 变化的纳秒 | 固定值
其中 System.nanoTime() 返回设备运行时间(纳秒级),每次调用值都会变化。
Step 5:追踪密钥 → JNI 逆向
// 密钥获取链路String desKey = AHAPIHelper.getDesKey(context);→ getSignDesKey(context);→ CheckSignUtil.get3desKey(context); // native 方法
get3desKey带有native标识,属于 JNI 开发,密钥在 SO 层。

打开 APK 资源文件 lib/ 目录,找到 libnative-lib.so:


使用 IDA 分析 SO 文件:
包名 com.example.jni,类名 CheckSignUtil,在 IDA 的 Export 中搜索:
java_com_example_jni_CheckSignUtil
确认为静态注册:

按 F5 反编译为 C 伪代码:

追踪到 DES3_KEY 变量:


🎯 找到 DES Key:
appapiche168comappapiche168co
在线验证 3DES 加密:

✅ 加密结果一致,密钥确认正确。
五、完整登录脚本
将以上逆向成果汇总为 Python 脚本:
import hashlibfrom Crypto.Cipher import DES3from Crypto.Util.Padding import padimport base64import requestsimport timeimport uuiddef nanoTime():CUSTOM_INIT_NS = 38418185978939current_real_ns = int(time.perf_counter() * 10 ** 9)offset = current_real_ns - CUSTOM_INIT_NSresult = int(time.perf_counter() * 10**9 - offset)return resultdef pwdEncrypt(pwd):md5 = hashlib.md5()md5.update(pwd.encode("utf-8"))return md5.hexdigest().upper()def getSign(dict):md5 = hashlib.md5()key = "W@oC!AH_6Ew1f6%8"str1 = key + "".join(["{}{}".format(key, dict[key]) for key in sorted(dict.keys())]) + keymd5.update(str1.encode("utf-8"))return md5.hexdigest().upper()def getUdid():str1 = f"{str(uuid.uuid4())}|{nanoTime()}|508715"DES3_KEY = "appapiche168comappapiche"DES3_IV = "appapich"MODE = DES3.MODE_CBCBLOCK_SIZE = DES3.block_sizekey_bytes = DES3_KEY.encode("utf-8")iv_bytes = DES3_IV.encode("utf-8")plain_bytes = str1.encode("utf-8")cipher = DES3.new(key_bytes, MODE, iv=iv_bytes)padded_data = pad(plain_bytes, BLOCK_SIZE)encrypted_bytes = cipher.encrypt(padded_data)base64_str = base64.b64encode(encrypted_bytes).decode("utf-8")wrapped_str = base64_str[:60] + " " + base64_str[60:]return wrapped_strdef login(uname, pwd):udid = getUdid()enpwd = pwdEncrypt(pwd)dict = {"_appid": "atc.android","appversion": "3.17.0","channelid": "csy","pwd": enpwd,"udid": udid,"username": uname}sign = getSign(dict)burp0_url = "https://dealercloudapi.xxx.com:443/tradercloud/sealed/login/login.ashx"burp0_headers = {"cache-control": "public, max-age=0","traceid": "atc.android_2ec446fa-9068-4baf-8ddc-a5c411b96570","content-type": "application/x-www-form-urlencoded","accept-encoding": "gzip","user-agent": "okhttp/3.14.9"}dict["_sign"] = signproxy = {"https": "127.0.0.1:8080", "http": "127.0.0.1:8080"}respon = requests.post(burp0_url, headers=burp0_headers, data=dict, proxies=proxy, verify=False)return respon.json()if __name__ == '__main__':res = login("13888888888", "123456")print(res)
总结
pwd | ||
_sign | ||
udid |
通过 Frida Hook 快速定位加密入口,结合 IDA 分析 SO 层密钥,最终实现完整的登录协议逆向。这是 Android 逆向中典型的"Java 层定位 + Native 层分析"流程。
夜雨聆风