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安全, 云资产清单, 代码完整性, 代码混淆, 反分析技术, 反汇编, 反爬虫, 堆栈模拟, 子域名暴力破解, 字节码, 安全报告生成, 恶意软件分析, 指纹识别, 数据可视化, 浏览器指纹, 混淆代码, 自动化检测, 蓝队分析, 虚拟机分析, 蜜罐操作码, 解密分析, 逆向工程