Bootstrap

【APP_汽修宝】数据采集案例APP_数据解密分析


如果不会写代码,那就出书、写博客、做视频、录播客。
                     📚 S35赛季末王者昭君罗


关键代码定位

动态分析

  • 下面是我们通过访问目标页面时 Frida hook 捕获HashMap的调用堆栈:
    在这里插入图片描述
a: username b: AI爱答题
java.lang.Throwable
        at java.util.HashMap.put(Native Method)
        at org.json.JSONObject.put(JSONObject.java:267)
        at org.json.JSONTokener.readObject(JSONTokener.java:384)
        at org.json.JSONTokener.nextValue(JSONTokener.java:100)
        at com.qp333.car.api.DecodeInterceptor.intercept(DecodeInterceptor.java:86)  ## 解码拦截器
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
        at com.qp333.car.api.ParamsInterceptor.intercept(ParamsInterceptor.java:60)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)

        ...

分析该堆栈后,我们定位到了com.qp333.car.api.DecodeInterceptor.intercept目标代码如下:
在这里插入图片描述
其中通过hookaesDecryptString方法得知,通过此方法可以将http响应中的密文转化成明文
在这里插入图片描述

关键代码分析

这段代码展示了如何在 Java 中使用一些工具和方法来解密 JSON 数据,并将解密后的数据放回到 JSON 对象中。让我们逐步解析这段代码:

jSONObject.put("data", new JSONTokener(
    aesDecryptString(
        jSONObject.getString("data"),
        StringUtils.MD5(
            String.format(
                Locale.getDefault(), 
                "%s%s", 
                UserManager.get().sessionid, 
                str
            )
        ),
        String.format(
            Locale.getDefault(), 
            "%s%s%s%s", 
            str + "000", 
            Character.valueOf(str.charAt(1)), 
            Character.valueOf(str.charAt(3)), 
            Character.valueOf(str.charAt(7))
        )
    )
).nextValue());
获取原始加密数据:
jSONObject.getString("data")

这是从 jSONObject 中获取名为 data 的字段的值,该值是一个加密的字符串。

生成解密密钥:
StringUtils.MD5(
    String.format(
        Locale.getDefault(), 
        "%s%s", 
        UserManager.get().sessionid, 
        str
    )
)

这里生成了一个解密密钥。具体步骤是:

使用String.format方法将 UserManager.get().sessionidstr 拼接成一个字符串,Locale.getDefault() 确保格式化时使用默认的区域设置。
将拼接后的字符串传递给 StringUtils.MD5 方法,计算其 MD5 哈希值。这个哈希值将作为解密的密钥。

生成初始向量(IV):
String.format(
    Locale.getDefault(), 
    "%s%s%s%s", 
    str + "000", 
    Character.valueOf(str.charAt(1)), 
    Character.valueOf(str.charAt(3)), 
    Character.valueOf(str.charAt(7))
)

这里生成了解密过程中的初始向量(IV),具体步骤是:

使用 String.format 方法将 str 的第一个字符加上 “000”、str 的第 1、3、7 个字符拼接成一个字符串。
Locale.getDefault() 确保格式化时使用默认的区域设置。
解密数据:

aesDecryptString(
    jSONObject.getString("data"), 
    StringUtils.MD5(String.format(Locale.getDefault(), "%s%s", UserManager.get().sessionid, str)), 
    String.format(Locale.getDefault(), "%s%s%s%s", str + "000", Character.valueOf(str.charAt(1)), Character.valueOf(str.charAt(3)), Character.valueOf(str.charAt(7)))
)

调用aesDecryptString方法,使用从 jSONObject 获取的加密数据、生成的密钥和 IV 进行解密。aesDecryptString 方法返回解密后的字符串。

解析解密后的 JSON 数据:

new JSONTokener(aesDecryptString(...)).nextValue()

将解密后的字符串传递给 JSONTokener,并调用nextValue方法解析解密后的 JSON 数据。

将解密后的数据放回 JSON 对象中:

jSONObject.put("data", new JSONTokener(...).nextValue())

将解析后的 JSON 数据放回 jSONObject 中 data 字段。

整体流程

这段代码的目的是:

  1. 从 JSON 对象中获取加密的 data 字段。
  2. 生成解密密钥和初始向量(IV)。
  3. 使用这些密钥和 IV 通过 aesDecryptString 方法解密数据。
  4. 将解密后的 JSON 数据解析并放回到原来的 jSONObject 中。

代码重构

import base64
import hashlib

from Crypto.Cipher import AES


def md5_string(s):
    return hashlib.md5(s.encode('utf-8')).hexdigest()


def pad(data):
    block_size = AES.block_size
    pad_len = block_size - (len(data) % block_size)
    return data + chr(pad_len) * pad_len


def unpad(data):
    pad_len = ord(data[-1])
    return data[:-pad_len]


def aes_decrypt(data, key, iv):
    cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
    decrypted = cipher.decrypt(base64.b64decode(data))
    return decrypted.decode('utf-8', 'ignore')


def parse_result(data, session_id, time_str):
    # 生成动态MD5密钥
    md5_key = md5_string(f"{session_id}{time_str}")

    # 生成动态IV
    iv_dynamic = f"{time_str}000{time_str[1]}{time_str[3]}{time_str[7]}"

    print(f"MD5 Key: {md5_key}")
    print(f"Dynamic IV: {iv_dynamic}")

    try:
        decrypted_data = aes_decrypt(data, md5_key, iv_dynamic)
        print(f"Decrypted data (raw): {decrypted_data}")
        return decrypted_data
    except Exception as e:
        print(f"Error during decryption: {e}")
        return None


def main():

    session_id = '请求参数中获取'
    encrypted_message = "加密数据"
    timestamp = "1718554525"  # URL请求发起时间戳

    # 解密
    decrypted_data = parse_result(encrypted_message, session_id, timestamp)
    print("Decrypted Data:", decrypted_data)


if __name__ == "__main__":
    main()
;