B9ph0met/px-vm

GitHub: B9ph0met/px-vm

对 PerimeterX 字节码虚拟机进行逆向工程分析,揭示其五层加密、107 个操作码及反分析技术的安全研究项目。

Stars: 26 | Forks: 7

# PerimeterX Auditor 虚拟机分析 ## 摘要 本仓库记录了对 PerimeterX 的 `auditor.js` 进行逆向工程的结果,这是一个字节码虚拟机,用作 PX 机器人检测管道中的辅助指纹识别层。本次分析涵盖: - 字节码提取与 5 层解密流程 - 操作码表重建(107 个基础 + 40 个蜜罐 + 24 个填充 + 16 个超级指令组) - 常量池解密(1230 条目,1095 个加密字符串) - 基于 CFG 的反汇编器,支持超级指令子调度解析 - 栈模拟清理器,生成可读的伪代码 - 反分析技术:蜜罐操作码、重叠指令、代码完整性哈希 ## 背景 2026 年 4 月 2 日(周四),PerimeterX 在其机器人检测管道中部署了新的字节码虚拟机。 ## 内容说明 `auditor.js` 看起来不像普通的 PX 传感器脚本。它没有常见的混淆属性查找和收集器函数,而是包含: - **8 个大型 base64 字符串**(`_fg0` 到 `_fg7`),加密的虚拟机程序分散在这些变量中 - **一个 XOR 解密函数**(`_dp`),使用每个站点特有的密钥(`_pk`)来解包程序 JSON - **一个 Fisher-Yates 洗牌**,对操作码表进行置换,使字节码值因构建版本而异 - **一个调度循环**,包含 107+ 个 case 处理器,即虚拟机解释器 - **BigInt 算术**,用于 RSA 加密指纹输出 - **一个代码完整性哈希**(`_0x8df7`),对虚拟机自身的源代码进行哈希以派生解密密钥,因此任何修改都会静默破坏字节码解密 ## 第一步:字节码提取 虚拟机程序分散在 8 个变量中,连接后通过 `_dp()` 使用每个站点特有的 XOR 密码(由 `_pk` 密钥)进行解密: ``` var _pk = 893686289; function _dp(_b) { var _r = atob(_b), _o = new Array(_r.length); for (var _i = 0; _i < _r.length; _i++) { _o[_i] = String.fromCharCode( _r.charCodeAt(_i) ^ (((_pk >>> (8 * (_i % 4))) ^ Math.imul(_i + 1, 0x6B8B4567)) & 0xFF) ); } return _o.join(""); } ``` 结果是一个 JSON 对象,具有混淆的两字符键名(例如,`"uo"` 表示种子,`"dk"` 表示随机数)。映射表将它们转换为标准名称。 ``` node extractor.js # program.json ``` ### 程序结构 | 字段 | 描述 | |-----|-------------| | `s` | 种子(12755),驱动所有加密操作 | | `n` | 随机数(1603710985),每个程序的随机化 | | `g` | 生成器标志,启用完整性哈希解密层 | | `x` | 加密标志,常量使用 XOR 加密 | | `c` | 常量池,1230 条目 | | `f` | 函数,112 条目,包含加密的字节码 | | `e` | 入口点,函数索引 0 | ## 第二步:常量解密 所有 1095 个字符串常量使用两层加密: **第一层:** 静态 murmur XOR,由 `4008000571` 密钥,位置相关。 **第二层:** PRNG 流 XOR,使用 glibc LCG,通过将程序种子与每个常量的索引结合(通过 Knuth 乘法哈希)来种子。 在解密之前,种子与**环境指纹**(`_0xaf48`)进行 XOR 运算,这是一个 8 位位掩码,通过探测浏览器 API 计算得出: | 位 | 测试 | Chrome | |-----|------|--------| | 0 | `typeof window.matchMedia === "function"` | 1 | | 1 | `document.elementFromPoint` 存在 | 1 | | 2 | `typeof window.requestAnimationFrame === "function"` | 1 | | 3 | `typeof window.getComputedStyle === "function"` | 1 | | 4 | `CSS.supports` 存在 | 1 | | 5 | `navigator.sendBeacon` 存在 | 1 | | 6 | `document.execCommand` 存在 | 1 | | 7 | `process.versions.node` 存在(Node.js) | 0 | 对于 Chrome:`_0xaf48 = 0b01111111 = 127`,有效种子 = `12755 ^ 127 = 12716`。 这意味着相同的程序在不同的环境中会产生不同的解密结果。在 Node.js 与 Chrome 与 Firefox 中运行会产生不同的种子。 ``` node decrypt_constants.js # program_decrypted.json, constants_table.txt ``` ### 常量揭示的内容 解密的字符串准确告诉我们虚拟机指纹识别什么: **浏览器指纹识别:** `screenWidth`、`screenHeight`、`innerWidth`、`innerHeight`、`devicePixelRatio`、`colorDepth`、`platform`、`userAgent`、`language`、`timezone`、`timezoneOffset`、`forcedColors`、`highContrast` **性能计时:** `navigationStart`、`domComplete`、`domLoading`、`fetchStart`、`requestStart`、`responseEnd`、`secureConnectionStart`、`serverTiming` **RSA 加密:** `BigInt`、`modPow`、`AQAB`(base64 中的 65537)、`modulusLength`、`shiftLeft`、`shiftRight`、`getRandomValues` **DOM/SVG 探测:** `http://www.w3.org/2000/svg`、`createElementNS`、`getBoundingClientRect`、`getTotalLength`、`getBBox` **PX 字段名:** `mtr`、`tst`、`mst`、`enc`、`sbx`、`fstec`、`pdc`、`prb`、`wvi`、`wva`、`pti`、`dis`、`los`、`cv`、`sc`、`jd`、`ads`、`enve`、`init` **端点引用:** `https://fst-ec.perimeterx.net/?id=` **反调试器:** `_CMP_RCX_07;_JNZ_0x0A_EB_CC`、`CC|CD-04|BREAKPOINT-005` ## 第三步:操作码表 107 个基础操作码覆盖完整的 JavaScript 语言,外加动态生成的噪声: **40 个蜜罐操作码**是算术/比较操作的替代实现,使用数学等价但语法不同的表达式。`ADD` 可能出现为 `(a^b) + 2*(a&b)` 或 `-((-a)-b)` 或 `a-(-b)`。每个基础操作码最多可以有 3 个变体,从种子确定性生成。简单的 `ADD` 指令可以在同一程序中以 4 个不同的字节码值出现,从而破坏模式匹配方法。 **24 个填充操作码**在置换中被分配,但没有处理器,永远不会发出。它们的存在是为了扩展操作码空间,使洗牌更难反转。 **16 个超级指令组**是最重要的反分析特性。当调度循环将操作码解析为超级指令领导者时,处理器从字节码流中读取一个额外字节,并调度到子处理器。子处理器可能是完全不同的操作: | 领导者解析为 | 子字节 | 实际执行 | |---|---|---| | `FOR_IN_NEXT` | 74 | `FOR_IN_NEXT` | | `FOR_IN_NEXT` | 100 | `MAKE_CLOSURE` | | `ASSIGN_OP_VAR` | 165 | `ASSIGN_OP_VAR` | | `ASSIGN_OP_VAR` | 37 | `JMP` | | `GET_VAR_PROP_C` | 143 | `SET_VAR_POP` | | `GET_VAR_PROP_C` | 23 | `JMP_NULLISH` | 操作码表通过 Fisher-Yates 洗牌进行置换,由有效种子种子,因此字节码值因构建版本而异。 ``` node build_opcodes.js # opcode_table.json, opcode_table.txt ``` ## 第四步:CFG 构建器 这是工具包的核心。`cfg.js` 通过从 PC=0 开始跟踪所有执行路径来构建控制流图,使用正确的加密上下文解码每条指令。 ### 为什么不是线性反汇编器 PX 在块边界使用重叠指令。同样的字节在一条执行路径上解码为操作数,在另一条上解码为操作码,取决于块加密上下文。线性扫描每个字节位置一次,会错过备选路径。CFG 跟随失败和跳转边,独立解码每条路径。 ### 五层字节码加密 1. **第一层**(`_0x3ca8`):静态 murmur XOR 对原始 base64 字节,由 `4008000571` 密钥 2. **第二层**(`_0xece1`):每个函数的 XOR,有两个子层:位置相关静态密钥 + 代码完整性哈希密钥 3. **第三层**(`_0x427d`):每块滚动 XOR。每个加密块(由 `fn.bl` 边界定义)获得额外的 XOR,派生自函数密钥和块索引。块 0 首次访问时未加密;块 1+ 已加密。这就是为什么线性反汇编器对第一个块有效,但对后续块产生垃圾的原因。 4. **操作码置换**:Fisher-Yates 洗牌 + 位置相关偏移 + 块相关偏移 5. **每条指令数 XOR**:操作数字节与从指令起始位置派生的密钥进行 XOR CFG 非破坏性地应用所有五层(操作数 XOR 动态计算,不原地进行),因此重叠指令区域不会相互损坏。 ### 超级指令解析 对于每个超级指令领导者,CFG 读取子字节,在 `super_groups.json` 中查找实际处理器,并为真实操作码解码操作数。来自融合跳转操作码(例如,看起来像 `ASSIGN_OP_VAR` 但实际上是 `JMP`)的跳转目标被正确跟随。 ``` node cfg.js # all functions -> cfg_output/ node cfg.js 79 # single function to stdout ``` 经浏览器执行轨迹验证:跨 14 个函数跟踪了 600 条指令,栈增量零不匹配。所有 34 个唯一操作码已验证。超级指令调度在初始化期间执行的所有 10 个领导者组都确认正确。 ## 第五步:清理器 获取 CFG 输出并运行栈模拟以生成表达式注释。将原始字节码转换为可读的伪代码。 ``` node cleaner.js 79 # fn79 to stdout ``` 按顺序遍历指令,跟踪虚拟栈。每次 push/pop/call 构建一个表达式字符串: ``` 0018 GET_VAR ; 0.0001 001e GET_VAR ; or 0024 PUSH_CONST ; "_0x166" 002b CALL_METHOD_C ; 0.0001._0x88(or, "_0x166") ... 0114 PUSH_CONST ; "fontSize" 011b PUSH_CONST ; "pdc" 0122 CALL_METHOD_C ; _0x18c.getHours("fontSize", "pdc") ``` 仅抑制空栈上的纯算术/比较噪声。其他所有内容都保留,因为 CFG 已经过滤了蜜罐和填充。 112 个函数,保留 5435 条指令,抑制 207 条噪声。fn79(指纹收集器,1109 条指令)有 85% 的表达式注释覆盖率。 ## 虚拟机架构 基于栈的虚拟机,具有 256 槽栈、作用域链、try/catch 处理器链和 for-in 迭代器栈。调度循环读取 2 字节 LE 操作码,通过置换 + 位置 XOR + 块偏移解析,就地解密操作数,执行处理器,然后重新加密操作数,因此字节码在内存中永远不会完全解密。 ### 关键发现 - 使用 `BigInt`、`modPow`、指数 65537 对指纹输出进行 RSA 加密 - 通过构造路径上的 `getTotalLength()` 和 `getBBox()` 进行 SVG 渲染指纹识别 - 完整的 `performance.timing` 瀑布收集 - 反调试器检测标记(`CC|CD-04|BREAPOINT-005`) - 函数 79 是主要指纹收集器(8361 字节,约 1200 条指令) ## 备注 人工智能已用于帮助文档化代码、编写工具和起草本说明文档。 ## 免责声明 仅用于教育/安全研究。没有解决方案或绕过,只是记录虚拟机的工作原理,因为它确实很有趣。 如果 PerimeterX/HUMAN Security 的任何人对本仓库有疑虑,欢迎联系:b9h0met@proton.me
标签:BigInt运算, Bot检测, C2日志可视化, CFG分析, DAST, DNS 反向解析, JavaScript逆向, MITM代理, PerimeterX, RSA加密, Wayback Machine, Web安全, 云资产清单, 代码完整性, 代码混淆, 反分析技术, 反汇编, 反爬虫, 堆栈模拟, 子域名暴力破解, 字节码, 安全报告生成, 恶意软件分析, 指纹识别, 数据可视化, 浏览器指纹, 混淆代码, 自动化检测, 蓝队分析, 虚拟机分析, 蜜罐操作码, 解密分析, 逆向工程