From 0d75ee135c68fe3f07be6b5cbacd933a4d7ebbd0 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 14 Sep 2021 13:56:43 -0700 Subject: [PATCH] Combine SafeCast into SafeDisc; improvements --- .../Microsoft/PortableExecutable.cs | 57 +++++- BurnOutSharp/ProtectionType/SafeCast.cs | 86 --------- BurnOutSharp/ProtectionType/SafeDisc.cs | 174 +++++++----------- 3 files changed, 118 insertions(+), 199 deletions(-) delete mode 100644 BurnOutSharp/ProtectionType/SafeCast.cs diff --git a/BurnOutSharp/ExecutableType/Microsoft/PortableExecutable.cs b/BurnOutSharp/ExecutableType/Microsoft/PortableExecutable.cs index 723c800a..8b048258 100644 --- a/BurnOutSharp/ExecutableType/Microsoft/PortableExecutable.cs +++ b/BurnOutSharp/ExecutableType/Microsoft/PortableExecutable.cs @@ -233,17 +233,38 @@ namespace BurnOutSharp.ExecutableType.Microsoft /// /// Get the raw bytes from a section, if possible /// - public byte[] ReadRawSection(Stream stream, string sectionName, bool first = true) + public byte[] ReadRawSection(Stream stream, string sectionName, bool first = true, int offset = 0) { + // Special cases for non-offset data + if (offset == 0) + { + switch (sectionName) + { + case ".data": + return DataSectionRaw; + case ".edata": + return ExportDataSectionRaw; + case ".idata": + return ImportDataSectionRaw; + case ".rdata": + return ResourceDataSectionRaw; + case ".text": + return TextSectionRaw; + } + } + var section = first ? GetFirstSection(sectionName, true) : GetLastSection(sectionName, true); if (section == null) return null; lock (stream) { + int startingIndex = (int)Math.Max(section.PointerToRawData + offset, 0); + int readLength = (int)Math.Min(section.VirtualSize - offset, stream.Length); + long originalPosition = stream.Position; - stream.Seek((int)section.PointerToRawData, SeekOrigin.Begin); - byte[] sectionData = stream.ReadBytes((int)section.VirtualSize); + stream.Seek(startingIndex, SeekOrigin.Begin); + byte[] sectionData = stream.ReadBytes(readLength); stream.Seek(originalPosition, SeekOrigin.Begin); return sectionData; } @@ -253,14 +274,34 @@ namespace BurnOutSharp.ExecutableType.Microsoft /// /// Get the raw bytes from a section, if possible /// - public byte[] ReadRawSection(byte[] content, string sectionName, bool first = true) + public byte[] ReadRawSection(byte[] content, string sectionName, bool first = true, int offset = 0) { + // Special cases for non-offset data + if (offset == 0) + { + switch (sectionName) + { + case ".data": + return DataSectionRaw; + case ".edata": + return ExportDataSectionRaw; + case ".idata": + return ImportDataSectionRaw; + case ".rdata": + return ResourceDataSectionRaw; + case ".text": + return TextSectionRaw; + } + } + var section = first ? GetFirstSection(sectionName, true) : GetLastSection(sectionName, true); if (section == null) return null; - int offset = (int)section.PointerToRawData; - return content.ReadBytes(ref offset, (int)section.VirtualSize); + int startingIndex = (int)Math.Max(section.PointerToRawData + offset, 0); + int readLength = (int)Math.Min(section.VirtualSize - offset, content.Length); + + return content.ReadBytes(ref startingIndex, readLength); } /// @@ -358,7 +399,7 @@ namespace BurnOutSharp.ExecutableType.Microsoft pex.DataSectionRaw = pex.ReadRawSection(stream, ".data", false) ?? pex.ReadRawSection(stream, "DATA", false); // Export Table - pex.ImportDataSectionRaw = pex.ReadRawSection(stream, ".edata", false); + pex.ExportDataSectionRaw = pex.ReadRawSection(stream, ".edata", false); // Import Table pex.ImportDataSectionRaw = pex.ReadRawSection(stream, ".idata", false); @@ -446,7 +487,7 @@ namespace BurnOutSharp.ExecutableType.Microsoft pex.DataSectionRaw = pex.ReadRawSection(content, ".data", false) ?? pex.ReadRawSection(content, "DATA", false); // Export Table - pex.ImportDataSectionRaw = pex.ReadRawSection(content, ".edata", false); + pex.ExportDataSectionRaw = pex.ReadRawSection(content, ".edata", false); // Import Table pex.ImportDataSectionRaw = pex.ReadRawSection(content, ".idata", false); diff --git a/BurnOutSharp/ProtectionType/SafeCast.cs b/BurnOutSharp/ProtectionType/SafeCast.cs deleted file mode 100644 index 32aadb89..00000000 --- a/BurnOutSharp/ProtectionType/SafeCast.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; -using BurnOutSharp.ExecutableType.Microsoft; -using BurnOutSharp.Matching; -using BurnOutSharp.Tools; - -namespace BurnOutSharp.ProtectionType -{ - // TODO: Add the content checks from SafeDisc here - // TODO: Investigate if this entire file should be wrapped into SafeDisc - public class SafeCast : IContentCheck, IPathCheck - { - /// - public string CheckContents(string file, byte[] fileContent, bool includeDebug, PortableExecutable pex, NewExecutable nex) - { - // TODO: Obtain a sample to find where this string is in a typical executable - var contentMatchSets = new List - { - new ContentMatchSet(new List - { - // BoG_ *90.0&!! Yy> - new byte?[] - { - 0x42, 0x6F, 0x47, 0x5F, 0x20, 0x2A, 0x39, 0x30, - 0x2E, 0x30, 0x26, 0x21, 0x21, 0x20, 0x20, 0x59, - 0x79, 0x3E - }, - - // product activation library - new byte?[] - { - 0x70, 0x72, 0x6F, 0x64, 0x75, 0x63, 0x74, 0x20, - 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, - 0x6F, 0x6E, 0x20, 0x6C, 0x69, 0x62, 0x72, 0x61, - 0x72, 0x79 - }, - }, GetVersion, "SafeCast"), - }; - - return MatchUtil.GetFirstMatch(file, fileContent, contentMatchSets, includeDebug); - } - - /// - public ConcurrentQueue CheckDirectoryPath(string path, IEnumerable files) - { - var matchers = new List - { - new PathMatchSet(new PathMatch("cdac11ba.exe", useEndsWith: true), "SafeCast"), - }; - - return MatchUtil.GetAllMatches(files, matchers, any: true); - } - - /// - public string CheckFilePath(string path) - { - var matchers = new List - { - new PathMatchSet(new PathMatch("cdac11ba.exe", useEndsWith: true), "SafeCast"), - }; - - return MatchUtil.GetFirstMatch(path, matchers, any: true); - } - - public static string GetVersion(string file, byte[] fileContent, List positions) - { - int index = positions[0] + 20; // Begin reading after "BoG_ *90.0&!! Yy>" for old SafeDisc - int version = fileContent.ReadInt32(ref index); - int subVersion = fileContent.ReadInt32(ref index); - int subsubVersion = fileContent.ReadInt32(ref index); - - if (version != 0) - return $"{version}.{subVersion:00}.{subsubVersion:000}"; - - index = positions[0] + 18 + 14; // Begin reading after "BoG_ *90.0&!! Yy>" for newer SafeDisc - version = fileContent.ReadInt32(ref index); - subVersion = fileContent.ReadInt32(ref index); - subsubVersion = fileContent.ReadInt32(ref index); - - if (version == 0) - return string.Empty; - - return $"{version}.{subVersion:00}.{subsubVersion:000}"; - } - } -} diff --git a/BurnOutSharp/ProtectionType/SafeDisc.cs b/BurnOutSharp/ProtectionType/SafeDisc.cs index a3955cfc..feae2230 100644 --- a/BurnOutSharp/ProtectionType/SafeDisc.cs +++ b/BurnOutSharp/ProtectionType/SafeDisc.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using BurnOutSharp.ExecutableType.Microsoft; @@ -7,6 +8,8 @@ using BurnOutSharp.Tools; namespace BurnOutSharp.ProtectionType { + // TODO: Figure out how to properly distinguish SafeDisc and SafeCast since both use + // the same generic BoG_ string. The current combination check doesn't seem consistent public class SafeDisc : IContentCheck, IPathCheck { /// @@ -38,6 +41,9 @@ namespace BurnOutSharp.ProtectionType new PathMatchSet(new PathMatch("00000001.LT1", useEndsWith: true), "SafeDisc Lite"), new PathMatchSet(".SafeDiscDVD.bundle", "SafeDisc for Macintosh"), + + new PathMatchSet(new PathMatch("cdac11ba.exe", useEndsWith: true), "SafeCast"), + new PathMatchSet(new PathMatch("cdac14ba.dll", useEndsWith: true), "SafeCast"), }; /// @@ -48,117 +54,29 @@ namespace BurnOutSharp.ProtectionType if (sections == null) return null; - // Get the .text section, if it exists -- TODO: Figure out how to capture this automatically - var textSection = pex.GetFirstSection(".text", exact: true); - if (textSection != null) - { - // This subtract is needed because BoG_ starts before the .text section - int sectionAddr = (int)textSection.PointerToRawData - 64; - int sectionEnd = sectionAddr + (int)textSection.VirtualSize; - var matchers = new List - { - // BoG_ *90.0&!! Yy> - new ContentMatchSet( - new ContentMatch(new byte?[] - { - 0x42, 0x6F, 0x47, 0x5F, 0x20, 0x2A, 0x39, 0x30, - 0x2E, 0x30, 0x26, 0x21, 0x21, 0x20, 0x20, 0x59, - 0x79, 0x3E - }, start: sectionAddr, end: sectionEnd), - GetVersion, "SafeDisc"), + string name = Utilities.GetFileDescription(pex); + if (!string.IsNullOrWhiteSpace(name) && name.Equals("SafeCast2", StringComparison.OrdinalIgnoreCase)) + return $"SafeCast"; - // (char)0x00 + (char)0x00 + BoG_ - new ContentMatchSet( - new ContentMatch(new byte?[] { 0x00, 0x00, 0x42, 0x6F, 0x47, 0x5F }, start: sectionAddr, end: sectionEnd), - Get320to4xVersion, "SafeDisc"), - }; - - string match = MatchUtil.GetFirstMatch(file, fileContent, matchers, includeDebug); - if (!string.IsNullOrWhiteSpace(match)) - return match; - } + // Get the .text section, if it exists + string match = CheckSectionForProtection(file, fileContent, includeDebug, pex, ".text"); + if (!string.IsNullOrWhiteSpace(match)) + return match; // Get the .txt2 section, if it exists - var txt2Section = pex.GetFirstSection(".txt2", exact: true); - if (txt2Section != null) - { - // This subtract is needed because BoG_ starts before the .txt2 section - int sectionAddr = (int)txt2Section.PointerToRawData - 64; - int sectionEnd = sectionAddr + (int)txt2Section.VirtualSize; - var matchers = new List - { - // BoG_ *90.0&!! Yy> - new ContentMatchSet( - new ContentMatch(new byte?[] - { - 0x42, 0x6F, 0x47, 0x5F, 0x20, 0x2A, 0x39, 0x30, - 0x2E, 0x30, 0x26, 0x21, 0x21, 0x20, 0x20, 0x59, - 0x79, 0x3E - }, start: sectionAddr, end: sectionEnd), - GetVersion, "SafeDisc"), - - // (char)0x00 + (char)0x00 + BoG_ - new ContentMatchSet( - new ContentMatch(new byte?[] { 0x00, 0x00, 0x42, 0x6F, 0x47, 0x5F }, start: sectionAddr, end: sectionEnd), - Get320to4xVersion, "SafeDisc"), - }; - - string match = MatchUtil.GetFirstMatch(file, fileContent, matchers, includeDebug); - if (!string.IsNullOrWhiteSpace(match)) - return match; - } + match = CheckSectionForProtection(file, fileContent, includeDebug, pex, ".txt2"); + if (!string.IsNullOrWhiteSpace(match)) + return match; // Get the CODE section, if it exists - var codeSection = pex.GetFirstSection("CODE", exact: true); - if (codeSection != null) - { - // This subtract is needed because BoG_ starts before the CODE section - int sectionAddr = (int)codeSection.PointerToRawData - 64; - int sectionEnd = sectionAddr + (int)codeSection.VirtualSize; - var matchers = new List - { - // BoG_ *90.0&!! Yy> - new ContentMatchSet( - new ContentMatch(new byte?[] - { - 0x42, 0x6F, 0x47, 0x5F, 0x20, 0x2A, 0x39, 0x30, - 0x2E, 0x30, 0x26, 0x21, 0x21, 0x20, 0x20, 0x59, - 0x79, 0x3E - }, start: sectionAddr, end: sectionEnd), - GetVersion, "SafeDisc"), - - // (char)0x00 + (char)0x00 + BoG_ - new ContentMatchSet( - new ContentMatch(new byte?[] { 0x00, 0x00, 0x42, 0x6F, 0x47, 0x5F }, start: sectionAddr, end: sectionEnd), - Get320to4xVersion, "SafeDisc"), - }; - - string match = MatchUtil.GetFirstMatch(file, fileContent, matchers, includeDebug); - if (!string.IsNullOrWhiteSpace(match)) - return match; - } + match = CheckSectionForProtection(file, fileContent, includeDebug, pex, "CODE"); + if (!string.IsNullOrWhiteSpace(match)) + return match; // Get the .data section, if it exists - if (pex.DataSectionRaw != null) - { - var matchers = new List - { - // BoG_ *90.0&!! Yy> - new ContentMatchSet(new byte?[] - { - 0x42, 0x6F, 0x47, 0x5F, 0x20, 0x2A, 0x39, 0x30, - 0x2E, 0x30, 0x26, 0x21, 0x21, 0x20, 0x20, 0x59, - 0x79, 0x3E - }, GetVersion, "SafeDisc"), - - // (char)0x00 + (char)0x00 + BoG_ - new ContentMatchSet(new byte?[] { 0x00, 0x00, 0x42, 0x6F, 0x47, 0x5F }, Get320to4xVersion, "SafeDisc"), - }; - - string match = MatchUtil.GetFirstMatch(file, pex.DataSectionRaw, matchers, includeDebug); - if (!string.IsNullOrWhiteSpace(match)) - return match; - } + match = CheckSectionForProtection(file, fileContent, includeDebug, pex, ".data"); + if (!string.IsNullOrWhiteSpace(match)) + return match; // Get the stxt371 and stxt774 sections, if they exist -- TODO: Confirm if both are needed or either/or is fine bool stxt371Section = pex.ContainsSection("stxt371", exact: true); @@ -350,5 +268,51 @@ namespace BurnOutSharp.ProtectionType else return "1-4"; } + + private string CheckSectionForProtection(string file, byte[] fileContent, bool includeDebug, PortableExecutable pex, string sectionName) + { + // This subtract is needed because BoG_ starts before the section + var sectionRaw = pex.ReadRawSection(fileContent, sectionName, first: true, offset: -64); + if (sectionRaw != null) + { + var matchers = new List + { + new ContentMatchSet(new List + { + // BoG_ *90.0&!! Yy> + new byte?[] + { + 0x42, 0x6F, 0x47, 0x5F, 0x20, 0x2A, 0x39, 0x30, + 0x2E, 0x30, 0x26, 0x21, 0x21, 0x20, 0x20, 0x59, + 0x79, 0x3E + }, + + // product activation library + new byte?[] + { + 0x70, 0x72, 0x6F, 0x64, 0x75, 0x63, 0x74, 0x20, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, + 0x6F, 0x6E, 0x20, 0x6C, 0x69, 0x62, 0x72, 0x61, + 0x72, 0x79 + }, + }, GetVersion, "SafeCast"), + + // BoG_ *90.0&!! Yy> + new ContentMatchSet(new byte?[] + { + 0x42, 0x6F, 0x47, 0x5F, 0x20, 0x2A, 0x39, 0x30, + 0x2E, 0x30, 0x26, 0x21, 0x21, 0x20, 0x20, 0x59, + 0x79, 0x3E + }, GetVersion, "SafeDisc"), + + // (char)0x00 + (char)0x00 + BoG_ + new ContentMatchSet(new byte?[] { 0x00, 0x00, 0x42, 0x6F, 0x47, 0x5F }, Get320to4xVersion, "SafeDisc"), + }; + + return MatchUtil.GetFirstMatch(file, sectionRaw, matchers, includeDebug); + } + + return null; + } } }