Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
px-vm — Reverse-Engineering-Toolkit für den Bytecode-VM von PerimeterX, mit einem CFG-basierten Disassembler, einer 5-stufigen Entschlüsselungspipeline, Opcode-Tabellen-Rekonstruktion und Stack-Emulations-Bereinigung für Sicherheitsforschung zur Erkennung von Bot-Detection-Fingerprinting. | Kitploit
Tools/GitHubGitHub/b9ph0met/px-vm
Dynamische Analyse (Sandboxing)IDS/IPS-UmgehungReverse EngineeringWebsicherheitMalware-AnalyseKryptographieBinäranalysePapers & ForschungLernen & BildungAnti-BotFingerabdruck-Spoofing
55147vor 5 MonatenVon Kitploit geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
GitHub
b9ph0met/px-vm

px-vm

Repository anzeigen

Über

Reverse-Engineering-Toolkit für den Bytecode-VM von PerimeterX, mit einem CFG-basierten Disassembler, einer 5-stufigen Entschlüsselungspipeline, Opcode-Tabellen-Rekonstruktion und Stack-Emulations-Bereinigung für Sicherheitsforschung zur Erkennung von Bot-Detection-Fingerprinting.

Teilen

PerimeterX Auditor VM Analyse

Zusammenfassung

Dieses Repository dokumentiert das Reverse Engineering von PerimeterX auditor.js, einer Bytecode-Virtual Machine, die als sekundäre Fingerprinting-Ebene in der Bot-Erkennungspipeline von PX verwendet wird. Diese Analyse umfasst:

  • Bytecode-Extraktion und 5-stufige Entschlüsselungspipeline
  • Rekonstruktion der Opcode-Tabelle (107 Basis- + 40 Honig- + 24 Padding- + 16 Superinstruction-Gruppen)
  • Entschlüsselung des Konstanten-Pools (1230 Einträge, 1095 verschlüsselte Strings)
  • CFG-basierter Disassembler mit Auflösung von Superinstruction-Sub-Dispatch
  • Stack-Emulationsbereiniger, der lesbaren Pseudocode erzeugt
  • Anti-Analyse-Techniken: Honig-Opcodes, überlappende Instruktionen, Code-Integritäts-Hashing

Hinweis: Dieses Repository behandelt nur eine (statische) VM-Version und dient der Sicherheitsforschung und Analyse. Es enthält keine dynamischen Löser oder Produktionslöser-Implementierungen.

Hintergrund

Am Donnerstag, den 2. April 2026, hat PerimeterX eine neue Bytecode-VM als Teil seiner Bot-Erkennungspipeline eingesetzt.

Was enthalten ist

auditor.js sieht nicht wie ein normales PX-Sensor-Skript aus. Statt der üblichen obfuskierten Eigenschaftszugriffe und Collector-Funktionen:

  • 8 massive Base64-Strings (_fg0 bis _fg7), das verschlüsselte VM-Programm, aufgeteilt auf Variablen
  • Eine XOR-Entschlüsselungsfunktion (_dp) mit einem pro-Site-Schlüssel (_pk), die das Programm-JSON entpackt
  • Ein Fisher-Yates-Shuffle, das die Opcode-Tabelle permutiert, sodass Bytecode-Werte pro Build unterschiedlich sind
  • Eine Dispatch-Schleife mit 107+ Case-Handlern, der VM-Interpreter
  • BigInt-Arithmetik für die RSA-Verschlüsselung des Fingerprint-Outputs
  • Ein Code-Integritäts-Hash (_0x8df7), der den eigenen Quellcode der VM hasht, um einen Entschlüsselungsschlüssel abzuleiten, sodass jede Modifikation die Bytecode-Entschlüsselung stillschweigend bricht

Schritt 1: Bytecode-Extraktion

Das VM-Programm ist auf 8 Variablen aufgeteilt, verkettet und dann über _dp() mit einem pro-Site-XOR-Chiffre, das durch _pk geschlüsselt wird, entschlüsselt:

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("");
}

Das Ergebnis ist ein JSON-Objekt mit obfuskierten Zweizeichen-Schlüsselnamen (z.B. "uo" für Seed, "dk" für Nonce). Eine Mapping-Tabelle wandelt sie in Standardnamen um.

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

Programmstruktur

FeldBeschreibung
sSeed (12755), steuert alle kryptografischen Operationen
nNonce (1603730985), pro-Programm-Randomisierung
gGenerator-Flag, aktiviert die Integritäts-Hash-Entschlüsselungsebene
xVerschlüsseltes Flag, Konstanten sind XOR-verschlüsselt
cKonstanten-Pool, 1230 Einträge
fFunktionen, 112 Einträge mit verschlüsseltem Bytecode
eEinstiegspunkt, Funktionsindex 0

Schritt 2: Konstanten-Entschlüsselung

Alle 1095 String-Konstanten sind mit zwei Ebenen verschlüsselt:

Ebene 1: Statischer Murmur-XOR, geschlüsselt mit 4008000571, positionsabhängig.

