From bd40ca6d9df68cfa46c21c6b3900d1733bf8fbd0 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Thu, 23 Jun 2022 13:58:48 -0700 Subject: [PATCH] Use OpenMcdf for MSI --- BurnOutSharp/BurnOutSharp.csproj | 4 +- BurnOutSharp/FileType/MSI.cs | 100 ++++++++++++++++++++++++++++++- README.md | 3 +- 3 files changed, 100 insertions(+), 7 deletions(-) diff --git a/BurnOutSharp/BurnOutSharp.csproj b/BurnOutSharp/BurnOutSharp.csproj index 88d59127..11fb4a17 100644 --- a/BurnOutSharp/BurnOutSharp.csproj +++ b/BurnOutSharp/BurnOutSharp.csproj @@ -22,6 +22,7 @@ + @@ -58,9 +59,6 @@ - - all - diff --git a/BurnOutSharp/FileType/MSI.cs b/BurnOutSharp/FileType/MSI.cs index e49475f2..2307ff0f 100644 --- a/BurnOutSharp/FileType/MSI.cs +++ b/BurnOutSharp/FileType/MSI.cs @@ -1,9 +1,10 @@ using System; using System.Collections.Concurrent; using System.IO; +using System.Text; using BurnOutSharp.Interfaces; using BurnOutSharp.Tools; -using WixToolset.Dtf.WindowsInstaller; +using OpenMcdf; namespace BurnOutSharp.FileType { @@ -40,9 +41,39 @@ namespace BurnOutSharp.FileType string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(tempPath); - using (Database msidb = new Database(file, DatabaseOpenMode.ReadOnly)) + using (CompoundFile msi = new CompoundFile(stream, CFSUpdateMode.ReadOnly, CFSConfiguration.Default)) { - msidb.ExportAll(tempPath); + msi.RootStorage.VisitEntries((e) => + { + if (!e.IsStream) + return; + + var str = msi.RootStorage.GetStream(e.Name); + if (str == null) + return; + + byte[] strData = str.GetData(); + if (strData == null) + return; + + string decoded = DecodeStreamName(e.Name).TrimEnd('\0'); + byte[] nameBytes = Encoding.UTF8.GetBytes(e.Name); + + // UTF-8 encoding of 0x4840. + if (nameBytes[0] == 0xe4 && nameBytes[1] == 0xa1 && nameBytes[2] == 0x80) + decoded = decoded.Substring(3); + + foreach (char c in Path.GetInvalidFileNameChars()) + { + decoded = decoded.Replace(c, '_'); + } + + string filename = Path.Combine(tempPath, decoded); + using (Stream fs = File.OpenWrite(filename)) + { + fs.Write(strData, 0, strData.Length); + } + }, recursive: true); } // Collect and format all found protections @@ -70,5 +101,68 @@ namespace BurnOutSharp.FileType return null; } + + /// Adapted from LibMSI + private static string DecodeStreamName(string input) + { + if (input == null) + return null; + + int count = 0; + byte[] inputBytes = Encoding.UTF8.GetBytes(input); + int p = 0; // inputBytes[0] + + byte[] output = new byte[inputBytes.Length + 1]; + int q = 0; // output[0] + while (p < inputBytes.Length && inputBytes[p] != 0) + { + int ch = inputBytes[p]; + if ((ch == 0xe3 && inputBytes[p + 1] >= 0xa0) || (ch == 0xe4 && inputBytes[p + 1] < 0xa0)) + { + // UTF-8 encoding of 0x3800..0x47ff. + output[q++] = (byte)Mime2Utf(inputBytes[p + 2] & 0x7f); + output[q++] = (byte)Mime2Utf(inputBytes[p + 1] ^ 0xa0); + p += 3; + count += 2; + continue; + } + + if (ch == 0xe4 && inputBytes[p + 1] == 0xa0) + { + // UTF-8 encoding of 0x4800..0x483f. + output[q++] = (byte)Mime2Utf(inputBytes[p + 2] & 0x7f); + p += 3; + count++; + continue; + } + + output[q++] = inputBytes[p++]; + if (ch >= 0xc1) + output[q++] = inputBytes[p++]; + if (ch >= 0xe0) + output[q++] = inputBytes[p++]; + if (ch >= 0xf0) + output[q++] = inputBytes[p++]; + + count++; + } + + output[q] = 0; + return Encoding.ASCII.GetString(output); + } + + /// Adapted from LibMSI + private static int Mime2Utf(int x) + { + if (x < 10) + return x + '0'; + if (x < (10 + 26)) + return x - 10 + 'A'; + if (x < (10 + 26 + 26)) + return x - 10 - 26 + 'a'; + if (x == (10 + 26 + 26)) + return '.'; + return '_'; + } } } diff --git a/README.md b/README.md index 5f058050..09d2cb11 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,13 @@ C# port of the protection scanning ability of [BurnOut](http://burnout.sourcefor In addition to the original BurnOut code, the following libraries (or ports thereof) are used for file handling: - [HLLibSharp](https://github.com/mnadareski/HLLibSharp) - Various Valve archive format extraction +- [LibMSPackSharp](https://github.com/mnadareski/LibMSPackSharp) - Microsoft CAB extraction +- [openmcdf](https://github.com/ironfede/openmcdf) - MSI extraction - [psxt001z](https://github.com/Dremora/psxt001z) - PS1 LibCrypt detection - [SharpCompress](https://github.com/adamhathcock/sharpcompress) - 7zip/GZip/RAR/PKZIP extraction - [StormLibSharp](https://github.com/robpaveza/stormlibsharp) - MPQ extraction - [UnshieldSharp](https://github.com/mnadareski/UnshieldSharp) - InstallShield CAB extraction - [WiseUnpacker](https://github.com/mnadareski/WiseUnpacker) - Wise Installer extraction -- [WixToolset.Dtf](https://github.com/wixtoolset/Dtf) - MSI and Microsoft CAB extraction Please note that due to current library limitations, the functionality of StormLibSharp and WixToolset.Dtf are locked to Windows only.