本文记录了对某电商数据分析平台 API 响应加密机制的完整逆向过程,涵盖抓包侦察、参数验证、解密定位、算法识别与本地还原。该加密方案采用 AES-ECB + PKCS7 + gzip 的组合模式,密钥通过多层 toString(36) 编码混淆,属于典型的前端响应加密防护,适合作为响应解密类逆向的入门案例。


一、目标概况

项目 说明
目标平台 某电商数据分析平台
页面入口 aHR0cHM6Ly93d3cuY2hhbm1hbWEuY29tL3Byb21vdGlvblJhbmsv
数据接口 aHR0cHM6Ly9hcGktc2VydmljZS5jaGFubWFtYS5jb20vdjYvcHJvZHVjdC9zZWFyY2g=
鉴权方式 请求头固定参数:x-client-hash / x-client-id / x-encrypt-version / x-platform-id
加密方式 响应体加密:AES-ECB + PKCS7 解密 → WordArray 转 Uint8Array → gzip 解压 → JSON
分析日期 2026.07.29

URL 采用 Base64 编码脱敏,解码后可得原始地址。

逆向目标:还原接口响应中密文数据的解密逻辑,使本地可正常获取明文数据。


二、抓包侦察

2.1 捕获目标请求

打开浏览器 DevTools → Network 面板,通过翻页操作触发列表数据请求,定位到 search 接口。

请求头(关键部分):

x-client-hash: <固定哈希值>
x-client-id: <固定ID>
x-client-version: 1
x-encrypt-version: 2
x-platform-id: 10000

请求体(明文 JSON):

{
  "multi_category_id": "-1",
  "keyword": "",
  "page": 2,
  "size": 50,
  "sort": "duration_volume",
  "day_type": 1,
  "platform": "<平台标识>",
  "order_by": "desc"
}

响应体(密文,状态码 200):

在这里插入图片描述

2.2 初步判断

通过观察请求体与响应体,可以得出两个关键结论:

  1. 数据层存在加密:响应体为密文信息,需逆向解密方法才能获取明文数据
  2. 请求层无动态鉴权:请求头中的参数均为固定值,无需逆向生成逻辑

2.3 参数固定性验证

拦截多页请求,对比各参数变化情况:

参数 多次请求对比 结论
x-client-hash 始终相同 固定值(硬编码在代码中)
x-client-id 始终相同 固定值
x-encrypt-version 始终相同 固定值
x-platform-id 始终相同 固定值

在这里插入图片描述

结论:请求头参数均为固定值,无需逆向生成逻辑。核心逆向目标为响应体解密


三、解密定位

3.1 Hook 定位

在 DevTools Sources 面板中,注入 Hook 脚本拦截 JSON.parse 方法的调用,定位解密入口:

在这里插入图片描述

Hook 触发后出现明文信息,根据堆栈调用信息向上追溯,找到解密方法的调用位置。

3.2 断点验证

在解密方法处设置断点,观察变量值:

在这里插入图片描述

此时可以清晰看到完整的解密链路:

密文 e → AES 解密 → WordArray a → u(a) 转换 → Uint8Array o → ungzip 解压 → 明文 r → JSON.parse

四、解密逻辑分析

4.1 源码还原

从断点位置提取的关键代码如下:

// AES 密钥推导
n = s.enc.Utf8.parse(
    27..toString(36).toLowerCase().split("").map(function(t) {
        return String.fromCharCode(t.charCodeAt() + -39);
    }).join("")
    + 24901..toString(36).toLowerCase()
    + 33..toString(36).toLowerCase().split("").map(function(t) {
        return String.fromCharCode(t.charCodeAt() + -39);
    }).join("")
    + 976..toString(36).toLowerCase()
    + 20..toString(36).toLowerCase().split("").map(function(t) {
        return String.fromCharCode(t.charCodeAt() + -39);
    }).join("")
    + (function() {
        var t = Array.prototype.slice.call(arguments), e = t.shift();
        return t.reverse().map(function(t, n) {
            return String.fromCharCode(t - e - 24 - n);
        }).join("");
    })(10, 127, 154, 91, 151, 91, 136)
    + 11..toString(36).toLowerCase()
    + 13..toString(36).toLowerCase().split("").map(function(t) {
        return String.fromCharCode(t.charCodeAt() + -13);
    }).join("")
);

// AES-ECB 解密
a = s.AES.decrypt(e, n, {
    mode: s.mode.ECB,
    padding: s.pad.Pkcs7
});

// WordArray → Uint8Array
o = u(a);

// gzip 解压
r = c.ungzip(o, { to: "string" });

// JSON 解析
return JSON.parse(r);

4.2 逻辑拆解

将上述代码逐步拆解,解密流程如下:

