
Deofuscatore source-to-source VBScript & VBA con valutazione parziale
vbSparkle è un deoffuscatore Visual Basic multi-piattaforma source-to-source basato su valutazione parziale ed è principalmente dedicato all'analisi di codice malevolo scritto in VBScript e VBA (Macro Office).
È scritto in C# nativo e fornisce una libreria .Net Standard, e funziona su Windows, Linux, MacOS, ecc..
Il parsing di Visual Basic Script e VBA è gestito tramite l'uso di grammatica e parser lexer ANTLR.
Install-Package vbSparkle
dotnet add package vbSparkle
Dopo aver aggiunto la libreria vbSparkle al tuo progetto, devi solo chiamare la funzione PrettifyEncoded dalla classe statica VbPartialEvaluator:
string obfuscatedVB = "msgbox(chr(65)&chr(66)&(chr(67))"
string deobfuscated = VbPartialEvaluator.PrettifyEncoded(obfuscatedVB);
... restituirà MsgBox("ABC").
Il progetto allegato vbSparkle.CLI è un esempio di utilizzo di vbSparkle come CLI.
L'esempio attuale accetta come argomento un percorso oppure un binario completo in StdIn, e restituisce il risultato deoffuscato.

Il progetto allegato vbSparkle.Web è un esempio di utilizzo di vbSparkle all'interno di una Web UI.

L'offuscamento del codice VBScript e VBA è popolare tra gli attaccanti e consente di eludere le misure di rilevamento, antivirus, firewall, EDR, e rende più difficile l'analisi del malware.
Pertanto è comune vedere VBScript offuscato nel primissimo stadio di una compromissione di una macchina.
Una tipica tecnica di offuscamento del codice VBScript consiste nell'incapsulare lo script in una stringa offuscata ed eseguirlo tramite la funzione Execute("...code..." ) dopo che è stato decodificato / valutato.
Vediamo un esempio:
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 )
Qui, l'attaccante ha fatto uso di:
chr(CHAR_CODE) per valutare un carattere di stringa (es. chr(65) = "A")456127/3833) per valutare il valore del carattere& per assemblare tutti i caratteri decodificatiExecute(..) per eseguire lo script decodificato.Se seguiamo l'esecuzione dello script:
456127/3833 => 119chr(119) => "w"817650/7110 => 115chr(115) => "s""w" & "s" => "ws"Execute("ws.....Ecco lo script originale:
wscript.echo "hello"
Una deoffuscazione manuale è resa molto difficile e dispendiosa in termini di tempo su script di grandi dimensioni.
Una tecnica di deoffuscazione comune con VBScript consiste semplicemente nel sostituire la funzione Execute() con un WScript.Echo(, e poi eseguire lo script per ottenere l'output.
Quest'ultima tecnica è semplice ma ha alcuni svantaggi:
E, al momento in cui questo progetto è iniziato, non c'era alcuna alternativa a queste due tecniche.
La valutazione parziale non è un concetto nuovo, è usata principalmente dai compilatori per l'ottimizzazione del codice nei processi di pre-compilazione (es. https://en.wikipedia.org/wiki/Partial_evaluation).
Un valutatore parziale legge un programma (chiamato programma soggetto) insieme ad alcuni degli input per quel programma, e valuta solo le parti del programma che dipendono dagli input forniti. Una volta che tutte queste parti sono state valutate, il programma rimanente (chiamato programma residuo) viene emesso come output.
Per esempio, un processo di pre-compilazione trasformerebbe il seguente codice soggetto:
var1 = 3*3
var2 = sin(arg1)
var3 = var1 + func_xx(2 * var1 + var2)
Nel seguente codice residuo:
var2 = sin(arg1)
var3 = 9 + func_xx(18 + var2)
Il codice è stato parzialmente valutato prima della compilazione, per ottimizzare le prestazioni.
Vediamo che questo processo semplifica il codice ed elimina le operazioni non necessarie. È interessante perché l'offuscamento nasconde il codice aggiungendo codice e operazioni non necessari per l'esecuzione del codice.
Se esaminiamo il nostro campione iniziale, passo dopo passo:
456127/3833 possono essere valutate parzialmente: nessun effetto collateralechr(119) può essere valutato parzialmente: 119 è una costante, e questa è una funzione nativa, nessun effetto collateraleUna valutazione parziale di quel campione risulterebbe in:
Execute("wscript.echo ""hello""" & vbCrLf)
E indovina un po'? Oh, abbiamo appena deoffuscato il nostro campione.
Beh, in pratica, è molto più complicato:
Fortunatamente, ANTLR è un potente generatore di parser per leggere, elaborare, eseguire o tradurre testo strutturato o file binari. È noto per essere ampiamente utilizzato per costruire linguaggi, strumenti e framework. Da una grammatica, ANTLR genera un parser che può costruire alberi di parsing e genera anche un'interfaccia listener (o visitor) che rende facile rispondere al riconoscimento di frasi di interesse.
ANTLR può generare lexer, parser, tree parser e lexer-parser combinati. I parser possono generare automaticamente alberi di parsing o alberi sintattici astratti che possono essere ulteriormente elaborati con tree parser. ANTLR fornisce una notazione unica e coerente per specificare lexer, parser e tree parser.
Per impostazione predefinita, ANTLR legge una grammatica e genera un riconoscitore per il linguaggio definito dalla grammatica (cioè un programma che legge un flusso di input e genera un errore se il flusso di input non è conforme alla sintassi specificata dalla grammatica).
Per fare qualcosa di utile con il linguaggio, è possibile collegare azioni agli elementi della grammatica nella grammatica stessa. Queste azioni sono scritte nel linguaggio di programmazione in cui viene generato il riconoscitore. Quando il riconoscitore viene generato, le azioni vengono incorporate nel codice sorgente del riconoscitore nei punti appropriati.
Grazie a ANTLR, è stato veloce e facile generare una prima versione dell'AST, del lexer e del parser grazie alla grammatica esistente scritta da Ulrich Wolffgang.
Esempio di Railroad dalla grammatica VB:

Questa grammatica non funzionava completamente con alcune sintassi Visual Basic specifiche (codice inline, alcuni problemi con gli operatori) e sono state necessarie alcune modifiche per fornire una grammatica pienamente compatibile con VBS e VBA. La grammatica modificata si trova qui : Visual Basic Grammar
Ecco un esempio di albero di parsing visuale del seguente pezzo di codice:
Msgbox("Hello world " & Chr(33), vbCritical, "The Title")

Una volta che il parser e l'AST sono generati, possiamo iniziare a usarlo ed esplorare il nostro codice:
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 visitor è incaricato di attraversare l'AST e creare per ogni nodo il suo valutatore parziale/prettificatore.
Questi ultimi saranno incaricati di valutare ricorsivamente:

Per esempio, ecco il nostro visitor, che istanzia un valutatore 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);
}
}
Ecco un esempio di nodo con funzione 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)))}");
}
}
Quando il nodo viene visitato tramite il costruttore VbOnGotoStatement(, vengono visitati ricorsivamente i sotto-nodi e create istanze dei loro rispettivi visitor.
Poi, quando viene chiamato il metodo Prettify, i sotto-elementi vengono prettificati con lo stesso livello di valutazione parziale.