Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
px-vm — Boîte à outils de rétro-ingénierie pour la machine virtuelle bytecode de PerimeterX, comprenant un désassembleur basé sur CFG, un pipeline de déchiffrement à 5 couches, une reconstruction de table d'opcodes, et un nettoyeur d'émulation de pile pour la recherche en sécurité sur la détection des bots par empreinte numérique. | Kitploit
Outils/GitHubGitHub/b9ph0met/px-vm
Analyse Dynamique (Sandboxing)Évasion IDS/IPSRétro-ingénierieSécurité WebAnalyse de MalwareCryptographieAnalyse de BinairesArticles et RechercheApprentissage et ÉducationAnti-BotUsurpation d'Empreinte Numérique
5514il y a 4 moisVérifié par Kitploit

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →

À propos

Boîte à outils de rétro-ingénierie pour la machine virtuelle bytecode de PerimeterX, comprenant un désassembleur basé sur CFG, un pipeline de déchiffrement à 5 couches, une reconstruction de table d'opcodes, et un nettoyeur d'émulation de pile pour la recherche en sécurité sur la détection des bots par empreinte numérique.

GitHub
b9ph0met/px-vm

px-vm

Voir le dépôt
Partager

Analyse de la VM Auditor de PerimeterX

Résumé

Ce dépôt documente le rétro-ingénierie de auditor.js de PerimeterX, une machine virtuelle à bytecode utilisée comme couche secondaire d'empreinte digitale dans la chaîne de détection de bots de PX. Cette analyse couvre :

  • Extraction du bytecode et pipeline de déchiffrement à 5 couches
  • Reconstruction de la table d'opcodes (107 de base + 40 leurres + 24 de remplissage + 16 groupes de super-instructions)
  • Déchiffrement du pool de constantes (1230 entrées, 1095 chaînes chiffrées)
  • Désassembleur basé sur un graphe de flot de contrôle (CFG) avec résolution de sous-dispatch de super-instructions
  • Nettoyeur par émulation de pile produisant un pseudo-code lisible
  • Techniques d'anti-analyse : opcodes leurres, instructions qui se chevauchent, hachage d'intégrité du code

Note : Ce dépôt ne couvre qu'une seule version (statique) de la VM et est destiné à la recherche en sécurité et à des fins d'analyse. Il n'inclut pas de solveurs dynamiques ni d'implémentations de solveurs de production.

Contexte

Le jeudi 2 avril 2026, PerimeterX a déployé une nouvelle VM à bytecode dans le cadre de leur pipeline de détection de bots.

Contenu

auditor.js ne ressemble pas à un script de capteur PX normal. Au lieu des habituelles recherches de propriétés obfusquées et des fonctions de collecte :

  • 8 chaînes base64 massives (_fg0 à _fg7), le programme VM chiffré réparti entre des variables
  • Une fonction de déchiffrement XOR (_dp) avec une clé par site (_pk) qui décompresse le JSON du programme
  • Un mélange de Fisher-Yates qui permute la table d'opcodes, de sorte que les valeurs de bytecode diffèrent d'une build à l'autre
  • Une boucle de dispatch avec 107+ gestionnaires de cas, l'interpréteur de la VM
  • Des calculs BigInt pour le chiffrement RSA de la sortie de l'empreinte
  • Un hachage d'intégrité du code (_0x8df7) qui hache le propre code source de la VM pour dériver une clé de déchiffrement, de sorte que toute modification interrompt silencieusement le déchiffrement du bytecode

Étape 1 : Extraction du bytecode

Le programme de la VM est réparti sur 8 variables, concaténées, puis déchiffrées via _dp() à l'aide d'un chiffrement XOR par site avec la clé _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("");
}

Le résultat est un objet JSON avec des noms de clés obfusqués de deux caractères (par ex., "uo" pour la graine, "dk" pour le nonce). Une table de correspondance les convertit en noms standards.

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

Structure du programme

Étape 2 : Déchiffrement des constantes

Les 1095 chaînes constantes sont toutes chiffrées avec deux couches :

Couche 1 : XOR statique murmur avec la clé 4008000571, dépendante de la position.

Couche 2 : XOR de flux PRNG utilisant un LCG glibc, initialisé en combinant la graine du programme avec l'index de chaque constante via le hachage multiplicatif de Knuth.

