
Desofuscador de código fuente a código fuente para VBScript y VBA con evaluación parcial
vbSparkle es un desofuscador de Visual Basic multiplataforma, de fuente a fuente, basado en evaluación parcial y dedicado principalmente al análisis de código malicioso escrito en VBScript y VBA (Macro de Office).
Está escrito en C# nativo y proporciona una biblioteca .Net Standard, y funciona en Windows, Linux, MacOS, etc.
El análisis de Visual Basic Script y VBA se procesa mediante el uso de la gramática de ANTLR y sus analizadores léxicos.
Install-Package vbSparkle
dotnet add package vbSparkle
Una vez que hayas agregado la biblioteca vbSparkle a tu proyecto, solo tienes que llamar a la función PrettifyEncoded de la clase estática VbPartialEvaluator:
string obfuscatedVB = "msgbox(chr(65)&chr(66)&(chr(67))"
string deobfuscated = VbPartialEvaluator.PrettifyEncoded(obfuscatedVB);
... devolverá MsgBox("ABC").
El proyecto adjunto vbSparkle.CLI es un ejemplo de uso de vbSparkle como CLI.
El ejemplo actual toma como argumento una ruta o un binario completo en StdIn, y devuelve el resultado desofuscado.

El proyecto adjunto vbSparkle.Web es un ejemplo de uso de vbSparkle dentro de una interfaz web.

La ofuscación de código VBScript y VBA es popular entre los atacantes y permite evadir las medidas de detección, antivirus, firewalls, EDRs, y dificulta el análisis de malware.
Por lo tanto, es común ver VBScript ofuscado en la primera etapa del compromiso de una máquina.
Una técnica típica de ofuscación de código VBScript consiste en encapsular el script en una cadena ofuscada y ejecutarlo mediante la función Execute("...code..." function) después de que se decodifique / evalúe.
Veamos un ejemplo:
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 )
Aquí, el atacante hizo uso de:
chr(CHAR_CODE) para evaluar un carácter de cadena (p. ej. chr(65) = "A")456127/3833) para evaluar el valor del carácter& para ensamblar todos los caracteres decodificadosExecute(..) para ejecutar el script decodificado.Si seguimos la ejecución del script:
456127/3833 => 119chr(119) => "w"817650/7110 => 115chr(115) => "s""w" & "s" => "ws"Aquí está el script original:
wscript.echo "hello"
La desofuscación manual se vuelve muy difícil y requiere mucho tiempo en scripts grandes.
Una técnica de desofuscación común con VBScript consiste simplemente en reemplazar la función Execute() por WScript.Echo(, y luego ejecutar el script para obtener la salida.
Esta última técnica es simple pero tiene algunas desventajas:
Y, en el momento en que este proyecto comenzó, no había alternativa a estas dos técnicas.
La evaluación parcial no es un concepto nuevo; lo usan principalmente los compiladores para la optimización de código en procesos de precompilación (p. ej. https://en.wikipedia.org/wiki/Partial_evaluation).
Un evaluador parcial lee un programa (llamado programa sujeto) junto con algunas de las entradas de ese programa, y evalúa solo las partes del programa que dependen de las entradas proporcionadas. Una vez que todas estas partes han sido evaluadas, el programa restante (llamado programa residual) se emite como salida.
Por ejemplo, un proceso de precompilación transformaría el siguiente código sujeto:
var1 = 3*3
var2 = sin(arg1)
var3 = var1 + func_xx(2 * var1 + var2)
En el siguiente código residual:
var2 = sin(arg1)
var3 = 9 + func_xx(18 + var2)
El código ha sido evaluado parcialmente antes de la compilación, para optimizar el rendimiento.
Vemos que este proceso simplifica el código y elimina operaciones innecesarias. Es interesante porque la ofuscación oculta el código agregando código y operaciones innecesarias para la ejecución del código.
Si inspeccionamos nuestra muestra inicial, paso a paso:
456127/3833 puede ser evaluada parcialmente: sin efectos secundarioschr(119) puede ser evaluado parcialmente: 119 es una constante, y esta es una función nativa, sin efectos secundarios"w" & "s" puede ser evaluado parcialmente: es una concatenación de caracteres constante, sin efectos secundarios"Execute(" tiene efectos secundarios conocidos (ejecución de script), no se evaluará, salvo que los efectos secundarios de las funciones puedan gestionarse / emularseUna evaluación parcial de esa muestra daría como resultado:
Execute("wscript.echo ""hello""" & vbCrLf)
¿Y adivina qué? Oh, acabamos de desofuscar nuestra muestra.
Bueno, en la práctica, es mucho más complicado:
Afortunadamente, ANTLR es un potente generador de parsers para leer, procesar, ejecutar o traducir texto estructurado o archivos binarios. Es conocido por ser ampliamente utilizado para construir lenguajes, herramientas y frameworks. A partir de una gramática, ANTLR genera un parser que puede construir árboles de análisis y también genera una interfaz de listener (o visitor) que facilita responder al reconocimiento de frases de interés.
ANTLR puede generar lexers, parsers, tree parsers y lexer-parsers combinados. Los parsers pueden generar automáticamente árboles de análisis o árboles de sintaxis abstracta que pueden procesarse posteriormente con tree parsers. ANTLR proporciona una notación única y consistente para especificar lexers, parsers y tree parsers.
Por defecto, ANTLR lee una gramática y genera un reconocedor para el lenguaje definido por la gramática (es decir, un programa que lee un flujo de entrada y genera un error si el flujo de entrada no se ajusta a la sintaxis especificada por la gramática).
Para hacer algo útil con el lenguaje, se pueden adjuntar acciones a los elementos de la gramática. Estas acciones se escriben en el lenguaje de programación en el que se está generando el reconocedor. Cuando se genera el reconocedor, las acciones se incrustan en el código fuente del reconocedor en los puntos apropiados.
Gracias a ANTLR, fue rápido y fácil generar una primera versión del AST, del lexer y del parser, gracias a la gramática existente escrita por Ulrich Wolffgang.
Ejemplo de diagrama railroad de la gramática VB:

Esta gramática no funcionaba completamente con algunas sintaxis específicas de Visual Basic (código en línea, algunos problemas con operadores) y se necesitaron algunos cambios para proporcionar una gramática totalmente compatible con VBS y VBA. La gramática modificada se puede encontrar aquí: Gramática de Visual Basic
Aquí hay un ejemplo de árbol de análisis visual del siguiente fragmento de código:
Msgbox("Hello world " & Chr(33), vbCritical, "The Title")

Una vez que el parser y el AST se generan, podemos comenzar a usarlo y explorar nuestro código:
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 se encarga de recorrer el AST y crear cada nodo evaluador parcial/prettifier.
Estos últimos se encargarán de evaluar recursivamente:

Por ejemplo, aquí está nuestro visitor, que instancia un evaluador 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);
}
}
Aquí hay un ejemplo de nodo con la función 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)))}");
}
}
Cuando el nodo se visita gracias al constructor VbOnGotoStatement(, se visitan recursivamente los subnodos y se crean instancias de sus propios visitors.
Luego, cuando se llama al método Prettify, los subelementos se formatean con el mismo nivel de evaluación parcial.