diff --git a/OfficePurge/App.config b/OfficePurge/App.config index 9d2c7ad..bae5d6d 100644 --- a/OfficePurge/App.config +++ b/OfficePurge/App.config @@ -1,6 +1,6 @@ - + diff --git a/OfficePurge/OfficePurge.csproj b/OfficePurge/OfficePurge.csproj index bcfb810..f5aa554 100644 --- a/OfficePurge/OfficePurge.csproj +++ b/OfficePurge/OfficePurge.csproj @@ -1,5 +1,6 @@  + Debug @@ -8,7 +9,7 @@ Exe OfficePurge OfficePurge - v4.7 + v4.6.1 512 true true @@ -38,8 +39,15 @@ false + + ..\packages\Costura.Fody.3.3.3\lib\net40\Costura.dll + + + ..\packages\OpenMcdf.2.2.1.3\lib\net40\OpenMcdf.dll + + @@ -58,4 +66,12 @@ + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/OfficePurge/Program.cs b/OfficePurge/Program.cs index a6470ce..744044a 100644 --- a/OfficePurge/Program.cs +++ b/OfficePurge/Program.cs @@ -3,211 +3,332 @@ using System.Linq; using OpenMcdf; using System.IO; +using System.IO.Compression; namespace OfficePurge { - class Program - { - private static string document = ""; - private static string filename = ""; - private static string module = ""; - private static bool list_modules = false; - - public static void PrintHelp() - { - Utils.HelpMenu(); - } - static void Main(string[] args) - { - try - { - if (args.Length == 0 || args.Contains("-h")) - { - PrintHelp(); - return; - } - - Dictionary argDict = Utils.ParseArgs(args); - - if (argDict.ContainsKey("d")) - { - document = argDict["d"]; - if (document != "word" && document != "excel" && document != "publisher") - { - Console.WriteLine("\n[!] Unknown document type. Options are 'word', 'excel', or 'publisher'.\n"); - return; - } - } - else - { - Console.WriteLine("\n[!] Missing document type (-d)\n"); - return; - } - - if (argDict.ContainsKey("f")) - { - filename = argDict["f"]; - } - else - { - Console.WriteLine("\n[!] Missing file (-f)\n"); - return; - } - - if (args.Contains("-l")) - { - list_modules = true; - } - else - { - if (argDict.ContainsKey("m")) - { - module = argDict["m"]; - } - else - { - Console.WriteLine("\n[!] Missing module (-m)\n"); - return; - } - } - - // VBA Purging - try - { - // Make a copy of document to VBA Purge if user is not listing modules - if (!list_modules) - { - string outFilename = Utils.getOutFilename(filename); - string oleFilename = outFilename; - - if (File.Exists(outFilename)) File.Delete(outFilename); - File.Copy(filename, outFilename); - filename = outFilename; - } - - CompoundFile cf = new CompoundFile(filename, CFSUpdateMode.Update, 0); - CFStorage commonStorage = cf.RootStorage; - - if (document == "word") - { - commonStorage = cf.RootStorage.GetStorage("Macros"); - } - - else if (document == "excel") - { - commonStorage = cf.RootStorage.GetStorage("_VBA_PROJECT_CUR"); - } - - else if (document == "publisher") - { - commonStorage = cf.RootStorage.GetStorage("VBA"); - } - - - // Grab data from "dir" module stream. Used to retrieve list of module streams in document. - byte[] dirStream = Utils.Decompress(commonStorage.GetStorage("VBA").GetStream("dir").GetData()); - List vbaModules = Utils.ParseModulesFromDirStream(dirStream); - - // Only list module streams in document and return - if (list_modules) - { - foreach (var vbaModule in vbaModules) - { - Console.WriteLine("\n[*] VBA module name: " + vbaModule.moduleName); - } - Console.WriteLine("\n[*] Finished listing modules\n"); - return; - } - - - byte[] streamBytes; - bool module_found = false; - foreach (var vbaModule in vbaModules) - { - //VBA Purging begins - if (vbaModule.moduleName == module) - { - Console.WriteLine("\n[*] VBA module name: " + vbaModule.moduleName); - Console.WriteLine("\n[*] Offset for code: " + vbaModule.textOffset); - Console.WriteLine("\n[*] Now purging VBA code in module: " + vbaModule.moduleName); - - // Get the CompressedSourceCode from module - streamBytes = commonStorage.GetStorage("VBA").GetStream(vbaModule.moduleName).GetData(); - string OG_VBACode = Utils.GetVBATextFromModuleStream(streamBytes, vbaModule.textOffset); - - // Remove P-code from module stream and set the module to only have the CompressedSourceCode - streamBytes = Utils.RemovePcodeInModuleStream(streamBytes, vbaModule.textOffset, OG_VBACode); - commonStorage.GetStorage("VBA").GetStream(vbaModule.moduleName).SetData(streamBytes); - module_found = true; - - } - } - - if (module_found == false) - { - Console.WriteLine("\n[!] Could not find module in document (-m). List all module streams with (-l).\n"); - cf.Commit(); - cf.Close(); - CompoundFile.ShrinkCompoundFile(filename); - File.Delete(filename); - return; - } - - - // Change offset to 0 so that document can find compressed source code. - commonStorage.GetStorage("VBA").GetStream("dir").SetData(Utils.Compress(Utils.ChangeOffset(dirStream))); - Console.WriteLine("\n[*] Module offset changed to 0."); - - // Remove performance cache in _VBA_PROJECT stream. Replace the entire stream with _VBA_PROJECT header. - byte[] data = Utils.HexToByte("CC-61-FF-FF-00-00-00"); - commonStorage.GetStorage("VBA").GetStream("_VBA_PROJECT").SetData(data); - Console.WriteLine("\n[*] PerformanceCache removed from _VBA_PROJECT stream."); - - // Check if document contains SRPs. Must be removed for VBA Purging to work. - try - { - commonStorage.GetStorage("VBA").Delete("__SRP_0"); - commonStorage.GetStorage("VBA").Delete("__SRP_1"); - commonStorage.GetStorage("VBA").Delete("__SRP_2"); - commonStorage.GetStorage("VBA").Delete("__SRP_3"); - Console.WriteLine("\n[*] SRP streams deleted!"); - } - catch (Exception) - { - Console.WriteLine("\n[*] No SRP streams found."); - } - - // Commit changes and close - cf.Commit(); - cf.Close(); - CompoundFile.ShrinkCompoundFile(filename); - Console.WriteLine("\n[*] VBA Purging completed successfully!\n"); - } - - // Error handle for file not found - catch (FileNotFoundException ex) when (ex.Message.Contains("Could not find file")) - { - Console.WriteLine("\n[!] Could not find path or file (-f). \n"); - } - - // Error handle when document specified and file chosen don't match - catch (CFItemNotFound ex) when (ex.Message.Contains("Cannot find item")) - { - Console.WriteLine("\n[!] File (-f) does not match document type selected (-d).\n"); - } - - // Error handle when document is not OLE/CFBF format - catch (CFFileFormatException) - { - Console.WriteLine("\n[!] Incorrect filetype (-f). Must be an OLE strucutred file. OfficePurge supports .doc, .xls, or .pub documents.\n"); - } - } - // Error handle for incorrect use of flags - catch (IndexOutOfRangeException) - { - Console.WriteLine("\n[!] Flags (-d), (-f), (-m) need an argument. Make sure you have provided these flags an argument.\n"); - } - } - } + class Program + { + private static string filename = ""; + private static string module = ""; + private static bool list_modules = false; + + public static void PrintHelp() + { + Utils.HelpMenu(); + } + static void Main(string[] args) + { + string outFilename = ""; + + try + { + if (args.Length == 0 || args.Contains("-h")) + { + PrintHelp(); + return; + } + + Dictionary argDict = Utils.ParseArgs(args); + + if (argDict.ContainsKey("f")) + { + filename = Path.GetFullPath(argDict["f"]); + } + else + { + Console.WriteLine("\n[!] Missing file (-f)\n"); + return; + } + + if (args.Contains("-l")) + { + list_modules = true; + } + else + { + if (argDict.ContainsKey("m")) + { + module = argDict["m"]; + } + else + { + Console.WriteLine("\n[.] Will automatically decide which modules to purge."); + } + } + + bool is_OpenXML = false; + + // Temp path to unzip OpenXML files to + String unzipTempPath = ""; + + outFilename = Utils.getOutFilename(filename); + string oleFilename = outFilename; + + // VBA Purging + try + { + // Make a copy of document to VBA Purge if user is not listing modules + if (!list_modules) + { + if (File.Exists(outFilename)) File.Delete(outFilename); + File.Copy(filename, outFilename); + } + else + { + outFilename = outFilename.Replace("_PURGED", ""); + } + + try + { + unzipTempPath = CreateUniqueTempDirectory(); + ZipFile.ExtractToDirectory(filename, unzipTempPath); + + if (File.Exists(Path.Combine(unzipTempPath, "word", "vbaProject.bin"))) + { + oleFilename = Path.Combine(unzipTempPath, "word", "vbaProject.bin"); + } + else if (File.Exists(Path.Combine(unzipTempPath, "xl", "vbaProject.bin"))) + { + oleFilename = Path.Combine(unzipTempPath, "xl", "vbaProject.bin"); + } + else if (File.Exists(Path.Combine(unzipTempPath, "ppt", "vbaProject.bin"))) + { + oleFilename = Path.Combine(unzipTempPath, "ppt", "vbaProject.bin"); + } + + is_OpenXML = true; + } + catch (Exception e) + { + Console.WriteLine("Input file seems to be a 97-2003 Office document (OLE)"); + + if (!list_modules) + { + // Not OpenXML format, Maybe 97-2003 format, Make a copy + if (File.Exists(outFilename)) File.Delete(outFilename); + File.Copy(filename, outFilename); + } + else + { + oleFilename = outFilename; + } + } + + var mode = (list_modules) ? CFSUpdateMode.ReadOnly : CFSUpdateMode.Update; + + CompoundFile cf = new CompoundFile(oleFilename, mode, 0); + CFStorage commonStorage = cf.RootStorage; + + if (cf.RootStorage.TryGetStorage("Macros") != null) + { + commonStorage = cf.RootStorage.GetStorage("Macros"); + } + + else if (cf.RootStorage.TryGetStorage("_VBA_PROJECT_CUR") != null) + { + commonStorage = cf.RootStorage.GetStorage("_VBA_PROJECT_CUR"); + } + + else if (cf.RootStorage.TryGetStorage("VBA") != null) + { + // Publisher: VBA -> VBA + var interimStorage = cf.RootStorage.GetStorage("VBA"); + + try + { + var foo = interimStorage.GetStream("dir"); + } + catch (CFItemNotFound ex) when (ex.Message.Contains("Cannot find item")) + { + Console.WriteLine("[.] Publisher identified."); + commonStorage = interimStorage; + } + } + + var vbaStorage = commonStorage.GetStorage("VBA"); + if(vbaStorage == null) + { + throw new CFItemNotFound("Cannot find item"); + } + + // Grab data from "dir" module stream. Used to retrieve list of module streams in document. + byte[] dirStream = Utils.Decompress(vbaStorage.GetStream("dir").GetData()); + List vbaModules = Utils.ParseModulesFromDirStream(dirStream); + + // Only list module streams in document and return + if (list_modules) + { + foreach (var vbaModule in vbaModules) + { + Console.WriteLine("[*] VBA module name: " + vbaModule.moduleName); + } + Console.WriteLine("[*] Finished listing modules\n"); + + return; + } + + string [] dontPurgeTheseModules = { + "ThisDocument", + "ThisWorkbook", + "Sheet", + }; + + byte[] streamBytes; + bool module_found = false; + foreach (var vbaModule in vbaModules) + { + //VBA Purging begins + bool purge = true; + + if (module.Length > 0) + { + purge = vbaModule.moduleName == module; + } + else + { + foreach (string mod in dontPurgeTheseModules) + { + if (vbaModule.moduleName.StartsWith(mod)) + { + purge = false; + } + } + } + + if (purge) + { + Console.WriteLine("\n[*] Purging VBA code in module: " + vbaModule.moduleName); + Console.WriteLine("[*] Offset for code: " + vbaModule.textOffset); + + // Get the CompressedSourceCode from module + streamBytes = vbaStorage.GetStream(vbaModule.moduleName).GetData(); + string OG_VBACode = Utils.GetVBATextFromModuleStream(streamBytes, vbaModule.textOffset); + + // Remove P-code from module stream and set the module to only have the CompressedSourceCode + streamBytes = Utils.RemovePcodeInModuleStream(streamBytes, vbaModule.textOffset, OG_VBACode); + vbaStorage.GetStream(vbaModule.moduleName).SetData(streamBytes); + module_found = true; + } + + if (module_found && module.Length > 0) + { + break; + } + } + + if (module_found == false) + { + Console.WriteLine("\n[!] Could not find module in document (-m). List all module streams with (-l).\n"); + + if (!is_OpenXML) + { + cf.Commit(); + cf.Close(); + CompoundFile.ShrinkCompoundFile(oleFilename); + File.Delete(oleFilename); + if (File.Exists(outFilename)) File.Delete(outFilename); + } + + return; + } + + // Change offset to 0 so that document can find compressed source code. + vbaStorage.GetStream("dir").SetData(Utils.Compress(Utils.ChangeOffset(dirStream))); + Console.WriteLine("\n[*] Module offset changed to 0."); + + // Remove performance cache in _VBA_PROJECT stream. Replace the entire stream with _VBA_PROJECT header. + + string b1 = "00"; + string b2 = "00"; + + Random rnd = new Random(); + b1 = String.Format("{0:X2}", rnd.Next(0, 255)); + b2 = String.Format("{0:X2}", rnd.Next(0, 255)); + + byte[] data = Utils.HexToByte(String.Format("CC-61-FF-FF-00-{0}-{1}", b1, b2)); + vbaStorage.GetStream("_VBA_PROJECT").SetData(data); + Console.WriteLine("[*] PerformanceCache removed from _VBA_PROJECT stream."); + + // Check if document contains SRPs. Must be removed for VBA Purging to work. + try + { + for(int i = 0; i < 10; i++) + { + string srp = String.Format("__SRP_{0}", i); + var str = vbaStorage.TryGetStream(srp); + if (str != null) + { + vbaStorage.Delete(srp); + } + } + + Console.WriteLine("[*] SRP streams deleted!"); + } + catch (Exception e) + { + Console.WriteLine("[*] No SRP streams found."); + } + + // Commit changes and close + cf.Commit(); + cf.Close(); + CompoundFile.ShrinkCompoundFile(oleFilename); + + // Zip the file back up as a docm or xlsm + if (is_OpenXML) + { + if (File.Exists(outFilename)) File.Delete(outFilename); + ZipFile.CreateFromDirectory(unzipTempPath, outFilename); + } + + Console.WriteLine("[+] VBA Purging completed successfully!\n"); + } + + // Error handle for file not found + catch (FileNotFoundException ex) when (ex.Message.Contains("Could not find file")) + { + Console.WriteLine("[!] Could not find path or file (-f). \n"); + } + + // Error handle when document specified and file chosen don't match + catch (CFItemNotFound ex) when (ex.Message.Contains("Cannot find item")) + { + Console.WriteLine("[!] File (-f) does not contain macros.\n"); + } + + // Error handle when document is not OLE/CFBF format + catch (CFFileFormatException) + { + Console.WriteLine("[!] Incorrect filetype (-f). OfficePurge supports documents in .docm or .xlsm format as well as .doc/.xls/.pub in the Office 97-2003 format.\n"); + } + finally + { + if (is_OpenXML) + { + try + { + Directory.Delete(unzipTempPath, true); + } + catch (Exception) + { } + } + } + } + // Error handle for incorrect use of flags + catch (IndexOutOfRangeException) + { + Console.WriteLine("\n[!] Flags (-d), (-f), (-m) need an argument. Make sure you have provided these flags an argument.\n"); + } + } + + public static string CreateUniqueTempDirectory() + { + var uniqueTempDir = Path.GetFullPath(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); + Directory.CreateDirectory(uniqueTempDir); + return uniqueTempDir; + } + + } } diff --git a/OfficePurge/Utils.cs b/OfficePurge/Utils.cs index b0894ee..cf1f04b 100644 --- a/OfficePurge/Utils.cs +++ b/OfficePurge/Utils.cs @@ -9,189 +9,194 @@ namespace OfficePurge { class Utils { - public static Dictionary ParseArgs(string[] args) - { - Dictionary ret = new Dictionary(); - if (args.Length > 0) - { - for (int i = 0; i < args.Length; i += 2) - { - if (args[i].Substring(1).ToLower() == "l") - { - ret.Add(args[i].Substring(1).ToLower(), "true"); - } - else - { - ret.Add(args[i].Substring(1).ToLower(), args[i + 1]); - } - } - } - return ret; - } - public static void HelpMenu() - { - Console.WriteLine("\n __ ____ ____ __ ___ ____ ____ _ _ ____ ___ ____ "); - Console.WriteLine(" / \\( __)( __)( )/ __)( __)( _ \\/ )( \\( _ \\ / __)( __)"); - Console.WriteLine("( O )) _) ) _) )(( (__ ) _) ) __/) \\/ ( ) /( (_ \\ ) _) "); - Console.WriteLine(" \\__/(__) (__) (__)\\___)(____)(__) \\____/(__\\_) \\___/(____) v1.0"); - Console.WriteLine("\n\n Author: Andrew Oliveau\n\n"); - Console.WriteLine(" DESCRIPTION:"); - Console.WriteLine("\n\tOfficePurge is a C# tool that VBA purges malicious Office documents. "); - Console.WriteLine("\n\tVBA purging removes P-code from module streams within Office documents. "); - Console.WriteLine("\n\tDocuments that only contain source code and no compiled code are more"); - Console.WriteLine("\n\tlikely to evade AV detection and YARA rules.\n\n\t\n"); - Console.WriteLine(" USAGE:"); - Console.WriteLine("\n\t-d : Document type (word, excel, publisher)"); - Console.WriteLine("\n\t-f : Filename to VBA Purge"); - Console.WriteLine("\n\t-m : Module within document to VBA Purge"); - Console.WriteLine("\n\t-l : List module streams in document"); - Console.WriteLine("\n\t-h : Show help menu.\n\n"); - Console.WriteLine(" EXAMPLES:"); - Console.WriteLine("\n\t .\\OfficePurge.exe -d word -f .\\malicious.doc -m NewMacros"); - Console.WriteLine("\n\t .\\OfficePurge.exe -d excel -f .\\payroll.xls -m Module1"); - Console.WriteLine("\n\t .\\OfficePurge.exe -d publisher -f .\\donuts.pub -m ThisDocument"); - Console.WriteLine("\n\t .\\OfficePurge.exe -d word -f .\\malicious.doc -l\n\n"); - } - public static List ParseModulesFromDirStream(byte[] dirStream) - { - // 2.3.4.2 dir Stream: Version Independent Project Information - // https://msdn.microsoft.com/en-us/library/dd906362(v=office.12).aspx - // Dir stream is ALWAYS in little endian - - List modules = new List(); - - int offset = 0; - UInt16 tag; - UInt32 wLength; - ModuleInformation currentModule = new ModuleInformation { moduleName = "", textOffset = 0 }; - - while (offset < dirStream.Length) - { - tag = GetWord(dirStream, offset); - wLength = GetDoubleWord(dirStream, offset + 2); - - // taken from Pcodedmp - if (tag == 9) - wLength = 6; - else if (tag == 3) - wLength = 2; - - switch (tag) - { - // MODULESTREAMNAME Record - case 26: - currentModule.moduleName = System.Text.Encoding.UTF8.GetString(dirStream, (int)offset + 6, (int)wLength); - break; - - // MODULEOFFSET Record - case 49: - currentModule.textOffset = GetDoubleWord(dirStream, offset + 6); - modules.Add(currentModule); - currentModule = new ModuleInformation { moduleName = "", textOffset = 0 }; - break; - } - - offset += 6; - offset += (int)wLength; - } - - return modules; - } - - public class ModuleInformation - { - // Name of VBA module stream - public string moduleName; - - // Offset of VBA CompressedSourceCode in VBA module stream - public UInt32 textOffset; - } - - public static UInt16 GetWord(byte[] buffer, int offset) - { - var rawBytes = new byte[2]; - Array.Copy(buffer, offset, rawBytes, 0, 2); - return BitConverter.ToUInt16(rawBytes, 0); - } - - public static UInt32 GetDoubleWord(byte[] buffer, int offset) - { - var rawBytes = new byte[4]; - Array.Copy(buffer, offset, rawBytes, 0, 4); - return BitConverter.ToUInt32(rawBytes, 0); - } - public static byte[] Compress(byte[] data) - { - var buffer = new DecompressedBuffer(data); - var container = new CompressedContainer(buffer); - return container.SerializeData(); - } - public static byte[] Decompress(byte[] data) - { - var container = new CompressedContainer(data); - var buffer = new DecompressedBuffer(container); - return buffer.Data; - } - public static string GetVBATextFromModuleStream(byte[] moduleStream, UInt32 textOffset) - { - string vbaModuleText = Encoding.UTF8.GetString(Decompress(moduleStream.Skip((int)textOffset).ToArray())); - return vbaModuleText; - } - public static byte[] RemovePcodeInModuleStream(byte[] moduleStream, UInt32 textOffset, string OG_VBACode) - { - return Compress(Encoding.UTF8.GetBytes(OG_VBACode)).ToArray(); - } - public static string getOutFilename(String filename) - { - string fn = Path.GetFileNameWithoutExtension(filename); - string ext = Path.GetExtension(filename); - string path = Path.GetDirectoryName(filename); - return Path.Combine(path, fn + "_PURGED" + ext); - } - public static byte[] HexToByte(string hex) - { - hex = hex.Replace("-", ""); - byte[] raw = new byte[hex.Length / 2]; - for (int i = 0; i < raw.Length; i++) - { - raw[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16); - } - return raw; - } - public static byte[] ChangeOffset(byte[] dirStream) - { - int offset = 0; - UInt16 tag; - UInt32 wLength; - - // Change MODULEOFFSET to 0 - string zeros = "\0\0\0\0"; - - while (offset < dirStream.Length) - { - tag = GetWord(dirStream, offset); - wLength = GetDoubleWord(dirStream, offset + 2); - - // taken from Pcodedmp - if (tag == 9) - wLength = 6; - else if (tag == 3) - wLength = 2; - - switch (tag) - { - // MODULEOFFSET Record - case 49: - uint offset_change = GetDoubleWord(dirStream, offset + 6); - UTF8Encoding encoding = new UTF8Encoding(); - encoding.GetBytes(zeros, 0, (int)wLength, dirStream, (int)offset + 6); - break; - } - - offset += 6; - offset += (int)wLength; - } - return dirStream; - } - } + public static Dictionary ParseArgs(string[] args) + { + Dictionary ret = new Dictionary(); + if (args.Length > 0) + { + for (int i = 0; i < args.Length; i += 2) + { + if (args[i].Substring(1).ToLower() == "l") + { + ret.Add(args[i].Substring(1).ToLower(), "true"); + } + else + { + ret.Add(args[i].Substring(1).ToLower(), args[i + 1]); + } + } + } + return ret; + } + public static void HelpMenu() + { + Console.WriteLine("\n __ ____ ____ __ ___ ____ ____ _ _ ____ ___ ____ "); + Console.WriteLine(" / \\( __)( __)( )/ __)( __)( _ \\/ )( \\( _ \\ / __)( __)"); + Console.WriteLine("( O )) _) ) _) )(( (__ ) _) ) __/) \\/ ( ) /( (_ \\ ) _) "); + Console.WriteLine(" \\__/(__) (__) (__)\\___)(____)(__) \\____/(__\\_) \\___/(____) v1.0"); + Console.WriteLine("\n\n Author: Andrew Oliveau, tweaked by Mariusz Banach (mgeeky)\n"); + Console.WriteLine(" DESCRIPTION:"); + Console.WriteLine("\n\tOfficePurge is a C# tool that VBA purges malicious Office documents. "); + Console.WriteLine("\tVBA purging removes P-code from module streams within Office documents. "); + Console.WriteLine("\tDocuments that only contain source code and no compiled code are more"); + Console.WriteLine("\tlikely to evade AV detection and YARA rules.\n\n"); + Console.WriteLine(" SUPPORTED:"); + Console.WriteLine("\t- Word (pre-2007, 2007+)"); + Console.WriteLine("\t- Excel (pre-2007, 2007+)"); + Console.WriteLine("\t- Powerpoint (2007+)"); + Console.WriteLine("\t- Publisher (pre-2007)"); + Console.WriteLine(" USAGE:"); + Console.WriteLine("\t-f : Filename to VBA Purge"); + Console.WriteLine("\t-m : Module within document to VBA Purge"); + Console.WriteLine("\t-l : List module streams in document"); + Console.WriteLine("\t-h : Show help menu.\n"); + Console.WriteLine(" EXAMPLES:"); + Console.WriteLine("\n\t .\\OfficePurge.exe -f .\\malicious.doc -m NewMacros"); + Console.WriteLine("\t .\\OfficePurge.exe -f .\\payroll.xls -m Module1"); + Console.WriteLine("\t .\\OfficePurge.exe -f .\\payroll.pptm -m Module1"); + Console.WriteLine("\t .\\OfficePurge.exe -f .\\donuts.pub -m ThisDocument"); + Console.WriteLine("\t .\\OfficePurge.exe -f .\\malicious.doc -l\n"); + } + public static List ParseModulesFromDirStream(byte[] dirStream) + { + // 2.3.4.2 dir Stream: Version Independent Project Information + // https://msdn.microsoft.com/en-us/library/dd906362(v=office.12).aspx + // Dir stream is ALWAYS in little endian + + List modules = new List(); + + int offset = 0; + UInt16 tag; + UInt32 wLength; + ModuleInformation currentModule = new ModuleInformation { moduleName = "", textOffset = 0 }; + + while (offset < dirStream.Length) + { + tag = GetWord(dirStream, offset); + wLength = GetDoubleWord(dirStream, offset + 2); + + // taken from Pcodedmp + if (tag == 9) + wLength = 6; + else if (tag == 3) + wLength = 2; + + switch (tag) + { + // MODULESTREAMNAME Record + case 26: + currentModule.moduleName = System.Text.Encoding.UTF8.GetString(dirStream, (int)offset + 6, (int)wLength); + break; + + // MODULEOFFSET Record + case 49: + currentModule.textOffset = GetDoubleWord(dirStream, offset + 6); + modules.Add(currentModule); + currentModule = new ModuleInformation { moduleName = "", textOffset = 0 }; + break; + } + + offset += 6; + offset += (int)wLength; + } + + return modules; + } + + public class ModuleInformation + { + // Name of VBA module stream + public string moduleName; + + // Offset of VBA CompressedSourceCode in VBA module stream + public UInt32 textOffset; + } + + public static UInt16 GetWord(byte[] buffer, int offset) + { + var rawBytes = new byte[2]; + Array.Copy(buffer, offset, rawBytes, 0, 2); + return BitConverter.ToUInt16(rawBytes, 0); + } + + public static UInt32 GetDoubleWord(byte[] buffer, int offset) + { + var rawBytes = new byte[4]; + Array.Copy(buffer, offset, rawBytes, 0, 4); + return BitConverter.ToUInt32(rawBytes, 0); + } + public static byte[] Compress(byte[] data) + { + var buffer = new DecompressedBuffer(data); + var container = new CompressedContainer(buffer); + return container.SerializeData(); + } + public static byte[] Decompress(byte[] data) + { + var container = new CompressedContainer(data); + var buffer = new DecompressedBuffer(container); + return buffer.Data; + } + public static string GetVBATextFromModuleStream(byte[] moduleStream, UInt32 textOffset) + { + string vbaModuleText = Encoding.UTF8.GetString(Decompress(moduleStream.Skip((int)textOffset).ToArray())); + return vbaModuleText; + } + public static byte[] RemovePcodeInModuleStream(byte[] moduleStream, UInt32 textOffset, string OG_VBACode) + { + return Compress(Encoding.UTF8.GetBytes(OG_VBACode)).ToArray(); + } + public static string getOutFilename(String filename) + { + string fn = Path.GetFileNameWithoutExtension(filename); + string ext = Path.GetExtension(filename); + string path = Path.GetDirectoryName(filename); + return Path.Combine(path, fn + "_PURGED" + ext); + } + public static byte[] HexToByte(string hex) + { + hex = hex.Replace("-", ""); + byte[] raw = new byte[hex.Length / 2]; + for (int i = 0; i < raw.Length; i++) + { + raw[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16); + } + return raw; + } + public static byte[] ChangeOffset(byte[] dirStream) + { + int offset = 0; + UInt16 tag; + UInt32 wLength; + + // Change MODULEOFFSET to 0 + string zeros = "\0\0\0\0"; + + while (offset < dirStream.Length) + { + tag = GetWord(dirStream, offset); + wLength = GetDoubleWord(dirStream, offset + 2); + + // taken from Pcodedmp + if (tag == 9) + wLength = 6; + else if (tag == 3) + wLength = 2; + + switch (tag) + { + // MODULEOFFSET Record + case 49: + uint offset_change = GetDoubleWord(dirStream, offset + 6); + UTF8Encoding encoding = new UTF8Encoding(); + encoding.GetBytes(zeros, 0, (int)wLength, dirStream, (int)offset + 6); + break; + } + + offset += 6; + offset += (int)wLength; + } + return dirStream; + } + } } diff --git a/OfficePurge/packages.config b/OfficePurge/packages.config new file mode 100644 index 0000000..1bc0cfa --- /dev/null +++ b/OfficePurge/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index cb830fb..e3f69fe 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,21 @@ # OfficePurge +[(This repository is my fork of mandiant/OfficePurge)](https://github.com/mandiant/OfficePurge) + VBA purge your Office documents with OfficePurge. VBA purging removes P-code from module streams within Office documents. Documents that only contain source code and no compiled code are more likely to evade AV detection and YARA rules. Read more here. OfficePurge supports VBA purging Microsoft Office Word (.doc), Excel (.xls), and Publisher (.pub) documents. Original and purged documents for each supported file type with a macro that will spawn calc.exe can be found in `sample-data` folder. Author: Andrew Oliveau (@AndrewOliveau) +Tweaked by: Mariusz Banach / mgeeky # INSTALLATION/BUILDING ## Pre-Compiled -* Use the pre-compiled binary in the Releases section +* Use the pre-compiled binary in the Releases section ## Building Yourself @@ -42,7 +45,6 @@ The below 3rd party libraries are used in this project. # ARGUMENTS/OPTIONS -* -d - Document type to VBA purge (word, excel, publisher) * -f - Document filename to VBA purge * -m - Module within document to VBA purge (ex. Module1) * -l - List modules in a document @@ -50,10 +52,49 @@ The below 3rd party libraries are used in this project. # EXAMPLES -* `OfficePurge.exe -d word -f .\malicious.doc -m NewMacros` -* `OfficePurge.exe -d excel -f .\payroll.xls -m Module1` -* `OfficePurge.exe -d publisher -f .\donuts.pub -m ThisDocument` -* `OfficePurge.exe -d word -f .\malicious.doc -l` +* `OfficePurge.exe -f .\malicious.doc -m NewMacros` +* `OfficePurge.exe -f .\payroll.xls -m Module1` +* `OfficePurge.exe -f .\donuts.pub -m ThisDocument` +* `OfficePurge.exe -f .\donuts.pptm` +* `OfficePurge.exe -f .\malicious.doc -l` + +# Full Usage + +``` + __ ____ ____ __ ___ ____ ____ _ _ ____ ___ ____ + / \( __)( __)( )/ __)( __)( _ \/ )( \( _ \ / __)( __) +( O )) _) ) _) )(( (__ ) _) ) __/) \/ ( ) /( (_ \ ) _) + \__/(__) (__) (__)\___)(____)(__) \____/(__\_) \___/(____) v1.0 + + + Author: Andrew Oliveau, tweaked by Mariusz Banach (mgeeky) + + DESCRIPTION: + + OfficePurge is a C# tool that VBA purges malicious Office documents. + VBA purging removes P-code from module streams within Office documents. + Documents that only contain source code and no compiled code are more + likely to evade AV detection and YARA rules. + + SUPPORTED: + - Word (pre-2007, 2007+) + - Excel (pre-2007, 2007+) + - Powerpoint (2007+) + - Publisher (pre-2007) + + USAGE: + -f : Filename to VBA Purge + -m : Module within document to VBA Purge + -l : List module streams in document + -h : Show help menu. + + EXAMPLES: + .\OfficePurge.exe -f .\malicious.doc -m NewMacros + .\OfficePurge.exe -f .\payroll.xls -m Module1 + .\OfficePurge.exe -f .\payroll.pptm -m Module1 + .\OfficePurge.exe -f .\donuts.pub -m ThisDocument + .\OfficePurge.exe -f .\malicious.doc -l +``` # REFERENCES * Didier Steven's VBA purging article here