Avant le déchiffrement, la graine est XORée avec une empreinte environnementale (_0xaf48), un masque de 8 bits calculé en interrogeant les API du navigateur :

Pour Chrome : _0xaf48 = 0b01111111 = 127, soit une graine effective = 12755 ^ 127 = 12716.

Cela signifie que le même programme produit des résultats de déchiffrement différents selon l'environnement. L'exécuter sous Node.js vs Chrome vs Firefox donne des graines différentes.

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

Ce que révèlent les constantes

Les chaînes déchiffrées nous indiquent exactement ce que la VM prend comme empreinte :

Empreinte du navigateur : screenWidth, screenHeight, innerWidth, innerHeight, devicePixelRatio, colorDepth, platform, userAgent, language, timezone, timezoneOffset, forcedColors, highContrast

Chronométrage des performances : navigationStart, domComplete, domLoading, fetchStart, requestStart, responseEnd, secureConnectionStart, serverTiming

Cryptographie RSA : BigInt, modPow, AQAB (65537 en base64), modulusLength, shiftLeft, shiftRight, getRandomValues

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

Noms de champs PX : mtr, tst, mst, enc, sbx, fstec, pdc, prb, wvi, wva, pti, dis, los, cv, sc, jd, , ,

Références de points de terminaison : https://fst-ec.perimeterx.net/?id=

Anti-débogueur : _CMP_RCX_07;_JNZ_0x0A_EB_CC, CC|CD-04|BREAKPOINT-005

Étape 3 : Table d'opcodes

107 opcodes de base couvrant l'ensemble du langage JavaScript, plus du bruit généré dynamiquement :

40 opcodes leurres sont des implémentations alternatives des opérations arithmétiques/comparaisons utilisant des expressions mathématiquement équivalentes mais syntaxiquement différentes. ADD pourrait apparaître comme (a^b) + 2*(a&b) ou -((-a)-b) ou a-(-b). Chaque opcode de base peut avoir jusqu'à 3 variantes, générées de manière déterministe à partir de la graine. Une simple instruction ADD peut apparaître sous 4 valeurs de bytecode différentes au sein du même programme, brisant les approches par reconnaissance de motifs.

24 opcodes de remplissage sont alloués dans la permutation mais n'ont pas de gestionnaires et ne sont jamais émis. Ils existent pour agrandir l'espace des opcodes et rendre le mélange plus difficile à inverser.

16 groupes de super-instructions sont la fonctionnalité anti-analyse la plus importante. Lorsque la boucle de dispatch résout un opcode en un leader de super-instruction, le gestionnaire lit un octet supplémentaire dans le flux de bytecode et se dirige vers un sous-gestionnaire. Le sous-gestionnaire peut être une opération complètement différente :

La table d'opcodes est mélangée via Fisher-Yates avec une graine effective, de sorte que les valeurs de bytecode diffèrent d'une build à l'autre.

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

Étape 4 : Constructeur de CFG

Le cœur de la boîte à outils. cfg.js construit un graphe de flot de contrôle en suivant tous les chemins d'exécution à partir de PC=0, en décodant chaque instruction avec le contexte de chiffrement correct.

Pourquoi pas un désassembleur linéaire

PX utilise des instructions qui se chevauchent aux limites des blocs. Les mêmes octets se décodent comme des opérandes sur un chemin d'exécution et comme des opcodes sur un autre, selon le contexte de chiffrement du bloc. Un parcours linéaire décode chaque position d'octet une fois et manque le chemin alternatif. Le CFG suit à la fois les arêtes de passage et de saut, décodant chaque chemin indépendamment.

Chiffrement du bytecode à cinq couches

  1. Couche 1 (_0x3ca8) : XOR statique murmur sur les octets bruts base64, avec la clé 4008000571
  2. Couche 2 (_0xece1) : XOR par fonction avec deux sous-couches : clé statique dépendante de la position + clé de hachage d'intégrité du code
  3. Couche 3 (_0x427d) : XOR roulant par bloc. Chaque bloc de chiffrement (défini par les limites fn.bl) reçoit un XOR supplémentaire dérivé de la clé de la fonction et de l'index du bloc. Le bloc 0 n'est pas chiffré lors du premier accès ; les blocs 1+ sont chiffrés. C'est pourquoi un désassembleur linéaire fonctionne pour le premier bloc mais produit du charabia pour les blocs suivants.
  4. Permutation des opcodes : Mélange Fisher-Yates + décalage dépendant de la position + décalage dépendant du bloc
  5. XOR des opérandes par instruction : Octets d'opérande XORés avec une clé dérivée de la position de début de l'instruction

