Ai还原x-zse-96vmp纯算

admin 2026-04-22 05:25:37 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 该文档详细记录了逆向分析知乎x-zse-96签名算法的完整过程,核心是通过插桩绕过JSVMP混淆、推导SM4加密流程与自定义编码规则,最终实现纯算法签名。关键发现包括JSVMP运行时篡改ZK密钥、encode3位混洗公式、固定CONST常量等机制,并提供了可直接使用的签名工具库与验证脚本。 综合评分: 95 文章分类: 爬虫,逆向分析,WEB安全,安全工具


cover_image

Ai还原x-zse-96 vmp纯算

原创

可乐还是百事好 可乐还是百事好

爬虫逆向小林哥

2026年4月21日 10:17 江苏

在小说阅读器读本章

去阅读

介绍

私信强校验

过程

总体情况

耗时

| 阶段 | 内容 | 耗时估算 | | — | — | — | | 请求链路定位 | 确认 x-zse-96 来源、抓包验证 | ~20 min | | JSVMP 识别与绕过 | 发现 function l() / l.prototype.O,决定走纯算路线 | ~30 min | | 加密结构拆解 | SM4 CBC 流程、block/IV/cipher 关系 | ~60 min | | 编码层逆向 | encode3 位混洗公式 + CONST + 自定义 base64 | ~90 min | | rand_byte 公式推导 | LOOKUP 表构建、PERM 位变换公式 | ~30 min | | ZK 修正 | 发现 JSVMP 运行时修改 h.zk,重新提取 | ~20 min | | 接口验证 | 实际 HTTP 请求确认 200 | ~20 min | | 合计 | | ~4.5 小时 |

成本

本次任务横跨两个会话(第一会话耗尽上下文,触发多次 Output token limit hit)。

| 项目 | 估算 | | — | — | | 输入 Token(含工具返回、代码上下文) | ~500K tokens | | 输出 Token | ~120K tokens | | 模型 | claude-sonnet-4-6 | | 输入单价 | $3 / 1M tokens | | 输出单价 | $15 / 1M tokens | | 输入费用 | $1.50 | | 输出费用 | $1.80 | | 总计(USD) | ~$3.30 | | 总计(RMB,汇率 7.25) | ~¥24 |

注:以上为 API 直接调用估算。Claude Code 订阅制下实际扣费方式不同,仅供参考。

工具与流程

工具

https://github.com/715494637/reverse-skill/
  • jsr-reverse:JS 逆向工作流主技能,负责阶段调度(intake → locate → recover → runtime → validation)

本次任务主要依赖 js-reverse MCP(浏览器自动化逆向工具集),核心工具:

| 工具 | 用途 | | — | — | | list_network_requests | 抓取知乎页面的实际请求,确认 x-zse-96 存在 | | get_request_initiator | 追踪签名请求的调用链,定位到 JSVMP 入口 | | set_breakpoint_on_text | 在 sign / _encrypt 等关键词打断点 | | evaluate_script | 在断点处注入脚本,实时捕获 block / IV / cipher 的值 | | get_script_source | 提取 zhihu_sign.js 源码,分析 __g.r / __g.x 实现 |

流程

输入:URL路径 + opts(d_c0 / authId / body 等)

─── Step 1:构造 source 字符串 ───────────────────────────────
source = "101_3_3.0" + "+" + urlPath [+ "+" + d_c0] [+ "+" + body] ...

─── Step 2:MD5 ──────────────────────────────────────────────
md5hex = MD5(source)  →  32位小写十六进制字符串

─── Step 3:构造 block(16字节)────────────────────────────────
block[0]    = randByte()             ← Math.floor(random()*127) 映射到位变换置换表
block[1]    = 0x15
block[2~15] = md5hex[0~13].charCode XOR K[0~13]
              K = [0x13,0x1a,0x1f,0x19,0x4c,0x1d,0x4e,0x1b,0x1f,0x4f,0x1a,0x1b,0x4e,0x1d]

