
Boîte à outils de désobfuscation statique pour le bytecode JavaScript V8 compilé, axée sur les charges utiles JSCeal. Fournit des filtres basés sur des motifs, le déplissement du flux de contrôle, la reconstruction de chaînes et un renommage de fonctions assisté par LLM en option pour l’analyse.
Cet outil est dédié à la désobfuscation statique du bytecode JavaScript V8 compilé qui a été protégé avec javascript-obfuscator.
Il opère sur le pseudocode produit par View8, plutôt que sur le code source JavaScript d'origine. Le projet a été développé et testé sur des charges utiles JSCeal.
Les filtres sont pilotés par des motifs et destinés principalement à servir de boîte à outils de recherche et d'implémentation de référence. L'outil n'est pas un désobfuscateur JavaScript à usage général, ne reconstruit pas le code source d'origine et ne produit pas de JavaScript exécutable. Sa sortie reste du pseudocode View8 destiné à l'inspection statique, la recherche, la comparaison et l'exportation d'arbres de fonctions.
pickle de Python. Charger un fichier .pkl malveillant ou non fiable peut exécuter du code. Ne chargez que des fichiers sérialisés que vous avez générés localement avec View8.requirements.txt ;brotli pour le flux de travail de dépaquetage par lots sous Linux ;Créez un environnement Python isolé :
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install --upgrade pip
python3 -m pip install -r requirements.txt
Le backend OpenAI dans deobf_ai.py nécessite en outre le package Python OpenAI :
python3 -m pip install openai
Le backend Anthropic utilise l'API HTTP via requests. Le backend Ollama attend un serveur Ollama accessible.
La charge utile JSCeal d'origine app.jsc est compressée en Brotli. Sous Linux, elle peut être décompressée avec l'utilitaire brotli :
brotli -d app.jsc -o app.decompressed.jsc
Le flux de travail par lots sous scripts/ effectue cette étape avec scripts/unpack_all.sh.
Sous Windows, ou lorsque l'utilitaire en ligne de commande brotli n'est pas disponible, l'assistant Node.js inclus peut être utilisé comme repli. Il décompresse uniquement l'entrée et ne l'exécute pas :
node Utils/decompress-jsc.js app.jsc
Il écrit :
app.jsc.decompressed.jsc
Le cache de code V8 est spécifique à une version. Utilisez un désassembleur construit pour la même version de V8 que la charge utile.
Les échantillons JSCeal utilisés pendant le développement étaient basés sur V8 10.2.154.26. Les désassembleurs par défaut d'une compilation V8 sans rapport ne fonctionneront pas correctement.
L'arborescence source contient le code source du désassembleur et les correctifs V8 requis sous :
Utils/disasm/v8dasm.cpp
Utils/disasm/patches/
Un binaire Linux précompilé est distribué avec la version du projet, tandis que l'arborescence source contient le code source et les correctifs nécessaires pour le recompiler. Une description détaillée est disponible sur le Wiki du projet. Après avoir obtenu ou compilé le v8dasm correspondant, exécutez :
/path/to/v8dasm app.decompressed.jsc > app.jsc.disasm.txt
Fournissez le fichier désassemblé à view8.py et produisez à la fois une sortie sérialisée pour un traitement ultérieur et un pseudocode lisible par l'humain :
mkdir -p decompiled
python3 View8/view8.py \
--input_format disassembled \
--inp app.jsc.disasm.txt \
--normalize \
--out decompiled/app.dec.txt \
--export_format decompiled serialized
Cela produit :
decompiled/app.dec.txt
decompiled/app.dec.pkl
L'option --normalize rend les identifiants de fonctions générés reproductibles entre les exécutions répétées de désassemblage et de décompilation.
Il existe des filtres séparés pour les différentes couches d'obfuscation. Ils peuvent être appliqués ensemble à la sortie sérialisée de View8 avec deobf_all.py :
mkdir -p deobfuscated
python3 deobf_all.py \
--inp decompiled/app.dec.pkl \
--out deobfuscated/app.deobf.txt \
--export_format decompiled serialized
Le filtre de chaînes par défaut est la variante 2, utilisée par la majorité des charges utiles JSCeal analysées. Pour sélectionner explicitement le schéma de chaînes plus simple, ajoutez :
--str_deobf 1
Les sorties typiques sont :
deobfuscated/app.deobf.txt
deobfuscated/app.deobf.pkl
deobfuscated/app.deobf.txt.strings.txt
decompiled/app.dec.resolved_funcs.csv
Le CSV des fonctions résolues est un cache spécifique à l'échantillon. Lorsqu'il est absent, la passe de chaînes récupère la configuration de décodeur requise, écrit le CSV et continue la désobfuscation des chaînes dans la même exécution. Les exécutions ultérieures réutilisent le cache et sont normalement plus rapides.
Ne réutilisez pas un CSV de fonctions résolues avec une charge utile décompilée différente.
Après l'application de tous les filtres de désobfuscation structurelle, deobf_ai.py peut proposer des noms qui décrivent le comportement des fonctions. Il prend en charge les backends Anthropic, OpenAI et Ollama.
Transmettez le modèle explicitement afin que les exécutions restent reproductibles.
export ANTHROPIC_API_KEY='...'
python3 deobf_ai.py \
--inp deobfuscated/app.deobf.pkl \
--out deobfuscated/app.renamed.txt \
--llm_backend anthropic \
--model '<model-id>' \
--export_format decompiled serialized
export OPENAI_API_KEY='...'
python3 deobf_ai.py \
--inp deobfuscated/app.deobf.pkl \
--out deobfuscated/app.renamed.txt \
--llm_backend openai \
--model '<model-id>' \
--export_format decompiled serialized
python3 deobf_ai.py \
--inp deobfuscated/app.deobf.pkl \
--out deobfuscated/app.renamed.txt \
--llm_backend ollama \
--model '<local-model>' \
--ollama_url http://localhost:11434 \
--export_format decompiled serialized
En mode par défaut, le renommeur construit un arbre d'appels directs à partir de la fonction d'entrée et ne renomme que les fonctions atteintes par des appels. Ajoutez --greedy pour inclure toutes les références de fonctions visibles, y compris les callbacks et les gestionnaires assignés.
Le CSV à deux colonnes généré sert de cache et permet à une exécution interrompue de continuer. Sélectionnez un cache existant explicitement avec --csv :
python3 deobf_ai.py \
--inp deobfuscated/app.deobf.pkl \
--out deobfuscated/app.renamed.txt \
--csv deobfuscated/app.deobf.renamed_funcs.greedy.example-model.csv \
--llm_backend anthropic \
--model '<model-id>' \
--greedy \
--export_format decompiled serialized
En mode normal, le CSV est traité comme un cache potentiellement partiel. Les noms en cache sont appliqués en premier, les fonctions déjà couvertes par le cache sont retirées de l'arbre d'appels ou de références sélectionné, et le LLM n'est invoqué que pour les fonctions qui restent non résolues. Si le CSV couvre entièrement cet arbre, aucune clé API ni connexion LLM n'est requise. S'il ne couvre qu'une partie de l'arbre, le backend sélectionné est initialisé et les nouvelles correspondances générées sont ajoutées au même CSV.
Utilisez le même mode d'arbre que celui utilisé lors de la création du CSV. Un CSV produit à partir d'une exécution --greedy nécessite normalement à nouveau --greedy si l'objectif est de continuer cette exécution plutôt que de réutiliser uniquement le sous-ensemble d'appels directs.
Utilisez --apply-csv-only lorsque le CSV contient déjà les étiquettes que vous souhaitez appliquer, y compris les correspondances révisées, éditées, importées ou rebasées :
python3 deobf_ai.py \
--inp deobfuscated/app.deobf.pkl \
--out deobfuscated/app.renamed.txt \
--csv renamed_functions.normalized.csv \
--apply-csv-only \
--export_format decompiled serialized
Ce mode :
--csv explicite ;--func.Les lignes dont l'identifiant de fonction d'origine n'existe pas dans l'entrée sont ignorées. La commande échoue lorsque le CSV ne contient aucune correspondance applicable au fichier chargé.
Utilisez --func avec l'identifiant complet exact de la fonction pour demander une analyse sémantique ciblée d'une fonction désobfusquée :
python3 deobf_ai.py \
--inp deobfuscated/app.deobf.pkl \
--func func_example_0x100001234 \
--llm_backend anthropic \
--model '<model-id>'
L'analyse comprend un nom proposé, un résumé du comportement, les entrées et la valeur de retour, les effets secondaires, la logique étape par étape, le pseudocode nettoyé, les preuves à l'appui et les incertitudes non résolues. La fourniture de --csv ajoute les noms sémantiques en cache comme contexte pour les références à l'intérieur de la fonction sélectionnée sans modifier le corpus chargé. Utilisez --analysis-out analysis/function.md pour enregistrer le rapport au format Markdown. Les correspondances approximatives ne sont affichées qu'à titre de suggestions ; l'identifiant de fonction demandé doit correspondre exactement.
Utilisez --help pour les options contrôlant la température, le traitement par lots, le mode de réflexion Anthropic, les limites de jetons et les chemins CSV personnalisés.
Les noms générés par LLM sont des aides à la navigation, pas des preuves. Vérifiez-les toujours par rapport au corps désobfusqué.
La sortie JSCeal désobfusquée est généralement très volumineuse. Rechargez la sortie sérialisée dans View8 et divisez-la en arbres de fonctions plus petits.
À ce stade, ajoutez --scope 0. La propagation de portée a déjà été effectuée par le désobfuscateur, et la répéter peut propager des valeurs de manière incorrecte.
Un arbre basé sur les relations de déclarants :
python3 View8/view8.py \
--input_format serialized \
--inp deobfuscated/app.deobf.pkl \
--out trees/declarers \
--export_format decompiled \
--tree start \
--scope 0
Une vue d'ensemble compacte des appels directs :
python3 View8/view8.py \
--input_format serialized \
--inp deobfuscated/app.deobf.pkl \
--out trees/calls \
--export_format decompiled \
--tree start \
--scope 0 \
--split_mode calls \
--inline_depth 1 \
--split_depth 5
Un arbre de références plus large, incluant les callbacks et les gestionnaires assignés :
python3 View8/view8.py \
--input_format serialized \
--inp deobfuscated/app.deobf.pkl \
--out trees/references \
--export_format decompiled \
--tree start \
--scope 0 \
--split_mode references \
--inline_depth 1 \
--split_depth 3
Différents fichiers JSC peuvent utiliser différents modes d'obfuscation des chaînes.
Le mode le plus simple observé utilise un décalage d'index et est géré par deobf_str1.py. Le mode JSCeal le plus courant utilise Base64, RC4, des chaînes fragmentées et des index transformés ; il est géré par deobf_str2.py.
Le pipeline complet sélectionne la variante 2 par défaut. Les filtres peuvent également être exécutés indépendamment pour les tests.
deobf_str2.pyUtilisez --help pour afficher tous les modes et options disponibles :
python3 deobf_str2.py --help
Une exécution directe de désobfuscation des chaînes peut être lancée avec :
python3 deobf_str2.py \
--inp decompiled/app.dec.pkl \
--out work/app.strings.txt \
--export_format decompiled serialized
Pendant l'exécution, le script identifie les fonctions de décodage de chaînes, charge les configurations en cache valides, résout celles manquantes, enregistre le CSV résultant et décode les chaînes. Une seconde exécution n'est pas nécessaire.
Lorsque deobf_str2.py est utilisé directement, son nom de CSV par défaut est resolved_funcs.csv. Sélectionnez un chemin spécifique à l'échantillon avec --csv ou -c :
python3 deobf_str2.py \
--inp decompiled/app.dec.pkl \
--out work/app.strings.txt \
--csv decompiled/app.dec.resolved_funcs.csv \
--export_format decompiled serialized \
--verbosity 1
Lors de l'enchaînement de filtres individuels, conservez la sortie sérialisée entre les étapes afin que les passes ultérieures puissent continuer à opérer sur les objets View8.
deobf_all.py applique les étapes suivantes dans l'ordre :
Le renommage de fonctions assisté par LLM est facultatif et est exécuté séparément après la désobfuscation structurelle.
Le dépôt comprend un flux de travail d'assistance complet sous scripts/. Tous les scripts sont conservés dans un seul répertoire et utilisent la même configuration centralisée.
scripts/config.sh chemins partagés des outils et de l'espace de travail
scripts/copy_payloads.sh collecte et nommage MD5 des fichiers JSCeal app.jsc
scripts/unpack_all.sh décompression Brotli
scripts/disasm_all.sh désassemblage V8 par lots
scripts/decompile_all.sh décompilation View8 par lots
scripts/deobfuscate_all.sh désobfuscation par lots avec un journal combiné
scripts/run_unattended.sh désobfuscation et validation détachées
scripts/collect_output.sh collecte des caches de décodeurs et des listes de chaînes
Le scripts/config.sh fourni contient des chemins issus d'un environnement d'exemple :
JSC_DEOBF_ROOT="$HOME/jsc_deobfuscator"
V8DASM="$HOME/code/v8/v8dasm"
Modifiez ce fichier une fois pour configurer le chemin d'installation, le désassembleur V8 correspondant, les répertoires de travail, les commandes externes, les chemins de journaux et la disposition de collecte. L'espace de travail par défaut est le répertoire à partir duquel le script d'assistance est lancé.
Chaque valeur peut également être remplacée par une variable d'environnement. JSC_HELPER_CONFIG peut sélectionner un fichier de configuration différent.
Une exécution par lots typique est :
scripts/copy_payloads.sh
scripts/unpack_all.sh
scripts/disasm_all.sh
scripts/decompile_all.sh
scripts/deobfuscate_all.sh
scripts/collect_output.sh
Les scripts préservent les conventions utilisées pour le corpus JSCeal, notamment le traitement des fichiers app.jsc découverts comme des charges utiles compressées en Brotli et leur nommage par MD5. Consultez scripts/README.md avant d'appliquer le flux de travail à des échantillons sans rapport.
Pour un lot long, scripts/run_unattended.sh lance la désobfuscation avec nohup, écrit des fichiers d'horodatage de journal, de PID et de statut, et valide chaque sortie générée pour détecter les références non résolues aux fonctions de décodeurs de chaînes en cache :
scripts/run_unattended.sh
Des échantillons sélectionnés peuvent être fournis explicitement :
scripts/run_unattended.sh \
decompiled/sample1.dec.pkl \
decompiled/sample2.dec.pkl
View8/ décompilateur View8 et exportateur d'arbres de fonctions
Utils/decompress-jsc.js repli de décompression Brotli pour Windows
Utils/disasm/v8dasm.cpp code source du désassembleur V8
Utils/disasm/patches/ correctifs V8 requis par le désassembleur
Utils/check_unresolved_decoder_references.py
assistant de validation de sortie
deobf_all.py pipeline de désobfuscation par défaut complet
deobf_str1.py filtre simple de décalage d'index de chaînes
deobf_str2.py filtre de chaînes RC4/Base64 avec récupération d'index
deobf_scope2.py propagation de la portée et du dictionnaire
deobf_unflattener.py dé-aplatissement du flux de contrôle
deobf_replace_ops.py remplacement des proxies et des wrappers d'opérations
deobf_globals.py propagation globale
deobf_inline_temporaries.py nettoyage final conservateur
deobf_ai.py renommage de fonctions assisté par LLM (facultatif)
scripts/ assistants de validation et de traitement par lots configurables
Chaque passe principale peut être exécutée séparément pour les tests. Exécutez le script sélectionné avec --help pour voir son interface complète :
python3 deobf_str1.py --help
python3 deobf_str2.py --help
python3 deobf_scope2.py --help
python3 deobf_unflattener.py --help
python3 deobf_replace_ops.py --help
python3 deobf_globals.py --help
python3 deobf_inline_temporaries.py --help
javascript-obfuscator. De nouvelles variantes peuvent nécessiter des détecteurs ou des transformations supplémentaires.Le pipeline a été testé par régression sur le corpus JSCeal utilisé dans la recherche associée. Les vérifications de base de la version incluent :
python3 -m compileall -q .
python3 deobf_all.py --help
python3 deobf_str2.py --help
python3 deobf_ai.py --help
python3 View8/view8.py --help
Pour chaque échantillon du corpus, vérifiez que l'exécution :
.pkl et .txt ;Le script d'assistance sans surveillance automatise la validation finale des références de décodeurs.
javascript-obfuscator.Le code source du JSC Deobfuscator écrit pour ce projet est sous licence
GNU General Public License, version 2 ou (à votre discrétion) toute version ultérieure
(GPL-2.0-or-later). Voir LICENSE pour le texte complet de la licence.
Copyright (C) 2026 Aleksandra "Hasherezade" Doniec @ Check Point Research.
Le sous-module View8 est un projet séparé. Le matériel de désassembleur dérivé de tiers sous Utils/disasm/ conserve sa provenance existante et n'est pas
sous nouvelle licence par l'avis de copyright ci-dessus.