
Désovfuscateur source-à-source pour VBScript et VBA avec évaluation partielle
vbSparkle est un désobfuscateur Visual Basic multi-plateforme, de source à source, basé sur l'évaluation partielle et principalement dédié à l'analyse de code malveillant écrit en VBScript et en VBA (macro Office).
Il est écrit en C# natif et fournit une bibliothèque .Net Standard; il fonctionne sur Windows, Linux, macOS, etc.
L'analyse de Visual Basic Script et de VBA est effectuée grâce à la grammaire et aux analyseurs lexicaux d'ANTLR.
Install-Package vbSparkle
dotnet add package vbSparkle
Une fois que vous avez ajouté la bibliothèque vbSparkle à votre projet, il vous suffit d'appeler la fonction PrettifyEncoded de la classe statique VbPartialEvaluator :
string obfuscatedVB = "msgbox(chr(65)&chr(66)&(chr(67))"
string deobfuscated = VbPartialEvaluator.PrettifyEncoded(obfuscatedVB);
... retournera MsgBox("ABC").
Le projet joint vbSparkle.CLI est un exemple d'utilisation de vbSparkle en tant que CLI.
L'exemple actuel prend soit un chemin en argument, soit un binaire complet dans StdIn, et renvoie le résultat désobfusqué.

Le projet joint vbSparkle.Web est un exemple d'utilisation de vbSparkle dans une interface Web.

