首页
社区
课程
招聘
[原创] 某书 X-s 签名逆向分析(含风控点)-有了 AI人人都能还原VMP
发表于: 3天前 1461

[原创] 某书 X-s 签名逆向分析(含风控点)-有了 AI人人都能还原VMP

3天前
1461

首先抓包分析 /api/sns/web/v1/homefeed 接口,发现请求头中有两个关键签名参数:

全局搜索 X-s,定位到签名生成位置:

跟进 seccore_signv2

签名流程很清晰:url + body → MD5 → mnsv2 加密 → JSON 包装 → 自定义 Base64

核心就是 window.mnsv2

这是最难的一步。window.mnsv2 只是个入口,实际执行的解释器藏在别处。

VM 的典型特征

我使用自研的逆向 MCP 工具,通过 Hook + 调用栈追踪:

最终定位到 VM 文件:

VM 解释器代码当然是混淆的。使用 Babel AST 配合 AI 进行解混淆:

解混淆后得到可读的解释器代码。

在代码中找到这样的字符串:

这就是 VM 的字节码,通过自定义的 switch 基于栈来执行。

基于栈的 VM 只是一种实现方式,还有基于寄存器的 VM,原理类似。

分析解释器的 switch-case,构建操作码映射:

这一步用 AI 辅助分析非常高效。

有了操作码表,就可以对字节码进行反编译:

这里需要多次动态调试来验证反编译结果的正确性,同样交给 AI 完成。

反编译产出一个大文件,需要分割成独立函数:

有些厂商会在这层再加控制流平坦化,这时需要在浏览器中 trace 执行流程。

最后,结合静态分析和动态调试,还原完整算法。

Base64 表:

JSON 结构:

关键点:mns0301 使用 RC4 + 预置 S-box 加密,服务器持有相同的 S-box,可以完全解密还原原始数据

签名不只是"防篡改",更是"行为数据上报通道"。

回顾 135 字节输入结构,关键字段都是风控相关

服务器解密签名后,可以进行以下判断:

核心机制:行为数据全局累积,每次请求时打包进签名

为什么选这些元素? 都是用户正常浏览必然会经过的区域。如果一个"用户"发了 100 个请求,但从未触碰过这些核心元素 → 爬虫。

服务器通过对比多次请求中的计数器变化来判断:

从反汇编还原的逻辑:

人类点击的物理极限约 50-100ms,77ms 是一个合理的阈值:

对于不使用浏览器、直接发 HTTP 请求的纯协议爬虫,这套风控机制带来了显著挑战:

问题:计数器不能恒定

纯协议必须维护会话状态

这套风控的设计意图

结论:纯协议爬虫必须维护会话状态,模拟计数器的单调递增趋势,这大大增加了爬虫的开发成本。

mns0301 使用 RC4 流密码 + 预置 S-box,整体难度适中,核心在于定位 VM 入口和还原字节码逻辑。

try {
    var _ = "X-s",
        b = "X-t",
        x = getRealUrl(a, c, d),
        p = seccore_signv2;
    p && (r.headers[_] = p(x, s),
          r.headers[b] = +new Date + "")
}
try {
    var _ = "X-s",
        b = "X-t",
        x = getRealUrl(a, c, d),
        p = seccore_signv2;
    p && (r.headers[_] = p(x, s),
          r.headers[b] = +new Date + "")
}
function seccore_signv2(e, r) {
    var a = window.toString,
        c = e;
    // 拼接 url + body
    "[object Object]" === a.call(r) || "[object Array]" === a.call(r) ||
    (void 0 === r ? "undefined" : (0, m._)(r)) === "object" && null !== r
        ? c += JSON.stringify(r)
        : "string" == typeof r && (c += r);
 
    var d = (0, h.Pu)([c].join("")),  // MD5(url + body)
        s = (0, h.Pu)(e),              // MD5(url)
        f = window.mnsv2(c, d, s),     // ★ 核心加密
        l = {
            x0: u.i8,           // 版本号 "4.3.0"
            x1: "xxx-pc-web",   // appId
            x2: window[u.mj] || "PC"// 平台
            x3: f,              // mns 签名
            x4: r ? void 0 === r ? "undefined" : (0, m._)(r) : ""
        };
    return "XYS_" + (0, h.xE)((0, h.lz)(JSON.stringify(l)))
}
function seccore_signv2(e, r) {
    var a = window.toString,
        c = e;
    // 拼接 url + body
    "[object Object]" === a.call(r) || "[object Array]" === a.call(r) ||
    (void 0 === r ? "undefined" : (0, m._)(r)) === "object" && null !== r
        ? c += JSON.stringify(r)
        : "string" == typeof r && (c += r);
 
    var d = (0, h.Pu)([c].join("")),  // MD5(url + body)
        s = (0, h.Pu)(e),              // MD5(url)
        f = window.mnsv2(c, d, s),     // ★ 核心加密
        l = {
            x0: u.i8,           // 版本号 "4.3.0"
            x1: "xxx-pc-web",   // appId
            x2: window[u.mj] || "PC"// 平台
            x3: f,              // mns 签名
            x4: r ? void 0 === r ? "undefined" : (0, m._)(r) : ""
        };
    return "XYS_" + (0, h.xE)((0, h.lz)(JSON.stringify(l)))
}
// 发现 22 个随机命名的全局函数
for (k in window) {
    if (/^_[a-f0-9]{20,}$/.test(k)) console.log(k);
}
 