┌─────────────────────────────────────────────────────────┐
│  Step 1: 推导 AES 密钥                                    │
│    key = 多层 toString(36) 编码 + charCode 偏移拼接        │
│    最终结果: 16字节字符串 (AES-128)                         │
├─────────────────────────────────────────────────────────┤
│  Step 2: AES-ECB 解密                                     │
│    plaintext = AES.decrypt(ciphertext, key, {             │
│        mode: ECB, padding: PKCS7                          │
│    })                                                     │
│    // 输出为 CryptoJS WordArray 对象                       │
├─────────────────────────────────────────────────────────┤
│  Step 3: WordArray → Uint8Array                           │
│    bytes = u(wordArray)                                   │
│    // 逐字提取 4 字节,按大端序拆分                         │
├─────────────────────────────────────────────────────────┤
│  Step 4: gzip 解压                                        │
│    jsonStr = pako.ungzip(bytes, { to: "string" })         │
├─────────────────────────────────────────────────────────┤
│  Step 5: JSON 解析                                        │
│    data = JSON.parse(jsonStr)                             │
└─────────────────────────────────────────────────────────┘

4.3 子函数验证

密钥推导:将混淆代码在 Node.js 中执行,得到 AES 密钥为 16 字节字符串,对应 AES-128。

u 方法(WordArray → Uint8Array 转换):

function u(t) {
    var e, n, i = t.words.length, a = new Uint8Array(t.sigBytes), o = 0;
    for (n = 0; n < i; n++)
        e = t.words[n],
        a[o++] = e >> 24,
        a[o++] = e >> 16 & 255,
        a[o++] = e >> 8 & 255,
        a[o++] = 255 & e;
    return a;
}

该函数将 CryptoJS 的 WordArray(32 位整数数组)按大端序逐字节拆分为 Uint8Array,属于标准转换操作。

AES 解密验证:使用推导出的密钥对密文进行 AES-ECB + PKCS7 解密,解密后的数据经 u 方法转换后为 gzip 压缩的二进制数据,符合预期。

gzip 解压验证:使用 pako 库对解密后的 Uint8Array 进行解压,得到明文 JSON 字符串,与浏览器中观察到的明文数据完全一致。


五、本地还原

5.1 Node.js 实现

const CryptoJS = require('crypto-js');
const pako = require('pako');

function u(t) {
    var e, n, i = t.words.length, a = new Uint8Array(t.sigBytes), o = 0;
    for (n = 0; n < i; n++)
        e = t.words[n],
        a[o++] = e >> 24,
        a[o++] = e >> 16 & 255,
        a[o++] = e >> 8 & 255,
        a[o++] = 255 & e;
    return a;
}

function decryptResponse(ciphertext) {
    const key = CryptoJS.enc.Utf8.parse('<推导出的AES密钥>');
    const decrypted = CryptoJS.AES.decrypt(ciphertext, key, {
        mode: CryptoJS.mode.ECB,
        padding: CryptoJS.pad.Pkcs7
    });
    const bytes = u(decrypted);
    const jsonStr = pako.ungzip(bytes, { to: 'string' });
    return JSON.parse(jsonStr);
}

const ciphertext = '...'; // 接口返回的密文
console.log(decryptResponse(ciphertext));

5.2 Python 实现

from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import gzip
import json
import base64

def decrypt_response(ciphertext_b64):
    key = b'<推导出的AES密钥>'
    ciphertext = base64.b64decode(ciphertext_b64)
    cipher = AES.new(key, AES.MODE_ECB)
    plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size)
    json_str = gzip.decompress(plaintext).decode('utf-8')
    return json.loads(json_str)

ciphertext = '...'  # 接口返回的密文(Base64编码)
print(decrypt_response(ciphertext))

5.3 验证结果

通过本地还原的解密方法,能够正常解密接口返回的密文信息,得到明文数据:

在这里插入图片描述


六、总结

加密方案特征

维度 分析
算法类型 标准 AES-128-ECB + PKCS7(无魔改),配合 gzip 压缩
密钥来源 前端 JS 中通过 toString(36) + charCode 偏移多层混淆,但可本地执行还原
防护强度 较低——密钥推导逻辑硬编码在前端,ECB 模式无 IV,算法均为标准实现
压缩层 gzip 压缩(服务端加密前压缩,客户端解密后解压)
请求鉴权 无动态鉴权参数,请求头均为固定值

逆向思路回顾

抓包侦察 → 参数验证(固定性)→ Hook JSON.parse 定位 → 断点确认解密链路
→ 源码提取 → 密钥推导还原 → 子函数验证(u方法/ungzip)→ 本地还原 → 一致性验证

本案例属于前端响应加密的入门级难度,核心难点在于定位解密入口和还原混淆的密钥推导逻辑。解密链路清晰(AES → WordArray转换 → gzip解压 → JSON),算法均为标准实现,还原过程非常直接。


免责声明:本文仅用于技术学习与研究目的,请勿将相关技术用于非法用途。数据采集行为应遵守《网络安全法》等相关法律法规,尊重网站的服务条款和 robots.txt 协议。

Logo

电商企业物流数字化转型必备!快递鸟 API 接口,72 小时快速完成物流系统集成。全流程实战1V1指导,营造开放的API技术生态圈。

更多推荐