不止签名:某校园乐跑APP请求体AES加密逆向全记录 (Frida+Python)
乐跑网课神器zxxwk.online 邀请码8888
前言:当数据本身也成为一道谜题
在上一篇《破解某校园跑步APP的sign签名生成逻辑》中,我们成功攻克了sign签名。但道高一尺,魔高一丈。在一些安全性要求更高的APP中,开发者不仅会校验sign,还会将整个请求体(Request Body)进行加密,让抓包工具捕获到的数据变成一串毫无意义的乱码。
例如,你用Fiddler抓到的POST请求可能是这样的:
复制代码收起
POST https://api.example.com/api/v2/run/save
...
{"data":"aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890/aBcDeFgHiJkLmNoPqRsTuVwXyZ="}
复制
那个长长的data字符串,就是加密后的业务数据。本文将继续以那个熟悉的跑步APP为例,分享如何使用动态调试神器Frida来定位并复现其AES加密算法,最终用Python实现完整的加密数据构造。
第一步:静态分析的极限与动态调试的必要性
按照常规思路,我们依然可以使用Jadx反编译APK,然后搜索关键词,如"data"、encrypt、AES等。你很可能会找到一个加密工具类,里面有类似这样的代码:
java复制代码收起
// 模拟的Java加密代码
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class CryptoUtil {
private static final String KEY = "???"; // 密钥
private static final String IV = "???"; // 初始化向量
public static byte[] encrypt(String plainText) {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), "AES");
IvParameterSpec ivSpec = new IvParameterSpec(IV.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
return cipher.doFinal(plainText.getBytes());
}
}
复制
问题来了:KEY和IV的值可能是从C++层的.so库中获取的,或者是通过复杂的算法动态生成的。在Jadx的Java代码层面,你可能根本找不到它们。
这时,静态分析就遇到了瓶颈。我们需要一种方法,在APP运行时“潜入”其中,直接看看它调用encrypt函数时,传入的明文和返回的密文分别是什么。这就是Frida大显身手的时候。
第二步:Frida登场 - Hook关键加密函数
Frida是一个强大的动态插桩(Hook)工具,可以让你用JavaScript来与运行中的APP进行交互。
1. 环境准备:
- PC端安装
Frida:pip install frida-tools - 手机端(需Root)运行
frida-server。
2. 编写Hook脚本 (hook.js)
我们的目标是Hook住上面找到的CryptoUtil.encrypt方法。
javascript复制代码收起
// hook.js
Java.perform(function() {
console.log("Frida script loaded and running...");
// 定位到我们要Hook的类和方法
const CryptoUtil = Java.use("com.example.app.utils.CryptoUtil");
// Hook 'encrypt' 方法
CryptoUtil.encrypt.implementation = function(plainText) {
console.log("\n[+] Hooked encrypt function!");
console.log(" [->] Input (明文): " + plainText);
// 调用原始的加密方法
const result = this.encrypt(plainText);
// 将返回的 byte[] 转换为 Base64 字符串以便打印
const Base64 = Java.use("android.util.Base64");
const encodedResult = Base64.encodeToString(result, 2); // 2 = NO_WRAP
console.log(" [<-] Output (密文Base64): " + encodedResult);
// 如果想找到 Key 和 IV, 可以在这里进一步Hook Cipher.init()
// const Cipher = Java.use("javax.crypto.Cipher");
// Cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function(opmode, key, spec) {
// console.log("Hooked Cipher.init! Found Key and IV!");
// // 在这里打印key和iv的字节
// ...
// }
return result;
};
});
复制
3. 运行Frida:
在PC命令行中执行:frida -U -l hook.js -f com.example.app.package.name --no-pause
这条命令会自动启动APP并注入我们的脚本。现在,当你在手机上操作APP,触发加密行为时,你的PC命令行就会实时打印出加密前后的数据!
通过这种方式,我们不仅验证了加密函数的位置,更重要的是,我们拿到了加密前的明文JSON和加密后的密文Base64。这两组成对的数据,是后续我们在Python中进行验证的“标准答案”。
第三步:Python复现 - pycryptodome实战
虽然通过Frida我们可以进一步Hook出Key和IV,但假设我们没找到,而是通过其他方式(比如从.so文件里硬抠,或者是一些固定字符串)得知了它们。现在,我们需要用Python来验证我们的发现。
python复制代码收起
# pip install pycryptodome
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64
# --- 假设我们通过某种方式获取了Key和IV ---
# 这通常是整个逆向过程中最困难的一步
# 它们可能是16、24或32字节的字符串
AES_KEY = b'this_is_a_secret_key_123456' # 16字节的Key (AES-128)
AES_IV = b'1234567890abcdef' # 16字节的IV
def aes_encrypt(plain_text: str) -> str:
"""
使用AES/CBC/PKCS7模式加密文本,并返回Base64编码的字符串。
"""
# 1. 创建加密器
cipher = AES.new(AES_KEY, AES.MODE_CBC, AES_IV)
# 2. 将明文编码为bytes,并进行填充(PKCS7)
plain_bytes = plain_text.encode('utf-8')
padded_bytes = pad(plain_bytes, AES.block_size)
# 3. 执行加密
encrypted_bytes = cipher.encrypt(padded_bytes)
# 4. 将加密后的bytes进行Base64编码,返回字符串
encrypted_base64_str = base64.b64encode(encrypted_bytes).decode('utf-8')
return encrypted_base64_str
# --- 使用示例 ---
if __name__ == '__main__':
# 这个明文JSON是我们用Frida抓到的
clear_json_data = '{"distance":2516,"runTime":900,"points":"..."}'
# 用Python进行加密
python_encrypted_data = aes_encrypt(clear_json_data)
print(f"Python计算出的密文: {python_encrypted_data}")
# 这个密文是我们用Frida抓到的,用作比对
frida_captured_data = "aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890/aBcDeFgHiJkLmNoPqRsTuVwXyZ="
print(f"Frida捕获到的密文: {frida_captured_data}")
if python_encrypted_data == frida_captured_data:
print("\n[SUCCESS] 恭喜!Python复现成功!")
else:
print("\n[FAILED] 失败了...请检查Key, IV, 编码或填充方式。")
复制
总结:从观察者到掌控者
从sign签名到请求体加密,APP的安全防护层层递进。Jadx的静态分析为我们指明方向,而Frida的动态Hook则让我们能直抵核心,绕过代码混淆和动态加载的障碍,成为一个“上帝视角”的观察者。
最终,通过Python的pycryptodome等库,我们将观察到的逻辑转化为可执行的代码,从而彻底掌握了与服务器对话的能力。
这个过程充满了挑战,尤其是在寻找Key和IV的环节,它们可能被隐藏在任何角落。但每攻克一个难点,都意味着你对逆向工程的理解又深入了一层。
如果你对Frida的使用有疑问,或者在某个APP的AES加密分析中卡住了,不妨把你的思路分享出来。也许你遇到的问题,正是别人刚刚解决的。欢迎在我的主页留言交流。
更多推荐



所有评论(0)