
VBScript- & VBA-Source-to-Source-Deobfuskator mit partieller Auswertung
vbSparkle ist ein Quell-zu-Quell-, plattformübergreifender Visual-Basic-Deobfuskator, der auf partieller Auswertung basiert und hauptsächlich der Analyse von schädlichem Code in VBScript und VBA (Office-Makro) gewidmet ist.
Es ist in nativem C# geschrieben, bietet eine .Net Standard-Bibliothek und funktioniert unter Windows, Linux, MacOS usw.
Das Parsen von Visual Basic Script und VBA erfolgt mithilfe der ANTLR-Grammatik- und Lexer-Parser.
Install-Package vbSparkle
dotnet add package vbSparkle
Sobald Sie die vbSparkle-Bibliothek zu Ihrem Projekt hinzugefügt haben, müssen Sie nur die Funktion PrettifyEncoded aus der statischen Klasse VbPartialEvaluator aufrufen:
string obfuscatedVB = "msgbox(chr(65)&chr(66)&(chr(67))"
string deobfuscated = VbPartialEvaluator.PrettifyEncoded(obfuscatedVB);
... wird MsgBox("ABC") zurückgegeben.
Das beigefügte Projekt vbSparkle.CLI ist ein Beispiel für die Verwendung von vbSparkle als CLI.
Das aktuelle Beispiel nimmt entweder einen Pfad als Argument oder eine vollständige Binärdatei über StdIn entgegen und gibt das deobfusizierte Ergebnis zurück.

Das beigefügte Projekt vbSparkle.Web ist ein Beispiel für die Verwendung von vbSparkle in einer Web-UI.

