Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
px-vm — PerimeterX 字节码虚拟机的逆向工程工具包,包含基于控制流图的反汇编器、五层解密管道、操作码表重建以及栈模拟清理器,用于安全研究中的机器人检测指纹识别。 | Kitploit
工具/GitHubGitHub/b9ph0met/px-vm
动态分析 (沙盒)IDS/IPS规避逆向工程Web安全恶意软件分析密码学二进制分析论文与研究学习与教育反机器人指纹欺骗
GitHub
551465个月前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享
b9ph0met/px-vm

px-vm

PerimeterX 字节码虚拟机的逆向工程工具包,包含基于控制流图的反汇编器、五层解密管道、操作码表重建以及栈模拟清理器,用于安全研究中的机器人检测指纹识别。

查看仓库

PerimeterX Auditor VM 分析

摘要

本仓库记录了 PerimeterX 的 auditor.js 的反向工程结果,该文件是一个字节码虚拟机,用作 PX 机器人检测管道中的二级指纹识别层。本分析涵盖以下内容:

  • 字节码提取与五层解密管道
  • 操作码表重建(107 个基本操作码 + 40 个蜜罐操作码 + 24 个填充操作码 + 16 个超级指令组)
  • 常量池解密(1230 个条目,1095 个加密字符串)
  • 基于 CFG 的反汇编器,支持超级指令子分派解析
  • 堆栈模拟清理器,生成可读的伪代码
  • 反分析技术:蜜罐操作码、重叠指令、代码完整性哈希

注意: 本仓库仅涵盖一个(静态)VM 版本,旨在用于安全研究与分析目的。不包含动态求解器或生产级求解器实现。

背景

2026 年 4 月 2 日星期四,PerimeterX 在其机器人检测管道中部署了新的字节码虚拟机。

内部内容

auditor.js 看起来不像普通的 PX 传感器脚本。它没有常见的混淆属性查找和收集器函数:

  • 8 个巨大的 base64 字符串(_fg0 到 _fg7),加密的 VM 程序分散在多个变量中
  • 一个 XOR 解密函数(_dp),使用特定站点的密钥(_pk)解包程序 JSON
  • 一个 Fisher-Yates 洗牌,用于排列操作码表,使得每次构建的字节码值不同
  • 一个分发循环,包含 107 个以上的 case 处理程序,即 VM 解释器
  • 用于指纹输出 RSA 加密的大整数算术
  • 一个代码完整性哈希(_0x8df7),对 VM 自身的源代码进行哈希处理以派生解密密钥,因此任何修改都会静默地破坏字节码解密

步骤 1:字节码提取

VM 程序被分割到 8 个变量中,拼接后通过 _dp() 使用特定站点的 XOR 密码进行解密,该密码由 _pk 做密钥:

root@kitploit:~
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" 表示随机数)。一个映射表将其转换为标准名称。

root@kitploit:~
node extractor.js
# -> program.json

程序结构

步骤 2:常量解密

所有 1095 个字符串常量都经过两层加密:

第 1 层: 静态 murmur XOR,密钥为 4008000571,位置相关。

第 2 层: 使用 glibc LCG 的 PRNG 流 XOR,通过 Knuth 乘法哈希将程序种子与每个常量的索引组合作为种子。

解密前,种子会与环境指纹(_0xaf48)进行 XOR,该指纹是一个 8 位比特掩码,通过探测浏览器 API 计算得到:

对于 Chrome:_0xaf48 = 0b01111111 = 127,有效种子为 12755 ^ 127 = 12716。

这意味着相同的程序在不同环境中会产生不同的解密结果。在 Node.js、Chrome 和 Firefox 中运行会得到不同的种子。

root@kitploit:~
node decrypt_constants.js
# -> program_decrypted.json, constants_table.txt

常量揭示的内容

解密后的字符串精确告诉我们 VM 指纹收集的内容:

浏览器指纹识别: 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(65537 的 base64 编码)、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、、

端点引用: https://fst-ec.perimeterx.net/?id=

反调试器: _CMP_RCX_07;_JNZ_0x0A_EB_CC、CC|CD-04|BREAKPOINT-005

步骤 3:操作码表

107 个基本操作码覆盖完整的 JavaScript 语言,加上动态生成的噪音:

40 个蜜罐操作码 是算术/比较运算的替代实现,使用数学上等价但语法不同的表达式。ADD 可能表现为 (a^b) + 2*(a&b) 或 -((-a)-b) 或 a-(-b)。每个基本操作码最多可以有 3 个变体,由种子确定性地生成。一个简单的 ADD 指令在同一个程序中可以出现 4 种不同的字节码值,从而破坏模式匹配方法。

24 个填充操作码 在排列中被分配但没有处理程序,也永远不会被发射。它们的存在是为了扩展操作码空间,使洗牌更难逆转。

16 个超级指令组 是最重要的反分析特性。当分发循环将一个操作码解析为超级指令引导码时,处理程序从字节码流中再读取一个额外的字节并分派到子处理程序。子处理程序可以是一个完全不同的操作:

操作码表通过使用有效种子作为种子的 Fisher-Yates 洗牌进行排列,因此字节码值每次构建都不同。

root@kitploit:~
node build_opcodes.js
# -> opcode_table.json, opcode_table.txt

步骤 4:CFG 构建器

工具包的核心。cfg.js 通过追踪从 PC=0 开始的所有执行路径来构建控制流图,并在正确的加密上下文中解码每条指令。

为什么不用线性反汇编器

PX 在块边界处使用了重叠指令。相同的字节在某条执行路径上解码为操作数,在另一条路径上则解码为操作码,具体取决于块的加密上下文。线性扫描只解码每个字节位置一次,会丢失替代路径。CFG 同时追踪直通边和跳转边,独立解码每条路径。

五层字节码加密

  1. 第 1 层(_0x3ca8):对原始 base64 字节进行静态 murmur XOR,密钥为 4008000571
  2. 第 2 层(_0xece1):每个函数进行 XOR,包含两个子层:位置相关的静态密钥 + 代码完整性哈希密钥
  3. 第 3 层(_0x427d):每个块的滚动 XOR。每个加密块(由 fn.bl 边界定义)获得一个额外的 XOR,该 XOR 由函数密钥和块索引派生。块 0 在首次访问时未加密,后续块 1+ 则加密。这就是为什么线性反汇编器对第一个块有效,但对后续块产生垃圾。
  4. 操作码排列:Fisher-Yates 洗牌 + 位置相关偏移 + 块相关偏移
  5. 每条指令的操作数 XOR:操作数字节与指令起始位置派生的密钥进行 XOR

CFG 以非破坏性方式应用所有五层(操作数 XOR 是即时计算的,而非就地计算),因此重叠指令区域不会相互损坏。

超级指令解析

对于每个超级指令引导码,CFG 读取子字节,在 super_groups.json 中查找实际处理程序,并为真实操作码解码操作数。来自融合跳转操作码(例如看起来是 ASSIGN_OP_VAR 但实际上是 JMP)的跳转目标会被正确跟踪。

root@kitploit:~
node cfg.js        # 所有函数 -> cfg_output/
node cfg.js 79     # 单个函数,输出到标准输出

已通过浏览器执行轨迹验证:跨 14 个函数追踪了 600 条指令,堆栈增量零不匹配。所有 34 个唯一操作码均已验证。在初始化期间执行的所有 10 个引导码组中,超级指令分派确认正确。

步骤 5:清理器

接收 CFG 输出并运行堆栈模拟以生成表达式注释。将原始字节码转换为可读的伪代码。

root@kitploit:~
node cleaner.js 79     # fn79 输出到标准输出

按顺序遍历指令,跟踪虚拟堆栈。每个压栈/出栈/调用都会构建一个表达式字符串:

root@kitploit:~
  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%。

VM 架构

基于堆栈的虚拟机,具有 256 个槽位的堆栈、作用域链、try/catch 处理程序链和 for-in 迭代器堆栈。分发循环读取 2 字节小端操作码,通过排列 + 位置 XOR + 块偏移进行解析,就地解密操作数,执行处理程序,然后重新加密操作数,使得字节码在内存中永远不会完全解密。

关键发现

  • 使用 BigInt、modPow、指数 65537 对指纹输出进行 RSA 加密
  • 通过构造路径上的 getTotalLength() 和 getBBox() 进行 SVG 渲染指纹识别
  • 完整的 performance.timing 水瀑布收集
  • 反调试检测标记(CC|CD-04|BREAKPOINT-005)
  • 函数 79 是主要的指纹收集器(8361 字节,约 1200 条指令)

注意

AI 已被用于帮助编写代码文档、开发工具和起草此自述文件。

免责声明

纯属教育/安全研究目的。无求解器或绕过方法,仅作为记录虚拟机工作原理,因为其本身确实有趣。

如果 PerimeterX/HUMAN Security 的任何人对本仓库有异议,请随时联系:[email protected]

下载工具
字段描述
s种子(12755),驱动所有加密操作
n随机数(1603730985),每次程序随机化
g生成器标志,启用完整性哈希解密层
x加密标志,常量经过 XOR 加密
c常量池,1230 个条目
f函数,112 个条目,包含加密的字节码
e入口点,函数索引 0
位测试Chrome
0typeof window.matchMedia === "function"1
1document.elementFromPoint 存在1
2typeof window.requestAnimationFrame === "function"1
3typeof window.getComputedStyle === "function"1
4CSS.supports 存在1
5navigator.sendBeacon 存在1
6document.execCommand 存在1
7process.versions.node 存在(Node.js)0
enve
init
引导码解析为子字节实际执行
FOR_IN_NEXT74FOR_IN_NEXT
FOR_IN_NEXT100MAKE_CLOSURE
ASSIGN_OP_VAR165ASSIGN_OP_VAR
ASSIGN_OP_VAR37JMP
GET_VAR_PROP_C143SET_VAR_POP
GET_VAR_PROP_C23JMP_NULLISH