// Hook 其中一个,观察调用栈
hook_function("window._0c6b9e549fef9ab9b4798ad1f12ea82b", logStack=true)
// 发现 22 个随机命名的全局函数
for (k in window) {
    if (/^_[a-f0-9]{20,}$/.test(k)) console.log(k);
}
 
// Hook 其中一个,观察调用栈
hook_function("window._0c6b9e549fef9ab9b4798ad1f12ea82b", logStack=true)
https://fe-static.xxxcdn.com/.../ds.js
https://fe-static.xxxcdn.com/.../ds.js
var bytecode = "ABt7CAAUSAAACADfSAAACAD1SAAACAAH..."  // 约 6KB
var bytecode = "ABt7CAAUSAAACADfSAAACAD1SAAACAAH..."  // 约 6KB
OpCode 助记符 操作
0x00 PUSH 压栈
0x01 POP 出栈
0x02 ADD 加法
0x03 CALL 调用函数
... ... ...
output/mns0301/functions/
├── build_input.js      # 构建 135 字节输入
├── rc4_encrypt.js      # RC4 加密
├── custom_base64.js    # 自定义 Base64
└── main.js             # 主流程
output/mns0301/functions/
├── build_input.js      # 构建 135 字节输入
├── rc4_encrypt.js      # RC4 加密
├── custom_base64.js    # 自定义 Base64
└── main.js             # 主流程
偏移      大小    字段           说明
─────────────────────────────────────────────────
[0-3]     4B     Header         "xh`)" 魔数
[4-7]     4B     Random         随机数 (little-endian)
[8-15]    8B     Timestamp1     当前时间戳 ms
[16-23]   8B     Timestamp2     页面加载时间戳 ms
[24-27]   4B     Field3         固定值 (15-17)
[28-31]   4B     Field4         计数器
[32-35]   4B     Field5         计数器
[36-43]   8B     Double         随机浮点数
[44-96]   53B    a1             "4" + a1_cookie (53字节)
[97]      1B     分隔符          '\n'
[98-10710B    xsecappid      "xxx-pc-web"
[108]     1B     分隔符          '\x01'
[109-134] 26B    Extra          1随机 + 17固定 + 8随机
─────────────────────────────────────────────────
偏移      大小    字段           说明
─────────────────────────────────────────────────
[0-3]     4B     Header         "xh`)" 魔数
[4-7]     4B     Random         随机数 (little-endian)
[8-15]    8B     Timestamp1     当前时间戳 ms
[16-23]   8B     Timestamp2     页面加载时间戳 ms
[24-27]   4B     Field3         固定值 (15-17)
[28-31]   4B     Field4         计数器
[32-35]   4B     Field5         计数器
[36-43]   8B     Double         随机浮点数
[44-96]   53B    a1             "4" + a1_cookie (53字节)
[97]      1B     分隔符          '\n'
[98-10710B    xsecappid      "xxx-pc-web"
[108]     1B     分隔符          '\x01'
[109-134] 26B    Extra          1随机 + 17固定 + 8随机
─────────────────────────────────────────────────
输入构建 (135B) → RC4 加密 (预置 S-box) → 自定义 Base64 → "mns0301_" + result
输入构建 (135B) → RC4 加密 (预置 S-box) → 自定义 Base64 → "mns0301_" + result
SBOX = [108, 71, 200, 252, 102, 41, 228, 110, 198, 188, 243, 68, ...]
SBOX = [108, 71, 200, 252, 102, 41, 228, 110, 198, 188, 243, 68, ...]
MfgqrsbcyzPQRStuvC7mn501HIJBo2DEFTKdeNOwxWXYZap89+/A4UVLhijkl63G
MfgqrsbcyzPQRStuvC7mn501HIJBo2DEFTKdeNOwxWXYZap89+/A4UVLhijkl63G
ZmserbBoHQtNP+wOcza/LpngG8yJq42KWYj0DSfdikx3VT16IlUAFM97hECvuRX5
ZmserbBoHQtNP+wOcza/LpngG8yJq42KWYj0DSfdikx3VT16IlUAFM97hECvuRX5
┌─────────────────────────────────────────────────────────────┐
│                    风控数据流                                │
├─────────────────────────────────────────────────────────────┤
│  浏览器端                          服务器端                  │
│  ────────                          ────────                  │
│  采集行为数据                                                │
│       ↓                                                      │
│  写入 135B 输入结构                                          │
│       ↓                                                      │
│  RC4 加密 (预置 S-box)                                       │
│       ↓                                                      │
│  Base64 编码 → X-s 签名  ──────→  Base64 解码               │
│                                        ↓                     │
│                                   RC4 解密 (相同 S-box)      │
│                                        ↓                     │
│                                   还原 135B 原始数据         │
│                                        ↓                     │
│                                   分析行为特征 → 风控判定    │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│                    风控数据流                                │
├─────────────────────────────────────────────────────────────┤
│  浏览器端                          服务器端                  │
│  ────────                          ────────                  │
│  采集行为数据                                                │
│       ↓                                                      │
│  写入 135B 输入结构                                          │
│       ↓                                                      │
│  RC4 加密 (预置 S-box)                                       │
│       ↓                                                      │
│  Base64 编码 → X-s 签名  ──────→  Base64 解码               │
│                                        ↓                     │
│                                   RC4 解密 (相同 S-box)      │
│                                        ↓                     │
│                                   还原 135B 原始数据         │
│                                        ↓                     │
│                                   分析行为特征 → 风控判定    │
└─────────────────────────────────────────────────────────────┘
偏移      字段           风控用途
─────────────────────────────────────────────────
[4-7]     Random         请求唯一标识,防重放
[8-15]    Timestamp1     当前时间戳,检测时间异常
[16-23]   Timestamp2     页面加载时间,计算停留时长
[24-27]   Field3         行为标记位
[28-31]   Field4         ★ 点击计数器
[32-35]   Field5         ★ mouseenter 计数器
[36-43]   Double         随机数,增加熵值
─────────────────────────────────────────────────
偏移      字段           风控用途
─────────────────────────────────────────────────
[4-7]     Random         请求唯一标识,防重放
[8-15]    Timestamp1     当前时间戳,检测时间异常
[16-23]   Timestamp2     页面加载时间,计算停留时长
[24-27]   Field3         行为标记位
[28-31]   Field4         ★ 点击计数器
[32-35]   Field5         ★ mouseenter 计数器
[36-43]   Double         随机数,增加熵值
─────────────────────────────────────────────────
检测项 正常用户 爬虫/脚本
页面停留时长 > 几秒 0 或极短
点击计数 有累积 始终为 0
mouseenter 计数 有累积 始终为 0
点击/mouseenter 比例 接近 1:N 异常比例
请求间隔 > 100ms < 77ms 或完全一致
时间戳连续性 递增 跳跃或回退
┌──────────────────────────────────────────────────────────────┐
│                        页面生命周期                           │
├──────────────────────────────────────────────────────────────┤
│  页面加载                                                     │
│      ↓                                                       │
│  VM 初始化,对关键 DOM 元素挂载监听器                          │
│      │                                                       │
│      ├── #search-input      (搜索框,鼠标必经区域)            │
│      ├── #header-area       (头部,几乎必经)                  │
│      ├── #homefeed_recommend (首页推荐,主内容区)             │
│      └── #explore-guide-refresh (刷新按钮)                   │
│      ↓                                                       │
│  用户操作页面 → 计数器累积到全局变量                           │
│      ↓                                                       │
│  发起 API 请求时,签名函数读取计数器写入 X-s                   │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│                        页面生命周期                           │
├──────────────────────────────────────────────────────────────┤
│  页面加载                                                     │
│      ↓                                                       │
│  VM 初始化,对关键 DOM 元素挂载监听器                          │
│      │                                                       │
│      ├── #search-input      (搜索框,鼠标必经区域)            │
│      ├── #header-area       (头部,几乎必经)                  │
│      ├── #homefeed_recommend (首页推荐,主内容区)             │
│      └── #explore-guide-refresh (刷新按钮)                   │
│      ↓                                                       │

[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!

收藏
免费 10
支持
分享
最新回复 (3)
雪    币: 5651
活跃值: (9547)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
看看先
1天前
0
雪    币: 32
活跃值: (782)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
666
11小时前
0
雪    币: 13
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
tql
8小时前
0
游客
登录 | 注册 方可回帖
返回