Die Obfuskation von VBScript- und VBA-Code ist bei Angreifern beliebt und ermöglicht es, Erkennungsmaßnahmen, Antivirenprogramme, Firewalls und EDRs zu umgehen sowie die Malware-Analyse zu erschweren.
Daher ist es üblich, obfuskiertes VBScript in der allerersten Phase einer Maschinenkompromittierung zu sehen.
Eine typische Technik zur Obfuskation von VBScript-Code besteht darin, das Skript in eine obfuskierte Zeichenfolge zu verpacken und es über die Execute("...code..." function)-Funktion auszuführen, nachdem es dekodiert / ausgewertet wurde.
Schauen wir uns ein Beispiel an:
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 )
Hier hat der Angreifer Folgendes verwendet:
chr(CHAR_CODE), um ein Zeichen einer Zeichenfolge auszuwerten (z. B. chr(65) = "A")456127/3833), um den Zeichenwert auszuwerten&, um alle dekodierten Zeichen zusammenzusetzenExecute(..), um das dekodierte Skript auszuführen.Wenn wir der Skriptausführung folgen:
456127/3833 => 119chr(119) => "w"817650/7110 => 115chr(115) => "s""w" & "s" => "ws"Execute("ws.....Hier ist das ursprüngliche Skript:
wscript.echo "hello"
Eine manuelle Deobfuskation wird bei großen Skripten sehr schwierig und zeitaufwändig.
Eine übliche Deobfuskationstechnik bei VBScript besteht darin, einfach die Funktion Execute() durch WScript.Echo( zu ersetzen und das Skript dann auszuführen, um die Ausgabe zu erhalten.
Diese letztgenannte Technik ist einfach, hat aber einige Nachteile:
Und zu dem Zeitpunkt, als dieses Projekt gestartet wurde, gab es keine Alternative zu diesen beiden Techniken.
Partielle Auswertung ist kein neues Konzept; sie wird hauptsächlich von Compilern zur Code-Optimierung im Vorkompilierungsprozess verwendet (z. B. https://en.wikipedia.org/wiki/Partial_evaluation).
Ein Partial-Evaluator liest ein Programm (das sogenannte Subjektprogramm) zusammen mit einigen Eingaben für dieses Programm und wertet nur die Teile des Programms aus, die von den bereitgestellten Eingaben abhängen. Sobald alle diese Teile ausgewertet wurden, wird das verbleibende Programm (das sogenannte Restprogramm) als Ausgabe ausgegeben.
Zum Beispiel würde ein Vorkompilierungsprozess den folgenden Subjektcode transformieren:
var1 = 3*3
var2 = sin(arg1)
var3 = var1 + func_xx(2 * var1 + var2)
in den folgenden Restcode:
var2 = sin(arg1)
var3 = 9 + func_xx(18 + var2)
Der Code wurde vor der Kompilierung teilweise ausgewertet, um die Leistung zu optimieren.
Wir sehen, dass dieser Prozess den Code vereinfacht und unnötige Operationen eliminiert. Das ist interessant, weil die Obfuskation den Code verbirgt, indem sie Code und Operationen hinzufügt, die für die Ausführung des Codes unnötig sind.
Wenn wir unser erstes Beispiel Schritt für Schritt untersuchen:
456127/3833 können teilweise ausgewertet werden: keine Nebenwirkungenchr(119) kann teilweise ausgewertet werden: 119 ist eine Konstante, und dies ist eine native Funktion, ohne Nebenwirkungen"w" & "s" kann teilweise ausgewertet werden: Es handelt sich um eine konstante Zeichenverkettung ohne Nebenwirkungen"Execute(" hat bekannte Nebenwirkungen (Skriptausführung); es wird nicht ausgewertet, es sei denn, die Nebenwirkungen von Funktionen können verwaltet / emuliert werden.Eine partielle Auswertung dieses Beispiels würde zu folgendem Ergebnis führen:
Execute("wscript.echo ""hello""" & vbCrLf)
Und was glauben Sie? Oh, wir haben unser Beispiel gerade deobfuskiert.
Nun, in der Praxis ist es weitaus komplizierter:
Glücklicherweise ist ANTLR ein leistungsstarker Parser-Generator zum Lesen, Verarbeiten, Ausführen oder Übersetzen von strukturiertem Text oder Binärdateien. Es ist bekannt, dass es häufig zum Erstellen von Sprachen, Werkzeugen und Frameworks verwendet wird. Aus einer Grammatik erzeugt ANTLR einen Parser, der Parse-Bäume erstellen kann, und erzeugt außerdem eine Listener-Schnittstelle (oder einen Visitor), die es einfach macht, auf die Erkennung interessanter Phrasen zu reagieren.
ANTLR kann Lexer, Parser, Baum-Parser und kombinierte Lexer-Parser erzeugen. Parser können automatisch Parse-Bäume oder abstrakte Syntaxbäume erzeugen, die mit Baum-Parsern weiterverarbeitet werden können. ANTLR bietet eine einheitliche, konsistente Notation zur Spezifikation von Lexern, Parsern und Baum-Parsern.
Standardmäßig liest ANTLR eine Grammatik und erzeugt einen Erkerner für die durch die Grammatik definierte Sprache (d. h. ein Programm, das einen Eingabestrom liest und einen Fehler erzeugt, wenn der Eingabestrom nicht der durch die Grammatik angegebenen Syntax entspricht).
Um mit der Sprache etwas Nützliches zu tun, können Aktionen an Grammatikelemente in der Grammatik angehängt werden. Diese Aktionen werden in der Programmiersprache geschrieben, in der der Erkerner erzeugt wird. Wenn der Erkerner erzeugt wird, werden die Aktionen an den entsprechenden Stellen in den Quellcode des Erkerners eingebettet.
Dank ANTLR war es schnell und einfach, eine erste Version des AST, des Lexers und des Parsers zu erzeugen, dank einer vorhandenen Grammatik von Ulrich Wolffgang.
Beispiel für ein Railroad-Diagramm aus der VB-Grammatik:

Diese Grammatik funktionierte mit einigen spezifischen Visual-Basic-Syntaxen (Inline-Code, einige Operator-Probleme) nicht vollständig, und es waren einige Änderungen erforderlich, um eine vollständig VBS- und VBA-kompatible Grammatik bereitzustellen. Die modifizierte Grammatik finden Sie hier: Visual Basic Grammar
Hier ist ein Beispiel für einen visuellen Parse-Baum des folgenden Codeausschnitts:
Msgbox("Hello world " & Chr(33), vbCritical, "The Title")

Sobald der Parser und der AST erzeugt sind, können wir ihn verwenden und unseren Code erkunden:
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;
}
}
}
Ein Visitor ist dafür zuständig, den AST zu durchlaufen und für jeden Knoten einen Partial-Evaluator/Prettifier zu erstellen.
Diese sind dafür zuständig, rekursiv auszuwerten:

Zum Beispiel ist hier unser Visitor, der einen VbModule-Evaluator instanziiert:
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);
}
}
Hier ist ein Beispiel für einen Knoten mit der Prettify-Funktion:
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)))}");
}
}
Wenn der Knoten über den Konstruktor VbOnGotoStatement( besucht wird, werden rekursiv Unterknoten besucht und Instanzen der jeweiligen Visitor erstellt.
Wenn dann die Methode Prettify aufgerufen wird, werden Unterelemente mit derselben Stufe der partiellen Auswertung aufbereitet.