Skip to content
KitploitKITPLOIT
ツールブログ
Log in
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
px-vm — PerimeterXのバイトコードVM用リバースエンジニアリングツールキット。CFGベースの逆アセンブラ、5層の復号パイプライン、オペコードテーブルの再構築、ボット検出フィンガープリンティングのセキュリティ研究のためのスタックエミュレーションクリーナーを備えています。 | Kitploit
ツール/GitHubGitHub/b9ph0met/px-vm
動的分析 (サンドボックス)IDS/IPS回避リバースエンジニアリングウェブセキュリティマルウェア分析暗号化バイナリ解析論文と研究学習と教育アンチボットフィンガープリントスプーフィング
5514265ヶ月前Kitploit レビュー済み

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有
GitHub
b9ph0met/px-vm

px-vm

PerimeterXのバイトコードVM用リバースエンジニアリングツールキット。CFGベースの逆アセンブラ、5層の復号パイプライン、オペコードテーブルの再構築、ボット検出フィンガープリンティングのセキュリティ研究のためのスタックエミュレーションクリーナーを備えています。

リポジトリを見る

PerimeterX Auditor VM 解析

概要

このリポジトリは、PerimeterX の auditor.js のリバースエンジニアリングを文書化したものです。auditor.js は、PX のボット検出パイプラインにおいて二次的なフィンガープリンティング層として使用されるバイトコード仮想マシンです。この分析では以下をカバーしています。

  • バイトコードの抽出と5層の復号パイプライン
  • オペコードテーブルの再構築(基本107種 + ハニー40種 + パディング24種 + スーパーインストラクショングループ16種)
  • 定数プールの復号(1230エントリ、1095個の暗号化文字列)
  • スーパーインストラクションのサブディスパッチ解決を備えたCFGベースの逆アセンブラ
  • 可読性の高い疑似コードを生成するスタックエミュレーションクリーナー
  • アンチ解析技術:ハニーオペコード、命令の重複、コード整合性ハッシュ

注: このリポジトリは1つの(静的)VMバージョンのみを対象としており、セキュリティ研究および解析目的を意図しています。動的ソルバーや本番用ソルバーの実装は含まれていません。

背景

2026年4月2日木曜日、PerimeterX はボット検出パイプラインの一部として新たなバイトコードVMを展開しました。

内部構造

auditor.js は通常の PX センサースクリプトのように見えません。通常の難読化されたプロパティルックアップやコレクター関数の代わりに:

  • 8つの巨大な base64 文字列(_fg0 から _fg7)、暗号化された VM プログラムが変数に分割されています
  • XOR 復号関数(_dp)はサイト固有の鍵(_pk)を使用し、プログラム JSON を展開します
  • Fisher-Yates シャッフルによってオペコードテーブルが並び替えられ、ビルドごとにバイトコードの値が異なります
  • ディスパッチループには 107 以上のケースハンドラがあり、VM インタプリタとして機能します
  • フィンガープリント出力の RSA 暗号化に BigInt 演算を使用
  • コード整合性ハッシュ(_0x8df7)は VM 自身のソースをハッシュ化して復号鍵を導出するため、変更を加えるとバイトコードの復号が静かに失敗します

ステップ 1: バイトコードの抽出

VM プログラムは 8 つの変数に分割され、連結された後、_dp() によって _pk を鍵とするサイト固有の XOR 暗号を用いて復号されます:

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 オブジェクトであり、キー名は難読化された2文字(例:シードは "uo"、ナンスは "dk")です。変換テーブルによって標準名に変換されます。

node extractor.js
# -> program.json

プログラム構造

フィールド説明
sシード (12755)、すべての暗号操作を駆動
nナンス (1603730985)、プログラムごとのランダム化
gジェネレータフラグ、整合性ハッシュ復号層を有効化
x暗号化フラグ、定数は XOR 暗号化されている
c定数プール、1230エントリ
f関数、112エントリ(暗号化されたバイトコードを含む)
eエントリポイント、関数インデックス 0

ステップ 2: 定数の復号

1095 個すべての文字列定数は 2 層で暗号化されています。

層 1: 静的 murmur XOR、鍵は 4008000571、位置依存。

層 2: PRNG ストリーム XOR、glibc LCG を使用。シードはプログラムシードと各定数のインデックスを Knuth の乗算ハッシュで組み合わせて生成。

復号前に、シードは 環境フィンガープリント(_0xaf48)と XOR されます。これはブラウザ API をプローブして計算される 8 ビットのビットマスクです:

ビットテスト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

Chrome の場合: _0xaf48 = 0b01111111 = 127、よって有効シード = 12755 ^ 127 = 12716。