L'obfuscation de code VBScript et VBA est populaire parmi les attaquants et permet de contourner les mesures de détection, les antivirus, les pare-feu, les EDR, et rend l'analyse des malwares plus difficile.
Ainsi, il est courant de voir du VBScript obfusqué dès le tout premier stade d'une compromission de machine.
Une technique typique d'obfuscation de code VBScript consiste à encapsuler le script dans une chaîne obfusquée et à l'exécuter via la fonction Execute("...code..." function) après qu'elle a été décodée / évaluée.
Voyons un exemple :
Execute(chr( 456127/3833 ) & chr( 817650/7110 ) & chr( 759429/7671 ) & chr( 834138/7317 ) & chr( 57960/552 ) & chr( 2549-2437 ) & chr( 1078568/9298 ) & chr( 5143-5097 ) & chr( -8682+8783 ) & chr( 529-430 ) & chr( 673088/6472 ) & chr( 761682/6862 ) & chr( 302336/9448 ) & chr( 9427-9393 ) & chr( 410176/3944 ) & chr( -5271+5372 ) & chr( 796608/7376 ) & chr( 2896-2788 ) & chr( 318792/2872 ) & chr( 1076-1042 ) & vbcrlf )
Ici, l'attaquant a utilisé :
chr(CHAR_CODE) pour évaluer un caractère de chaîne (par ex. chr(65) = "A")456127/3833) pour évaluer la valeur du caractère& pour assembler tous les caractères décodésExecute(..) pour exécuter le script décodé.Si nous suivons l'exécution du script :
456127/3833 => 119chr(119) => "w"817650/7110 => 115chr(115) => "s""w" & "s" => "ws"Execute("ws.....Voici le script original :
wscript.echo "hello"
Une désobfuscation manuelle devient très difficile et chronophage sur de grands scripts.
Une technique de désobfuscation courante avec VBScript consiste simplement à remplacer la fonction Execute() par WScript.Echo(, puis à exécuter le script pour obtenir la sortie.
Cette dernière technique est simple mais présente quelques inconvénients :
Et, au moment où ce projet a démarré, il n'existait aucune alternative à ces deux techniques.
L'évaluation partielle n'est pas un concept nouveau ; elle est surtout utilisée par les compilateurs pour l'optimisation de code dans les processus de pré-compilation (par ex. https://en.wikipedia.org/wiki/Partial_evaluation).
Un évaluateur partiel lit un programme (appelé le programme sujet) ainsi que certaines des entrées de ce programme, et n'évalue que les parties du programme qui dépendent des entrées fournies. Une fois toutes ces parties évaluées, le programme restant (appelé le programme résiduel) est émis en sortie.
Par exemple, un processus de pré-compilation transformerait le code sujet suivant :
var1 = 3*3
var2 = sin(arg1)
var3 = var1 + func_xx(2 * var1 + var2)
en le code résiduel suivant :
var2 = sin(arg1)
var3 = 9 + func_xx(18 + var2)
Le code a été partiellement évalué avant la compilation, afin d'optimiser les performances.
On voit que ce processus simplifie le code et élimine les opérations inutiles. C'est intéressant car l'obfuscation masque le code en ajoutant du code et des opérations inutiles pour l'exécution du code.
Si nous inspectons notre échantillon initial, étape par étape :
456127/3833 peuvent être partiellement évaluées : aucun effet de bordchr(119) peut être partiellement évaluée : 119 est une constante, et c'est une fonction native, aucun effet de bord"w" & "s" peut être partiellement évalué : c'est une concaténation de caractères constants, aucun effet de bordExecute( a un effet de bord connu (exécution de script), elle ne sera pas évaluée, sauf si les effets de bord des fonctions peuvent être gérés / émulésUne évaluation partielle de cet échantillon donnerait :
Execute("wscript.echo ""hello""" & vbCrLf)
Et devinez quoi ? Nous venons de désobfusquer notre échantillon.
Eh bien, en pratique, c'est bien plus compliqué :
Heureusement, ANTLR est un puissant générateur d'analyseurs syntaxiques pour lire, traiter, exécuter ou traduire du texte structuré ou des fichiers binaires. Il est connu pour être largement utilisé afin de construire des langages, des outils et des frameworks. À partir d'une grammaire, ANTLR génère un analyseur syntaxique capable de construire des arbres d'analyse et génère également une interface d'écoute (listener) (ou un visiteur) qui facilite la réponse à la reconnaissance des expressions d'intérêt.
ANTLR peut générer des analyseurs lexicaux, des analyseurs syntaxiques, des analyseurs d'arbres et des analyseurs combinés lexeur-parseur. Les analyseurs syntaxiques peuvent générer automatiquement des arbres d'analyse ou des arbres de syntaxe abstraite qui peuvent ensuite être traités avec des analyseurs d'arbres. ANTLR fournit une notation unique et cohérente pour spécifier les analyseurs lexicaux, les analyseurs syntaxiques et les analyseurs d'arbres.
Par défaut, ANTLR lit une grammaire et génère un reconnaisseur pour le langage défini par la grammaire (c'est-à-dire un programme qui lit un flux d'entrée et génère une erreur si le flux d'entrée n'est pas conforme à la syntaxe spécifiée par la grammaire).
Afin de faire quelque chose d'utile avec le langage, des actions peuvent être attachées aux éléments de la grammaire. Ces actions sont écrites dans le langage de programmation dans lequel le reconnaisseur est généré. Lorsque le reconnaisseur est généré, les actions sont intégrées dans le code source du reconnaisseur aux points appropriés.
Grâce à ANTLR, il a été rapide et facile de générer une première version de l'AST, de l'analyseur lexical et de l'analyseur syntaxique grâce à la grammaire existante écrite par Ulrich Wolffgang.
Exemple de diagramme ferroviaire de la grammaire VB :

Cette grammaire ne fonctionnait pas parfaitement avec certaines syntaxes Visual Basic spécifiques (code en ligne, certains problèmes d'opérateurs) et quelques modifications ont été nécessaires pour fournir une grammaire entièrement compatible VBS et VBA. La grammaire modifiée est disponible ici : Visual Basic Grammar
Voici un exemple d'arbre d'analyse visuel du morceau de code suivant :
Msgbox("Hello world " & Chr(33), vbCritical, "The Title")

Une fois l'analyseur syntaxique et l'AST générés, nous pouvons commencer à les utiliser et explorer notre code :
using Antlr4.Runtime;
using System.Linq;
namespace vbSparkle
{
public class PartialEvaluator
{
public static string Prettify(string script)
{
var inputStream = new AntlrInputStream(script);
// Create the lexer
var lexer = new VBScriptLexer(inputStream);
// Instanciate of common token from lexer
var commonTokenStream = new CommonTokenStream(lexer);
// Create the parser
var parser = new VBScriptParser(commonTokenStream);
// Get the start rule (root element of our AST)
VBScriptParser.StartRuleContext stContext = parser.startRule();
// Instanciate the visitor
VbAnalyser analyser = new VbAnalyser();
// Visit the AST
analyser.Visit(stContext);
// Root AST element of a vbScript being a "Module", we get it
var module = analyser.Modules.FirstOrDefault();
// Then we partially evaluate our module
string result = module.Prettify(partialEvaluation: true);
return result;
}
}
}
Un visiteur est chargé de parcourir l'AST et de créer pour chaque nœud un évaluateur partiel / embellisseur.
Ces derniers seront chargés d'évaluer récursivement :

Par exemple, voici notre visiteur, qui instancie un évaluateur VbModule :
public class VbAnalyser
{
public List<VbModule> Modules { get; set; } = new List<VbModule>();
internal void Visit(VBScriptParser.StartRuleContext stContext)
{
var moduleContext = stContext.module();
VbModule module = new VbModule(this, moduleContext);
Modules.Add(module);
}
}
Voici un exemple de nœud avec la fonction Prettify :
public class VbOnGotoStatement : VbStatement<VBScriptParser.OnGoToStmtContext>
{
public VBValueStatement OnValue { get; set; }
public VBValueStatement[] GotoValues { get; set; }
public VbOnGotoStatement(IVBScopeObject context, VBScriptParser.OnGoToStmtContext bloc)
: base(context, bloc)
{
OnValue = VBValueStatement.Get(context, bloc.valueStmt().First());
GotoValues = bloc.valueStmt().Skip(1).Select(v => VBValueStatement.Get(context, v)).ToArray();
}
public override DExpression Prettify(bool partialEvaluation = false)
{
return new DCodeBlock($"On {OnValue.Prettify(partialEvaluation)} Goto {string.Join(", ", GotoValues.Select(v=> v.Prettify(partialEvaluation)))}");
}
}
Lorsque le nœud est visité grâce au constructeur VbOnGotoStatement(, il visite récursivement les sous-nœuds et crée des instances de leurs propres visiteurs.
Ensuite, lorsque la méthode Prettify est appelée, les sous-éléments sont embellis avec le même niveau d'évaluation partielle.
Ce projet est sous copyright de l'Airbus CERT et distribué sous la licence Apache 2.0.