Ebene 2: PRNG-Stream-XOR mittels eines glibc-LCG, gesät durch Kombination des Programm-Seeds mit dem Index jeder Konstante mittels Knuths multiplikativem Hash.

Vor der Entschlüsselung wird der Seed mit einem Umgebungs-Fingerabdruck (_0xaf48) XOR-verknüpft, einer 8-Bit-Bitmaske, die durch Abfragen von Browser-APIs berechnet wird:

BitTestChrome
0typeof window.matchMedia === "function"1
1document.elementFromPoint existiert1
2typeof window.requestAnimationFrame === "function"1
3typeof window.getComputedStyle === "function"1
4CSS.supports existiert1
5navigator.sendBeacon existiert1
6document.execCommand existiert1
7process.versions.node existiert (Node.js)0

Für Chrome: _0xaf48 = 0b01111111 = 127, ergibt effektiven Seed = 12755 ^ 127 = 12716.

Das bedeutet, dass dasselbe Programm in verschiedenen Umgebungen unterschiedliche Entschlüsselungsergebnisse liefert. Die Ausführung in Node.js vs. Chrome vs. Firefox ergibt unterschiedliche Seeds.

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

Was die Konstanten verraten

Die entschlüsselten Strings zeigen uns genau, was die VM fingerabdruckt:

Browser-Fingerprinting: screenWidth, screenHeight, innerWidth, innerHeight, devicePixelRatio, colorDepth, platform, userAgent, language, timezone, timezoneOffset, forcedColors, highContrast

Performance-Timing: navigationStart, domComplete, domLoading, fetchStart, requestStart, responseEnd, secureConnectionStart, serverTiming

RSA-Kryptografie: BigInt, modPow, AQAB (65537 in Base64), modulusLength, shiftLeft, shiftRight, getRandomValues

DOM/SVG-Abfrage: http://www.w3.org/2000/svg, createElementNS, getBoundingClientRect, getTotalLength, getBBox

PX-Feldnamen: mtr, tst, mst, enc, sbx, fstec, pdc, prb, wvi, wva, pti, dis, los, cv, sc, jd, ads, enve, init

Endpunkt-Referenzen: https://fst-ec.perimeterx.net/?id=

Anti-Debugger: _CMP_RCX_07;_JNZ_0x0A_EB_CC, CC|CD-04|BREAKPOINT-005

Schritt 3: Opcode-Tabelle

107 Basis-Opcodes, die die gesamte JavaScript-Sprache abdecken, plus dynamisch generiertes Rauschen:

40 Honig-Opcodes sind alternative Implementierungen von arithmetischen/Vergleichsoperationen mit mathematisch äquivalenten, aber syntaktisch unterschiedlichen Ausdrücken. ADD könnte als (a^b) + 2*(a&b) oder -((-a)-b) oder a-(-b) erscheinen. Jeder Basis-Opcode kann bis zu 3 Varianten haben, die deterministisch aus dem Seed generiert werden. Eine einfache ADD-Instruktion kann als 4 verschiedene Bytecode-Werte innerhalb desselben Programms erscheinen und bricht Mustervergleichsansätze.

24 Padding-Opcodes werden in der Permutation zugewiesen, haben aber keine Handler und werden nie ausgegeben. Sie existieren, um den Opcode-Raum zu erweitern und das Shuffle schwerer umkehrbar zu machen.

16 Superinstruction-Gruppen sind das wichtigste Anti-Analyse-Merkmal. Wenn die Dispatch-Schleife einen Opcode als Superinstruction-Leader auflöst, liest der Handler ein zusätzliches Byte aus dem Bytecode-Stream und dispatcht an einen Sub-Handler. Der Sub-Handler kann eine völlig andere Operation sein:

Leader löst auf alsSub-ByteFührt tatsächlich aus
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

Die Opcode-Tabelle wird mittels Fisher-Yates-Shuffle durch den effektiven Seed gemischt, sodass Bytecode-Werte pro Build unterschiedlich sind.

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

Schritt 4: CFG-Builder

Das Kernstück des Toolkits. cfg.js erstellt einen Kontrollflussgraphen, indem es allen Ausführungspfaden ab PC=0 folgt, jede Instruktion mit dem richtigen Verschlüsselungskontext dekodiert.

Warum kein linearer Disassembler

PX verwendet überlappende Instruktionen an Blockgrenzen. Dieselben Bytes werden auf einem Ausführungspfad als Operanden und auf einem anderen als Opcodes dekodiert, abhängig vom Blockverschlüsselungskontext. Ein linearer Scan dekodiert jede Byteposition nur einmal und übersieht den alternativen Pfad. Der CFG folgt sowohl Fall-Through- als auch Sprungkanten und dekodiert jeden Pfad unabhängig.