Le CFG applique les cinq couches de manière non destructive (le XOR des opérandes est calculé à la volée, pas sur place), de sorte que les régions d'instructions qui se chevauchent ne se corrompent pas mutuellement.

Résolution des super-instructions

Pour chaque leader de super-instruction, le CFG lit le sous-octet, cherche le gestionnaire réel dans super_groups.json, et décode l'opérande pour le véritable opcode. Les cibles de saut provenant d'opcodes de saut fusionnés (par ex., ce qui ressemble à ASSIGN_OP_VAR mais est en fait JMP) sont correctement suivies.

root@kitploit:~
node cfg.js        # toutes les fonctions -> cfg_output/
node cfg.js 79     # une seule fonction vers stdout

Vérifié par rapport aux traces d'exécution du navigateur : 600 instructions tracées dans 14 fonctions, 0 divergences dans les deltas de pile. Les 34 opcodes uniques validés. Le dispatch des super-instructions confirmé correct pour les 10 groupes leaders qui se sont exécutés lors de l'initialisation.

Étape 5 : Nettoyeur

Prend la sortie du CFG et exécute une émulation de pile pour produire des commentaires d'expression. Transforme le bytecode brut en pseudo-code lisible.

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

Parcourt les instructions dans l'ordre, en suivant une pile virtuelle. Chaque push/pop/appel construit une chaîne d'expression :

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

Seul le bruit arithmétique/de comparaison pur sur une pile vide est supprimé. Tout le reste est conservé car le CFG a déjà filtré les leurres et le remplissage.

112 fonctions, 5435 instructions conservées, 207 bruits supprimés. fn79 (le collecteur d'empreintes, 1109 instructions) a une couverture de commentaires d'expression de 85%.

Architecture de la VM

VM à base de pile avec une pile de 256 emplacements, une chaîne de portée, une chaîne de gestionnaires try/catch, et une pile d'itérateurs for-in. La boucle de dispatch lit des opcodes LE de 2 octets, les résout via permutation + XOR de position + décalage de bloc, déchiffre les opérandes sur place, exécute le gestionnaire, puis rechiffre les opérandes afin que le bytecode ne soit jamais complètement déchiffré en mémoire.

Résultats clés

  • Chiffrement RSA de la sortie de l'empreinte utilisant BigInt, modPow, exposant 65537
  • Empreinte de rendu SVG via getTotalLength() et getBBox() sur des chemins construits
  • Collecte complète de la cascade performance.timing
  • Marqueurs de détection anti-débogueur (CC|CD-04|BREAKPOINT-005)
  • La fonction 79 est le collecteur principal d'empreintes (8361 octets, ~1200 instructions)

Remarques

L'IA a été utilisée pour aider à documenter le code, écrire les outils et rédiger ce README.

Avertissement

À des fins purement éducatives / de recherche en sécurité. Pas de solveurs ni de contournements, juste une documentation du fonctionnement de la VM car c'est réellement intéressant.

Si quelqu'un de PerimeterX/HUMAN Security a des préoccupations concernant ce dépôt, n'hésitez pas à me contacter : [email protected]

Télécharger l’outil
ChampDescription
sGraine (12755), pilote toutes les opérations cryptographiques
nNonce (1603730985), randomisation par programme
gDrapeau générateur, active la couche de déchiffrement par hachage d'intégrité
xDrapeau chiffré, les constantes sont chiffrées par XOR
cPool de constantes, 1230 entrées
fFonctions, 112 entrées avec bytecode chiffré
ePoint d'entrée, index de fonction 0
BitTestChrome
0typeof window.matchMedia === "function"1
1document.elementFromPoint existe1
2typeof window.requestAnimationFrame === "function"1
3typeof window.getComputedStyle === "function"1
4CSS.supports existe1
5navigator.sendBeacon existe1
6document.execCommand existe1
7process.versions.node existe (Node.js)0
ads
enve
init
Leader résolu enSous-octetExécute réellement
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