これは、同じプログラムでも異なる環境で異なる復号結果が得られることを意味します。Node.js、Chrome、Firefox で実行すると異なるシードになります。

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 (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

ステップ 3: オペコードテーブル

JavaScript の全言語をカバーする 107 個の基本オペコードに加え、動的に生成されるノイズ:

40 個のハニーオペコード は、算術/比較演算の代替実装であり、数学的に等価だが構文的に異なる式を使用します。ADD は (a^b) + 2*(a&b) や -((-a)-b) や a-(-b) として現れることがあります。各基本オペコードは最大 3 つのバリアントを持ち、シードから決定論的に生成されます。単純な ADD 命令が同じプログラム内で 4 つの異なるバイトコード値として現れ、パターンマッチング手法を破ります。

24 個のパディングオペコード は並び替えに割り当てられますが、ハンドラはなく、決して発行されません。これらはオペコード空間を拡大し、シャッフルを逆転しにくくするために存在します。

16 個のスーパーインストラクショングループ は最も重要なアンチ解析機能です。ディスパッチループがオペコードをスーパーインストラクションリーダーとして解決すると、ハンドラはバイトコードストリームから追加で 1 バイトを読み取り、サブハンドラにディスパッチします。サブハンドラはまったく異なる操作になる可能性があります:

リーダーとして解決サブバイト実際に実行
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

オペコードテーブルは有効シードをシードとした Fisher-Yates でシャッフルされるため、バイトコードの値はビルドごとに異なります。

node build_opcodes.js
# -> opcode_table.json, opcode_table.txt

ステップ 4: CFG ビルダー

このツールキットの中核です。cfg.js は、PC=0 からすべての実行経路を追跡し、各命令を正しい暗号化コンテキストでデコードすることで制御フローグラフを構築します。

なぜ線形逆アセンブラではないのか

PX はブロック境界で重複する命令を使用します。同じバイトが、一方の実行経路ではオペランドとして、もう一方の実行経路ではオペコードとしてデコードされます。これはブロック暗号化コンテキストに依存します。線形スキャンは各バイト位置を一度だけデコードし、代替経路を見逃します。CFG はフォールスルーエッジとジャンプエッジの両方を追跡し、各経路を独立してデコードします。

5層のバイトコード暗号化

  1. 層 1 (_0x3ca8): 生の base64 バイトに対する静的 murmur XOR、鍵は 4008000571
  2. 層 2 (_0xece1): 関数ごとの XOR、2 つのサブレイヤー:位置依存の静的鍵 + コード整合性ハッシュ鍵
  3. 層 3 (_0x427d): ブロックごとのローリング XOR。各暗号化ブロック(fn.bl 境界で定義)は、関数鍵とブロックインデックスから導出される追加の XOR を受け取ります。ブロック 0 は最初のアクセス時は暗号化されていません。ブロック 1 以降は暗号化されています。これが、線形逆アセンブラが最初のブロックでは機能するが、後続のブロックではゴミを生成する理由です。
  4. オペコードの順列: Fisher-Yates シャッフル + 位置依存オフセット + ブロック依存オフセット
  5. 命令ごとのオペランド XOR: オペランドバイトは、命令開始位置から導出される鍵と XOR されます

CFG は 5 層すべてを非破壊的に適用します(オペランド XOR はその場で計算され、インプレースではない)。これにより、重複する命令領域が互いに破損しません。

スーパーインストラクションの解決

各スーパーインストラクションリーダーについて、CFG はサブバイトを読み取り、super_groups.json で実際のハンドラを検索し、実際のオペコードのオペランドをデコードします。融合されたジャンプオペコード(例えば、ASSIGN_OP_VAR のように見えて実際は JMP であるもの)からのジャンプ先も正しく追跡されます。

node cfg.js        # 全関数 -> cfg_output/
node cfg.js 79     # 単一関数を標準出力

ブラウザの実行トレースで検証済み:14 関数にわたって 600 命令をトレース、スタックデルタの不一致 0。34 のユニークなオペコードすべてを検証。初期化中に実行された 10 のリーダーグループすべてで、スーパーインストラクションディスパッチが正しいことを確認。

ステップ 5: クリーナー

CFG の出力を受け取り、スタックエミュレーションを実行して式コメントを生成します。生のバイトコードを読みやすい疑似コードに変換します。

node cleaner.js 79     # fn79 を標準出力

命令を順に辿り、仮想スタックを追跡します。各 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% の式コメントカバレッジ。

VM アーキテクチャ

スタックベースの 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 命令)

備考

コードの文書化、ツールの作成、この README の草稿には AI が使用されています。

免責事項

純粋に教育/セキュリティ研究目的です。ソルバーや回避策は含まず、VM の動作を文書化しているだけです。それは純粋に興味深いからです。

PerimeterX/HUMAN Security の関係者でこのリポジトリに関して懸念がある方は、遠慮なくご連絡ください:[email protected]

ツールをダウンロード