Fünf-Ebenen-Bytecode-Verschlüsselung

  1. Ebene 1 (_0x3ca8): Statischer Murmur-XOR auf rohen Base64-Bytes, geschlüsselt mit 4008000571
  2. Ebene 2 (_0xece1): Pro-Funktion-XOR mit zwei Unterebenen: positionsabhängiger statischer Schlüssel + Code-Integritäts-Hash-Schlüssel
  3. Ebene 3 (_0x427d): Pro-Block-rollendes XOR. Jeder Verschlüsselungsblock (definiert durch fn.bl-Grenzen) erhält ein zusätzliches XOR, das vom Funktionsschlüssel und Blockindex abgeleitet wird. Block 0 ist beim ersten Zugriff unverschlüsselt; Blöcke 1+ sind verschlüsselt. Daher funktioniert ein linearer Disassembler für den ersten Block, erzeugt aber für nachfolgende Blöcke Müll.
  4. Opcode-Permutation: Fisher-Yates-Shuffle + positionsabhängiger Offset + blockabhängiger Offset
  5. Pro-Instruktion-Operanden-XOR: Operanden-Bytes werden mit einem aus der Instruktionsstartposition abgeleiteten Schlüssel XOR-verknüpft

Der CFG wendet alle fünf Ebenen nicht-destruktiv an (Operanden-XOR wird dynamisch berechnet, nicht in-place), sodass sich überlappende Instruktionsregionen nicht gegenseitig korrumpieren.

Superinstruction-Auflösung

Für jeden Superinstruction-Leader liest der CFG das Sub-Byte, schlägt den tatsächlichen Handler in super_groups.json nach und dekodiert den Operanden für den echten Opcode. Sprungziele von fusionierten Sprung-Opcodes (z.B. was wie ASSIGN_OP_VAR aussieht, aber tatsächlich JMP ist) werden korrekt verfolgt.

root@kitploit:~
node cfg.js        # alle Funktionen -> cfg_output/
node cfg.js 79     # einzelne Funktion nach stdout

Verifiziert gegen Browser-Ausführungsspuren: 600 Instruktionen über 14 Funktionen verfolgt, 0 Abweichungen in Stack-Deltas. Alle 34 eindeutigen Opcodes validiert. Superinstruction-Dispatch für alle 10 Leader-Gruppen bestätigt, die während der Initialisierung ausgeführt wurden.

Schritt 5: Bereiniger

Nimmt CFG-Ausgabe und führt Stack-Emulation durch, um Ausdruckskommentare zu erzeugen. Wandelt rohen Bytecode in lesbaren Pseudocode um.

root@kitploit:~
node cleaner.js 79     # fn79 nach stdout

Geht Instruktionen in der Reihenfolge durch und verfolgt einen virtuellen Stack. Jeder Push/Pop/Aufruf baut einen Ausdrucksstring auf:

root@kitploit:~
  0018  GET_VAR                 ; 0.0001
  001e  GET_VAR                 ; oder
  0024  PUSH_CONST              ; "_0x166"
  002b  CALL_METHOD_C           ; 0.0001._0x88(oder, "_0x166")
  ...
  0114  PUSH_CONST              ; "fontSize"
  011b  PUSH_CONST              ; "pdc"
  0122  CALL_METHOD_C           ; _0x18c.getHours("fontSize", "pdc")

Nur reines arithmetisches/Vergleichsrauschen auf einem leeren Stack wird unterdrückt. Alles andere wird beibehalten, da der CFG bereits Honig- und Padding-Instruktionen gefiltert hat.

112 Funktionen, 5435 Instruktionen behalten, 207 Rauschen unterdrückt. fn79 (der Fingerprint-Collector, 1109 Instruktionen) hat 85% Ausdruckskommentar-Abdeckung.

VM-Architektur

Stack-basierte VM mit 256-Slot-Stack, Scope-Chain, Try/Catch-Handler-Chain und For-In-Iterator-Stack. Die Dispatch-Schleife liest 2-Byte-LE-Opcodes, löst sie durch Permutation + Positions-XOR + Block-Offset auf, entschlüsselt Operanden in-place, führt den Handler aus und verschlüsselt dann Operanden wieder, sodass Bytecode nie vollständig entschlüsselt im Speicher liegt.

Wichtigste Erkenntnisse

  • RSA-Verschlüsselung des Fingerprint-Outputs unter Verwendung von BigInt, modPow, Exponent 65537
  • SVG-Rendering-Fingerprinting über getTotalLength() und getBBox() auf konstruierten Pfaden
  • Vollständige performance.timing-Wasserfallsammlung
  • Anti-Debugger-Erkennungsmarker (CC|CD-04|BREAKPOINT-005)
  • Funktion 79 ist der Haupt-Fingerprint-Collector (8361 Bytes, ~1200 Instruktionen)

Anmerkungen

KI wurde verwendet, um das Dokumentieren des Codes, das Schreiben von Werkzeugen und das Verfassen dieser Readme zu unterstützen.

Haftungsausschluss

Rein zu Bildungs-/Sicherheitsforschungszwecken. Keine Löser oder Umgehungen, nur die Dokumentation der Funktionsweise der VM, weil sie wirklich interessant ist.

Falls jemand von PerimeterX/HUMAN Security Bedenken bezüglich dieses Repos hat, kann er sich gerne melden: [email protected]

Tool herunterladen