diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5afaf76..f8aff21 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -42,7 +42,7 @@ jobs: shell: bash run: | tag=$(git describe --tags --abbrev=0) - release_name="App-$tag-${{ matrix.target }}" + release_name="vbSparkle-$tag-${{ matrix.target }}" # Build everything dotnet publish Sources/vbSparkle.Console/ --framework netcoreapp3.1 --runtime "${{ matrix.target }}" -c Release -o "$release_name" @@ -61,6 +61,6 @@ jobs: - name: Publish uses: softprops/action-gh-release@v1 with: - files: "App*" + files: "vbSparkle-*" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index fa8e58d..1c798aa 100644 --- a/README.md +++ b/README.md @@ -28,11 +28,30 @@ string deobfuscated = VbPartialEvaluator.PrettifyEncoded(obfuscatedVB); ## As a CLI The attached project `vbSparkle.CLI` is an exemple of use of vbSparkle as a CLI. The current exemple take either a path as an argument or a full binary in `StdIn`, and return the deobfuscated result. -![Railroad Diagram](/Resources/cli-exemple.JPG) +![cli-exemple](/Resources/cli-exemple.JPG) + + +``` + -p, --path (Group: input) Path of directory or script file(s) + to be deobfuscated. + + --stdin (Group: input) (Default: false) Read from stdin + + -o, --output File offset. + + --sym-rename-mode (Default: None) Define how symbols can be renamed. + Valid values: None, Variables, Constants, All + + --junk-code-processing (Default: Comment) Define how junk code should be + processed. Valid values: Nothing, Remove, Comment + + -i, --indent-spacing (Default: 4) Defines the number of spaces taken into + account for the indentation of the code. +``` ## Web UI The attached project `vbSparkle.Web` is an exemple of use of vbSparkle within a Web UI. -![Railroad Diagram](/Resources/webUI.PNG) +![web-ui](/Resources/webUI.PNG) # Why to write a VBScript de-obfuscator based on partial-evaluation ? VBScript and VBA code obfuscation are popular among attackers and allow to evade detection measures, antivirus, firewalls, EDRs, and allows to make malware analysis more difficult. diff --git a/Resources/samples/sample_10.txt b/Resources/samples/sample_10.txt index 5112fa2..a6f54a7 100644 --- a/Resources/samples/sample_10.txt +++ b/Resources/samples/sample_10.txt @@ -1,14 +1,6 @@ wscript.sleep(10000) dim KWteaHafFeaq,JHgfeomgLpfMj:ZZFJFG58GJ55H85U5:dim kiolmp:kiolmp = chr(101):dhgprdt():i = 10 + 120 - 130:SDRertserfty = chr(10+10+10+9) -Function chr(MYURTHFYTR6YFH6RYHF) -Dim Z7UR7UFHEFRURGHRYHGYR -dim SDRRFGTYHGFGFGf -Z7UR7UFHEFRURGHRYHGYR = chr(MYURTHFYTR6YFH6RYHF) -for i = 1000 - 999 to len(Z7UR7UFHEFRURGHRYHGYR) -SDRRFGTYHGFGFGf = chr(asc(Z7UR7UFHEFRURGHRYHGYR)) -next -W = SDRRFGTYHGFGFGf -end Function + function OPLMITJGUCN57 (OLGTUR783J4H6UR,NHGUIRTNVUTI65,KIOYKGJUTH6785HT) OPLMITJGUCN57 = Replace(OLGTUR783J4H6UR,NHGUIRTNVUTI65,KIOYKGJUTH6785HT) end function diff --git a/Sources/vbSparkle.Console/Options.cs b/Sources/vbSparkle.Console/Options.cs index 723b690..6704b02 100644 --- a/Sources/vbSparkle.Console/Options.cs +++ b/Sources/vbSparkle.Console/Options.cs @@ -1,25 +1,35 @@ using CommandLine; using System.Collections.Generic; +using vbSparkle.Options; namespace vbSparkle.CLI { - class Options + class BaseOptions { - [Option('p', "path", Group = "inputGroup", HelpText = "Path of directory or script file(s) to be deobfuscated.")] - public IEnumerable InputFiles { get; set; } - - [Option("stdin", - Default = false, - Group = "inputGroup", - HelpText = "Read from stdin")] - public bool stdin { get; set; } - [Option('o', "output", Required = false, Default = null, HelpText = "File offset.")] public string Output { get; set; } - [Option( - Default = false, - HelpText = "Prints all messages to standard output.")] - public bool Verbose { get; set; } + [Option("sym-rename-mode", + Default = SymbolRenamingMode.None, + HelpText = "Define how symbols can be renamed.")] + public SymbolRenamingMode SymbolRenamingMode { get; set; } + + [Option("junk-code-processing", + Default = JunkCodeProcessingMode.Comment, + HelpText = "Define how junk code should be processed.")] + public JunkCodeProcessingMode JunkCodeProcessingMode { get; set; } + + [Option('i', "indent-spacing", + Default = 4, + HelpText = "Defines the number of spaces taken into account for the indentation of the code.")] + public int IndentSpacing { get; set; } + + } + + class Options: BaseOptions + { + [Option('p', "path", Required = true, HelpText = "Path of directory or script file(s) to be deobfuscated.")] + public IEnumerable InputFiles { get; set; } + } } diff --git a/Sources/vbSparkle.Console/Program.cs b/Sources/vbSparkle.Console/Program.cs index 65b048f..6cdb2c0 100644 --- a/Sources/vbSparkle.Console/Program.cs +++ b/Sources/vbSparkle.Console/Program.cs @@ -1,10 +1,16 @@ using CommandLine; -using System; +using CommandLine.Text; + using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection; using vbSparkle.Options; +using Colorful; +using System.Drawing; +using System.Threading.Tasks; +using System.Threading; namespace vbSparkle.CLI { @@ -12,14 +18,92 @@ class Program { static void Main(string[] args) { - Parser.Default.ParseArguments(args) - .WithParsed(opts => RunOptionsAndReturnExitCode(opts)) - .WithNotParsed(errs => HandleParseError(errs)); + InitializeConsoleHeader(); + //1- disable auto generated help + var parser = new Parser(with => with.HelpWriter = null); + + if (Console.IsInputRedirected) + { + //2- run parser and get result + var parserResult = parser.ParseArguments(args); + + parserResult.WithNotParsed(errs => DisplayHelp(parserResult, errs)); + parserResult.WithParsed(opts => ProcessStdIn(opts)); + } + else + { + //2- run parser and get result + var parserResult = parser.ParseArguments(args); + + parserResult.WithNotParsed(errs => DisplayHelp(parserResult, errs)); + parserResult.WithParsed(opts => RunOptionsAndReturnExitCode(opts)); + } } - private static void HandleParseError(IEnumerable errs) + private static void InitializeConsoleHeader() { + Console.ForegroundColor = Color.WhiteSmoke; + string version = Assembly.GetExecutingAssembly().GetName().Version.ToString(); + Console.ResetColor(); + Console.ReplaceAllColorsWithDefaults(); + Console.Title = "vbSparkle " + version; + + Console.WriteLine( + @" _ __ _ _ " + "\r\n" + + @"__ _| |__ / _\_ __ __ _ _ __| | _| | ___ " + "\r\n" + + @"\ \ / / '_ \\ \| '_ \ / _` | '__| |/ / |/ _ \" + "\r\n" + + @" \ V /| |_) |\ \ |_) | (_| | | | <| | __/" + "\r\n" + + @" \_/ |_.__/\__/ .__/ \__,_|_| |_|\_\_|\___|" + "\r\n" + + @" |_| v" + version + , Color.AliceBlue); + + Console.WriteLine(); + Console.WriteLine(); + + Console.WriteLine("Author(s): Sylvain Bruyere, Airbus CERT"); + Console.WriteLine("Copyright © Airbus CERT"); + Console.WriteLine("https://github.com/airbus-cert/vbSparkle"); + Console.WriteLine(); + Console.WriteLine(); + } + + private static void DisplayHelp(ParserResult result, IEnumerable errors) + { + var helpText = HelpText.AutoBuild(result, h => + { + h.AdditionalNewLineAfterOption = true; + h.Heading = string.Empty; + h.Copyright = string.Empty; + h.AddEnumValuesToHelpText = true; + + h.AddPreOptionsLine("Sample usage:"); + h.AddPreOptionsLine("> vbSparkle.CLI -p sample.vbs"); + + return HelpText.DefaultParsingErrorsHandler(result, h); + }, e => e); + + Console.WriteLine(helpText); + + Console.ReadLine(); + } + + private static void ProcessStdIn(BaseOptions opts) + { + using (var reader = new StreamReader(Console.OpenStandardInput(), Console.InputEncoding)) + { + string fileContent = reader.ReadToEnd(); + + string result = DeobfuscateContent(fileContent, opts); + + if (!string.IsNullOrWhiteSpace(opts.Output)) + File.AppendAllText(opts.Output, result); + else + { + WriteSyntaxColoringConsoleCode(result); + // Console.Out.Write(result); + } + } } private static void RunOptionsAndReturnExitCode(Options opts) @@ -27,32 +111,162 @@ private static void RunOptionsAndReturnExitCode(Options opts) if (opts.InputFiles.Count() > 0) foreach (var filename in opts.InputFiles) { - + Console.WriteLine($"# Processing {filename} ..."); string fileContent = File.ReadAllText(filename); - string result = DeobfuscateContent(fileContent); + string result = DeobfuscateContent(fileContent, opts); if (!string.IsNullOrWhiteSpace(opts.Output)) File.AppendAllText(opts.Output, result); else - Console.Out.Write(result); + WriteSyntaxColoringConsoleCode(result); } - if (opts.stdin) + + if (Console.IsInputRedirected) { - string fileContent = Console.In.ReadToEnd(); + ProcessStdIn(opts); + return; + } + } - string result = DeobfuscateContent(fileContent); + private static void WriteSyntaxColoringConsoleCode(string result) + { + Stopwatch perfWatch = new Stopwatch(); + + perfWatch.Start(); + + string[] vbKeywords = new string[] + { + "As", + "Binary", + "ByRef", + "ByVal", + "Date", + "Else", + "Empty", + "Error", + "False", + "For", + "Friend", + "Get", + "Input", + "Is", + "Len", + "Let", + "Lock", + "Me", + "Mid", + "New", + "Next", + "Nothing", + "Null", + "On", + "Option", + "Optional", + "ParamArray", + "Print", + "Private", + "Property", + "PtrSafe", + "Public", + "Resume", + "Seek", + "Set", + "Static", + "Step", + "String", + "Then", + "Time", + "To", + "True", + "WithEvents", + "Dim", + "ReDim", + "Preserve", + "If", + "Then", + "Function", + "Sub", + "GoSub", + "GoTo", + "On", + "Error", + "Do", + "Until", + "End", + "Exit", + "While", + "Loop", + "And", + "Or", + "\\+", + "\\&", + "\\=", + "\\-", + "\\*" + }; + + + string[] funcKeywords = new string[] + { + "Mid", + "Mid$", + "Asc", + "Asc$", + "Chr", + "Chr$", + "UBound", + "LBound", + "Len", + "UCase", + "LCase" + + }; + + StyleSheet styleSheet = new StyleSheet(Color.White); + + string[] dangerKeywords = new string[] + { + "CreateObject", + "WScript.Shell", + "WScript.GetObject", + + }; + + foreach (var v in dangerKeywords.Distinct().ToArray()) + styleSheet.AddStyle(v, Color.Red); + + foreach (var v in vbKeywords.Distinct().ToArray()) + styleSheet.AddStyle(v + "\\s+", Color.CornflowerBlue); + + foreach (var v in funcKeywords.Distinct().ToArray()) + styleSheet.AddStyle(v + "\\(", Color.LightBlue); + + + + //foreach (var v in funcKeywords.Distinct().ToArray()) + styleSheet.AddStyle("[a-zA-Z][\\w]*\\(", Color.SkyBlue); + + styleSheet.AddStyle("\\)", Color.LightBlue); + + styleSheet.AddStyle("\\\"(.*?)\\\"", Color.Orange); + styleSheet.AddStyle("\\\'(.*?).*", Color.Green); + + Console.WriteLine(new string('═', 80)); + + Console.WriteLineStyled(result + "\r\n", styleSheet); + + perfWatch.Stop(); + Console.ForegroundColor = Color.WhiteSmoke; + + Console.WriteLine(new string('═', 80)); + Console.WriteLine($"# Printed in {perfWatch.ElapsedMilliseconds} ms."); - if (!string.IsNullOrWhiteSpace(opts.Output)) - File.AppendAllText(opts.Output, result); - else - Console.Out.Write(result); - } } - private static string DeobfuscateContent(string content) + private static string DeobfuscateContent(string content, BaseOptions opts) { Stopwatch perfWatch = new Stopwatch(); @@ -60,13 +274,13 @@ private static string DeobfuscateContent(string content) var result = VbPartialEvaluator.PrettifyEncoded(content, new EvaluatorOptions() { - SymbolRenamingMode = SymbolRenamingMode.None, - JunkCodeProcessingMode = JunkCodeProcessingMode.Remove, - IndentSpacing = 4, + SymbolRenamingMode = opts.SymbolRenamingMode, + JunkCodeProcessingMode = opts.JunkCodeProcessingMode, + IndentSpacing = opts.IndentSpacing }); perfWatch.Stop(); - Console.WriteLine($"Computed in {perfWatch.ElapsedMilliseconds} ms."); + Console.WriteLine($"# Computed in {perfWatch.ElapsedMilliseconds} ms."); return result; } diff --git a/Sources/vbSparkle.Console/Properties/launchSettings.json b/Sources/vbSparkle.Console/Properties/launchSettings.json index 0e434ba..0d6ff9f 100644 --- a/Sources/vbSparkle.Console/Properties/launchSettings.json +++ b/Sources/vbSparkle.Console/Properties/launchSettings.json @@ -1,8 +1,20 @@ { "profiles": { - "vbSparkle.Console": { + "Help": { + "commandName": "Project", + "commandLineArgs": "" + }, + "Sample_7": { "commandName": "Project", "commandLineArgs": "-p $(SolutionDir)\\Resources\\samples\\sample_7.txt" + }, + "Sample_10": { + "commandName": "Project", + "commandLineArgs": "-p $(SolutionDir)\\Resources\\samples\\sample_10.txt --sym-rename-mode All" + }, + "vbSparkle.Console": { + "commandName": "Project", + "commandLineArgs": "" } } } \ No newline at end of file diff --git a/Sources/vbSparkle.Console/vbSparkle.CLI.csproj b/Sources/vbSparkle.Console/vbSparkle.CLI.csproj index b2b5565..92c3bb2 100644 --- a/Sources/vbSparkle.Console/vbSparkle.CLI.csproj +++ b/Sources/vbSparkle.Console/vbSparkle.CLI.csproj @@ -4,10 +4,28 @@ Exe netcoreapp3.1 vbSparkle.CLI.Program + False + Sylvain Bruyere, Airbus CERT + Airbus CERT + vbSparkle CLI + Airbus CERT + https://github.com/airbus-cert/vbSparkle + README.md + https://github.com/airbus-cert/vbSparkle + + 1.0.2 - + + True + \ + + + + + + diff --git a/Sources/vbSparkle/vbSparkle.csproj b/Sources/vbSparkle/vbSparkle.csproj index 8448d50..caa0de5 100644 --- a/Sources/vbSparkle/vbSparkle.csproj +++ b/Sources/vbSparkle/vbSparkle.csproj @@ -16,6 +16,7 @@ The parsing of Visual Basic Script and VBA is processed through the use of ANTLR Airbus CERT, Sylvain Bruyere Airbus CERT, Sylvain Bruyere Airbus + 1.0.2 @@ -49,4 +50,8 @@ The parsing of Visual Basic Script and VBA is processed through the use of ANTLR + + + +