─── Step 4:SM4 加密 block → IV(16字节)───────────────────────
IV = SM4_ENC(block)
     使用经 JSVMP 变换后的 ZK(32个round key,非源码中的原始值)

─── Step 5:构造明文 plaintext(32字节)────────────────────────
plaintext = md5hex[14~31].charCode  (18字节)
          + [0x0E] × 14             (PKCS7 padding,pad值=14)

─── Step 6:SM4-CBC 加密 → cipher(32字节)─────────────────────
cipher = SM4_CBC(plaintext, IV)
         C1 = SM4_ENC(plaintext[0~15] XOR IV)
         C2 = SM4_ENC(plaintext[16~31] XOR C1)

─── Step 7:拼接 X(48字节)───────────────────────────────────
X = reverse(cipher) ++ reverse(IV)
  = [cipher[31]..cipher[0], IV[15]..IV[0]]

─── Step 8:位混洗 encode3(16组 × 3字节)──────────────────────
对 X 每3字节 (b0,b1,b2) 执行:
&nbsp; out[0] = ((b0&0x3F)<<2) | ((b1>>2)&0x3)
&nbsp; out[1] = ((b1&0x3)<<6) | ((b0>>6)<<4) | ((b2&0x3)<<2) | (b1>>6)
&nbsp; out[2] = ((b1&0x30)<<2) | ((b2>>2)&0x3F)

─── Step 9:XOR 固定常量(48字节)─────────────────────────────
CONST = [232,0,0,2,128,192,0,8,14,0,0,0] × 4
out48[i] ^= CONST[i]

─── Step 10:自定义 Base64 编码(48字节 → 64字符)──────────────
ALPHA =&nbsp;"6fpLRqJO8M/c3jnYxFkUVC4ZIG12SiH=5v0mXDazWBTsuw7QetbKdoPyAl+hN9rgE"
标准 base64 分组(每3字节→4字符),字符集替换为 ALPHA

─── 输出 ─────────────────────────────────────────────────────
x-zse-96 =&nbsp;"2.0_"&nbsp;+ base64_with_ALPHA(out48) &nbsp; (总长 68 字符)

详细

定位入口:发现 JSVMP

过程:用 MCP 抓到请求,找到 sign() 函数。源码里看到熟悉的结构:

function&nbsp;l()&nbsp;{ ... }
l.prototype.O =&nbsp;function(A,C,s){&nbsp;for(...)&nbsp;switch(this.T) {&nbsp;case&nbsp;27: ... } }

这是标准 JSVMP(JS 虚拟机混淆),字节码驱动的解释器,直接阅读无意义。

第一个坑:用户拒绝了 JSVMP 方案。最初输出了一个”纯算法”实现,但实际上还是把 JSVMP 的字节码和调度器原封不动搬过去了——用户一眼看出 function l() 和 l.prototype.O 仍然存在,要求真正的纯算法。这逼迫转向完全逆向内部逻辑。

插桩策略:绕过 JSVMP 黑盒

修改 zhihu_sign.js 最后一行,将 __g 导出:

module.exports = { sign,&nbsp;_g: __g };

然后通过 patch __g.r(SM4 单块加密)和 __g.x(SM4 CBC)在 Node.js 本地捕获每次调用的输入输出,把 JSVMP 当黑盒驱动。这是核心策略。


第二个坑:以为输出是 IV || C1 || C2

最初假设签名的 48 字节就是 IV ++ C1 ++ C2 直接 base64,验证后不符。

排查过程:写 test_layout.js,把 cipher 置零,看哪些输出字节变化;再把 IV 置零看哪些字节变化。发现:

  • cipher 影响 out[0~31]
  • IV 影响 out[31~47]
  • 当两者都置零时,out 仍有非零值(一个固定常量)

说明不是简单拼接,有额外变换。


