乐跑网课神器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"encryptAES等。你很可能会找到一个加密工具类,里面有类似这样的代码:

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());
    }
}

复制

问题来了:KEYIV的值可能是从C++层的.so库中获取的,或者是通过复杂的算法动态生成的。在Jadx的Java代码层面,你可能根本找不到它们。

这时,静态分析就遇到了瓶颈。我们需要一种方法,在APP运行时“潜入”其中,直接看看它调用encrypt函数时,传入的明文和返回的密文分别是什么。这就是Frida大显身手的时候。

第二步:Frida登场 - Hook关键加密函数

Frida是一个强大的动态插桩(Hook)工具,可以让你用JavaScript来与运行中的APP进行交互。

1. 环境准备:

  • PC端安装Fridapip 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出KeyIV,但假设我们没找到,而是通过其他方式(比如从.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等库,我们将观察到的逻辑转化为可执行的代码,从而彻底掌握了与服务器对话的能力。

这个过程充满了挑战,尤其是在寻找KeyIV的环节,它们可能被隐藏在任何角落。但每攻克一个难点,都意味着你对逆向工程的理解又深入了一层。

如果你对Frida的使用有疑问,或者在某个APP的AES加密分析中卡住了,不妨把你的思路分享出来。也许你遇到的问题,正是别人刚刚解决的。欢迎在我的主页留言交流。

 

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