Présentation technique et contexte
Jsxer est un décompilateur haute performance conçu pour le format binaire JSXBIN utilisé par Adobe ExtendScript. Son objectif principal est de convertir les fichiers .jsxbin, généralement chiffrés, en code JavaScript lisible. Développé principalement en C++, ce projet offre des liaisons Python et une bibliothèque dynamique. Il intègre une fonctionnalité expérimentale de désobfuscation pour les outils tels que Jsxblind, facilitant ainsi l'analyse de scripts et les études de sécurité au sein de l'écosystème Adobe Creative Suite.
Architecture modulaire du projet
La structure de Jsxer repose sur une conception modulaire et stratifiée, divisée en plusieurs composants clés :
├── source/
│ ├── interface_cli/ # Interface en ligne de commande
│ ├── bibliothèque/ # Implémentation de la bibliothèque dynamique
│ └── moteur_jsxer/ # Noyau du décompilateur
│ ├── types_noeuds/ # Définitions des nœuds AST (plus de 45 variantes)
│ ├── decodeur_binaire.cpp # Module de décodage
│ ├── nettoyage.cpp # Logique de désobfuscation
│ ├── lecteur_flux.cpp # Lecteur de données binaires
│ └── utilitaires.cpp # Fonctions auxiliaires
├── en_têtes/ # Fichiers d'en-tête partagés
├── liaisons_python/ # Intégration avec Python
└── jeux_essais/ # Suite de tests automatisés
Mécanisme d'identification du format binaire
Le système vérifie la signature du fichier JSXBIN pour en déterminer la version, ce qui guide le processus de décodage :
// Dans en_têtes/lecteur.h
#define SIGNATURE_JSXBIN_V10 "@JSXBIN@ES@1.0@"
#define SIGNATURE_JSXBIN_V20 "@JSXBIN@ES@2.0@"
#define SIGNATURE_JSXBIN_V21 "@JSXBIN@ES@2.1@"
#define LONGUEUR_SIGNATURE 15
enum class VersionJsxbin : uint16_t {
Invalide = static_cast<uint16_t>(-1),
v10 = 0x0100,
v20 = 0x0200,
v21 = 0x0201,
};
Système de nœuds pour l'arbre syntaxique abstrait (AST)
Jsxer implémente une hiérarchie complète de nœuds AST pour représenter toutes les constructions syntaxiques d'ExtendScript :
| Catégorie de nœud | Nombre d'implémentations | Rôle principal |
|---|---|---|
| Expressions | 15 | Gestion des opérations et valeurs |
| Déclarations et contrôles | 12 | Structures conditionnelles et boucles |
| Éléments XML | 5 | Support de la syntaxe XML étendue |
| Fondamentaux | 13 | Identifiants, littéraux et types primitifs |
Algorithmes de décodage binaire
Le décodeur utilise une approche de lecture en flux avec une machine à états. Les fonctions principales se trouvent dans le fichier decodeur_binaire.cpp :
// Dans en_têtes/decodeur_binaire.h
NœudOperationAst creer_noeud(LecteurFlux& flux);
InformationLigne extraire_infos_ligne(LecteurFlux& flux);
int extraire_nombre_litteral(LecteurFlux& flux);
std::chaine decodage_variant(LecteurFlux& flux);
Le processus suit ces étapes : validation de la signature, identification de la version, parsing séquentiel des octets, construction de l'AST, et génération du code source JavaScript.
Implémentation du moteur de désobfuscation
Le module nettoyage.cpp fournit une fonctionnalité expérimentale pour inverser les obfuscations appliquées par Jsxblind :
// Dans en_têtes/nettoyage.h
bool doit_remplacer_symbole(ContexteNettoyage& contexte,
const OctetsSymbole& symbole,
bool est_operateur);
L'algorithme s'appuie sur une analyse contextuelle et la substitution de noms de variables et de fonctions altérés.
Gestion de la mémoire et optimisations
Les nœuds de l'AST sont gérés via des pointeurs intelligents pour prévenir les fuites mémoire :
// Dans en_têtes/types_noeuds/NœudAst.h
using NœudOperationAst = std::shared_ptr<NœudAst>;
Procédure de construction et de configuration
Jsxer s'appuie sur CMake pour une compilation multiplateforme. Voici un extrait du fichier de configuration principal :
# Extrait de CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(jsxer VERSION 1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
Les étapes de construction typiques incluent le clonage du dépôt, la configuration avec CMake, la compilation, et l'installation des exécutables. Les liaisons Python sont chargées dynamiquement selon le système d'exploitation :
# Dans liaisons_python/decompileur.py
def chemin_bibliotheque():
systeme = platform.system().lower()
if systeme == 'windows':
chemin = os.path.join(chemin_base, 'jsxer-bib.dll')
elif systeme == 'linux':
chemin = os.path.join(chemin_base, 'jsxer-bib.so')
elif systeme == 'darwin':
chemin = os.path.join(chemin_base, 'jsxer-bib.dylib')
return chemin
Scénarios d'utilisation avancée
Pour le traitement par lots de fichiers JSXBIN, un script Python peut être utilisé pour automatiser la conversion :
import os
from decompileur import convertir_jsxbin
def traitement_par_lots(dossier_entree, dossier_sortie):
for nom_fichier in os.listdir(dossier_entree):
if nom_fichier.endswith('.jsxbin'):
chemin_entree = os.path.join(dossier_entree, nom_fichier)
chemin_sortie = os.path.join(dossier_sortie,
nom_fichier.replace('.jsxbin', '.js'))
with open(chemin_entree, 'rb') as fichier:
donnees = fichier.read()
code_js = convertir_jsxbin(donnees, nettoyer=True)
with open(chemin_sortie, 'w', encoding='utf-8') as fichier:
fichier.write(code_js)
Jsxer peut également s'intégrer dans des workflows automatisés via des scripts shell pour la compilation, le décodage et la validation des sorties.
Recommandations pour l'optimisation des performances
Pour les fichiers volumineux, Jsxer privilégie une lecture par flux pour limiter l'empreinte mémoire. L'implémentation dans lecteur_flux.cpp illustre cette approche :
// Dans source/moteur_jetxer/lecteur_flux.cpp
class LecteurFlux {
public:
LecteurFlux(const std::chaine& donnees_brutes, bool appliquer_nettoyage);
Jeton prochain_jeton();
bool verifier_signature();
private:
std::chaine _tampon;
size_t _position_courante = 0;
// ... autres membres
};
Des comparaisons de performance indiquent des gains significatifs par rapport aux méthodes traditionnelles, avec des améliorations allant jusqu'à 74% sur des fichiers de 10 Mo. Pour les opérations massives, l'utilisation de threads avec des verrous d'exclusion est conseillée :
from concurrent.futures import ThreadPoolExecutor
import threading
class DecompileurThreadSafe:
def __init__(self):
self._verrou = threading.Lock()
def decompiler_donnees(self, jsxbin_brut):
with self._verrou:
return convertir_jsxbin(jsxbin_brut)
Intégration et extensibilité
Jsxer expose une API C++ concise pour une intégration directe dans d'autres projets :
// Dans en_têtes/jsxer.h
namespace jsxer {
int convertir_vers_js(const std::chaine& entree, std::chaine& sortie, bool nettoyer = false);
int tester_conversion(const std::chaine& entree, std::chaine& sortie, bool nettoyer = false);
}
Les développeurs peuvent étendre le système de nœuds AST en créant des sous-classes personnalisées pour des constructions syntaxiques spécifiques. Le projet inclut également un framework de tests pour valider les fonctionnalités, comme illustré dans cet exemple pour les expressions de tableaux.
Défis techniques et orientations futures
La feuille de route du projet comprend plusieurs axes d'amélioration : l'implémentation native de chaînes UTF-16, la corerction des séquences de paramètres de fonction, la précision d'affichage des nombres à virgule flottante, l'enrichissement de la suite de tests, le renforcement de la gestion des erreurs, et une étude approfondie des nœuds XML. Les contributeurs sont encouragés à fournir des benchmarks de performance lors des modifications liées à l'optimisation.
Les principaux défis résident dans la gestion complète du standard UTF-16, la réparation des déclarations utilisant des expressions virgulées, l'assurance d'une sortie numérique exacte, et le support correct de certains nœuds XML.