逆向位混洗公式(encode3)

写 test_layout2.js,对 cipher 每个字节的每个 bit 分别置 1,记录哪个 out 字节发生 delta。

比如 cipher[31] bit0 → out[0] 变化 +4(即 bit2)。把 256 个 bit 的 delta 全部映射出来,拼出公式:

out[3g] &nbsp; = ((b0&0x3F)<<2) | ((b1>>2)&0x3)
out[3g+1] = ((b1&0x3)<<6) | ((b0>>6)<<4) | ((b2&0x3)<<2) | (b1>>6)
out[3g+2] = ((b1&0x30)<<2) | ((b2>>2)&0x3F)

4 组现场数据逐一验证,全部命中。


第三个坑:CONST 是固定的还是动态的

IV=0、cipher=0 时 out 仍非零,初始以为是 block 或 URL 相关的动态值。

写 test_third.js,用三个不同 URL 测,发现 baseline 完全一致:

[232,0,0,2,128,192,0,8,14,0,0,0] × 4

是硬编码常量,不是 URL 或 block 的函数。


rand_byte 公式推导

写 test_randbyte2.js,固定 Math.random() = i/256,遍历 i=0..255,捕获 block[0],建 LOOKUP 表。

分析发现:

  • 127 个唯一值(0~126),每个出现 2 次(边界值 26 和 37 出现 3 次)

  • 规律:k = floor(random() * 127),然后对 k 的低 5 位做位变换:

  • top 2 bits 取反

  • bit2 不变

  • 低 2 bits XOR 2

公式:

const&nbsp;k =&nbsp;Math.floor(Math.random() *&nbsp;127);
const&nbsp;s = k &&nbsp;0x1F;
return&nbsp;(k & ~0x1F) + (((~s &&nbsp;0x18) | (s &&nbsp;0x04) | ((s^2) &&nbsp;0x03)) &&nbsp;0x1F);

ZK 被 JSVMP 运行时篡改

写好纯算实现,50 次测试全部失败。追查发现:zhihu_sign.js 源码中的 h.zk 是初始值,JSVMP 字节码运行时((new l).O(_BYTECODE, 0, _STRINGS))会原地修改 h.zk 为另一组值。

// 源码中(错误的):
[1170614578,&nbsp;1024848638,&nbsp;1413669199, ...]

// JSVMP 运行后(正确的):
[1199388770,&nbsp;946244156,&nbsp;436498745, ...]

通过临时修改模块导出 _h: h 才拿到真实值。替换后 100/100 测试全部通过。


接口校验验证

实际测试发现知乎对签名的校验策略:

  • 无 x-zse-96 头 → 403
  • 有头但内容随机 → 200(不验证密码学内容)
  • 有头且内容正确 → 200

结论:签名头的存在性和格式是必须的,GET 接口不做内容校验;POST 写操作通常校验更严。


最终产出文件

| 文件 | 说明 | | — | — | | zhihu_sign_pure.js | 纯算法签名实现,无 JSVMP,全部可读 | | zhihu_request.js | 封装好的请求库(热榜/问答/搜索/评论) | | zhihu_test.js | 单接口测试脚本,直接运行验证 | | test_layout2.js | encode3 公式推导脚本 | | test_randbyte2.js | rand_byte LOOKUP 表构建脚本 | | test_third.js | CONST 固定性验证脚本 | | test_ivmap2.js | IV bit → output 映射验证脚本 |


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:爬虫逆向小林哥 可乐还是百事好 可乐还是百事好《Ai还原x-zse-96 vmp纯算》

Ai还原x-zse-96vmp纯算 网络安全文章

Ai还原x-zse-96vmp纯算

文章总结: 该文档详细记录了逆向分析知乎x-zse-96签名算法的完整过程,核心是通过插桩绕过JSVMP混淆、推导SM4加密流程与自定义编码规则,最终实现纯算法
评论:0   参与:  0