diff --git a/BurnOutSharp/ExecutableType/Microsoft/Entries/ResourceDirectoryTableEntry.cs b/BurnOutSharp/ExecutableType/Microsoft/Entries/ResourceDirectoryTableEntry.cs index d850f70f..b48383a3 100644 --- a/BurnOutSharp/ExecutableType/Microsoft/Entries/ResourceDirectoryTableEntry.cs +++ b/BurnOutSharp/ExecutableType/Microsoft/Entries/ResourceDirectoryTableEntry.cs @@ -62,7 +62,7 @@ namespace BurnOutSharp.ExecutableType.Microsoft.Entries long lastPosition = stream.Position; try { - int dataEntryAddress = (int)EVORE.ConvertVirtualAddress(rdte.DataEntryOffset, sections); + int dataEntryAddress = (int)ConvertVirtualAddress(rdte.DataEntryOffset, sections); if (dataEntryAddress > 0) { stream.Seek(dataEntryAddress, SeekOrigin.Begin); @@ -93,7 +93,7 @@ namespace BurnOutSharp.ExecutableType.Microsoft.Entries { try { - int dataEntryAddress = (int)EVORE.ConvertVirtualAddress(rdte.DataEntryOffset, sections); + int dataEntryAddress = (int)ConvertVirtualAddress(rdte.DataEntryOffset, sections); if (dataEntryAddress > 0) rdte.DataEntry = ResourceDataEntry.Deserialize(content, dataEntryAddress); } @@ -104,5 +104,29 @@ namespace BurnOutSharp.ExecutableType.Microsoft.Entries return rdte; } + + /// + /// Convert a virtual address to a physical one + /// + /// Virtual address to convert + /// Array of sections to check against + /// Physical address, 0 on error + private static uint ConvertVirtualAddress(uint virtualAddress, SectionHeader[] sections) + { + // Loop through all of the sections + for (int i = 0; i < sections.Length; i++) + { + // If the section is invalid, just skip it + if (sections[i] == null) + continue; + + // Attempt to derive the physical address from the current section + var section = sections[i]; + if (virtualAddress >= section.VirtualAddress && virtualAddress <= section.VirtualAddress + section.VirtualSize) + return section.PointerToRawData + virtualAddress - section.VirtualAddress; + } + + return 0; + } } } \ No newline at end of file diff --git a/BurnOutSharp/ProtectionType/ProtectDisc.cs b/BurnOutSharp/ProtectionType/ProtectDisc.cs index 4770c97c..e95c888a 100644 --- a/BurnOutSharp/ProtectionType/ProtectDisc.cs +++ b/BurnOutSharp/ProtectionType/ProtectDisc.cs @@ -1,12 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using BurnOutSharp.ExecutableType.Microsoft; using BurnOutSharp.Matching; namespace BurnOutSharp.ProtectionType { - // TODO: Investigate the connection between this and VOB Protect CD/DVD + // This protection was called VOB ProtectCD / ProtectDVD in versions prior to 6 public class ProtectDISC : IContentCheck { /// @@ -40,6 +41,48 @@ namespace BurnOutSharp.ProtectionType return match; } + // Get the .data section, if it exists + var dataSection = sections.FirstOrDefault(s => Encoding.ASCII.GetString(s.Name).StartsWith(".data")); + if (dataSection != null) + { + int sectionAddr = (int)dataSection.PointerToRawData; + int sectionEnd = sectionAddr + (int)dataSection.VirtualSize; + var matchers = new List + { + // DCP-BOV + (char)0x00 + (char)0x00 + new ContentMatchSet( + new ContentMatch(new byte?[] { 0x44, 0x43, 0x50, 0x2D, 0x42, 0x4F, 0x56, 0x00, 0x00 }, start: sectionAddr, end: sectionEnd), + GetVersion3till6, "VOB ProtectCD/DVD"), + }; + + string match = MatchUtil.GetFirstMatch(file, fileContent, matchers, includeDebug); + if (!string.IsNullOrWhiteSpace(match)) + return match; + } + + // Get the second to last section + var secondToLastSection = sections.Length > 1 ? sections[sections.Length - 2] : null; + if (secondToLastSection != null) + { + int sectionAddr = (int)secondToLastSection.PointerToRawData; + int sectionEnd = sectionAddr + (int)secondToLastSection.VirtualSize; + var matchers = new List + { + // VOB ProtectCD + new ContentMatchSet( + new ContentMatch(new byte?[] + { + 0x56, 0x4F, 0x42, 0x20, 0x50, 0x72, 0x6F, 0x74, + 0x65, 0x63, 0x74, 0x43, 0x44 + }, start: sectionAddr, end: sectionEnd), + GetOldVersion, "VOB ProtectCD/DVD"), + }; + + string match = MatchUtil.GetFirstMatch(file, fileContent, matchers, includeDebug); + if (!string.IsNullOrWhiteSpace(match)) + return match; + } + // Get the last section (example names: ACE5, akxpxgcv, and piofinqb) var lastSection = sections.LastOrDefault(); if (lastSection != null) @@ -52,6 +95,11 @@ namespace BurnOutSharp.ProtectionType new ContentMatchSet( new ContentMatch(new byte?[] { 0x48, 0xFA, 0x4D, 0x45, 0x54, 0x49, 0x4E, 0x46 }, start: sectionAddr, end: sectionEnd), GetVersion76till10, "ProtectDISC"), + + // DCP-BOV + (char)0x00 + (char)0x00 + new ContentMatchSet( + new ContentMatch(new byte?[] { 0x44, 0x43, 0x50, 0x2D, 0x42, 0x4F, 0x56, 0x00, 0x00 }, start: sectionAddr, end: sectionEnd), + GetVersion3till6, "VOB ProtectCD/DVD"), }; string match = MatchUtil.GetFirstMatch(file, fileContent, matchers, includeDebug); @@ -59,9 +107,39 @@ namespace BurnOutSharp.ProtectionType return match; } + // Get the .vob.pcd section, if it exists + var vobpcdSection = sections.FirstOrDefault(s => Encoding.ASCII.GetString(s.Name).StartsWith(".vob.pcd")); + if (vobpcdSection != null) + return "VOB ProtectCD"; + return null; } + public static string GetOldVersion(string file, byte[] fileContent, List positions) + { + int position = positions[0] + 16; // Begin reading after "VOB ProtectCD" + char[] version = new ArraySegment(fileContent, position, 4).Select(b => (char)b).ToArray(); + if (char.IsNumber(version[0]) && char.IsNumber(version[2]) && char.IsNumber(version[3])) + return $"{version[0]}.{version[2]}{version[3]}"; + + // Look for the legacy support version + position = positions[0] + "VOB ProtectCD with LEGACY SYSIPHOS Support V".Length; + version = new ArraySegment(fileContent, position, 7).Select(b => (char)b).ToArray(); + if (char.IsNumber(version[0]) && char.IsNumber(version[2]) && char.IsNumber(version[4])) + return new string(version); + + return "old"; + } + + public static string GetVersion3till6(string file, byte[] fileContent, List positions) + { + string version = GetVOBVersion(fileContent, positions[0]); + if (version.Length > 0) + return version; + + return $"5.9-6.0 {GetVOBBuild(fileContent, positions[0])}"; + } + public static string GetVersion6till8(string file, byte[] fileContent, List positions) { string version, strBuild = string.Empty; @@ -196,5 +274,23 @@ namespace BurnOutSharp.ProtectionType return string.Empty; } + + private static string GetVOBBuild(byte[] fileContent, int position) + { + if (!char.IsNumber((char)fileContent[position - 13])) + return string.Empty; //Build info removed + + int build = BitConverter.ToInt16(fileContent, position - 4); // Check if this is supposed to be a 4-byte read + return $" (Build {build})"; + } + + // TODO: Ensure that this version finding works for all known versions + private static string GetVOBVersion(byte[] fileContent, int position) + { + byte version = fileContent[position - 2]; + byte subVersion = (byte)((fileContent[position - 3] & 0xF0) >> 4); + byte subSubVersion = (byte)((fileContent[position - 4] & 0xF0) >> 4); + return $"{version}.{subVersion}.{subSubVersion}"; + } } } diff --git a/BurnOutSharp/ProtectionType/VOBProtectCDDVD.cs b/BurnOutSharp/ProtectionType/VOBProtectCDDVD.cs deleted file mode 100644 index 2bbd5b08..00000000 --- a/BurnOutSharp/ProtectionType/VOBProtectCDDVD.cs +++ /dev/null @@ -1,238 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading; -using BurnOutSharp.Matching; -using BurnOutSharp.Tools; - -namespace BurnOutSharp.ProtectionType -{ - public class VOBProtectCDDVD : IContentCheck, IPathCheck - { - /// - public List GetContentMatchSets() - { - return new List - { - // VOB ProtectCD - new ContentMatchSet(new byte?[] - { - 0x56, 0x4F, 0x42, 0x20, 0x50, 0x72, 0x6F, 0x74, - 0x65, 0x63, 0x74, 0x43, 0x44 - }, GetOldVersion, "VOB ProtectCD/DVD"), - - // DCP-BOV + (char)0x00 + (char)0x00 - new ContentMatchSet(new byte?[] { 0x44, 0x43, 0x50, 0x2D, 0x42, 0x4F, 0x56, 0x00, 0x00 }, GetVersion, "VOB ProtectCD/DVD"), - - // .vob.pcd - new ContentMatchSet(new byte?[] { 0x2E, 0x76, 0x6F, 0x62, 0x2E, 0x70, 0x63, 0x64 }, "VOB ProtectCD"), - }; - } - - /// - public string CheckContents(string file, byte[] fileContent, bool includeDebug = false) => null; - - /// - public ConcurrentQueue CheckDirectoryPath(string path, IEnumerable files) - { - var matchers = new List - { - new PathMatchSet(new PathMatch("VOB-PCD.KEY", useEndsWith: true), "VOB ProtectCD/DVD"), - }; - - return MatchUtil.GetAllMatches(files, matchers, any: true); - } - - /// - public string CheckFilePath(string path) - { - var matchers = new List - { - new PathMatchSet(new PathMatch("VOB-PCD.KEY", useEndsWith: true), "VOB ProtectCD/DVD"), - }; - - return MatchUtil.GetFirstMatch(path, matchers, any: true); - } - - public static string GetOldVersion(string file, byte[] fileContent, List positions) - { - int position = positions[0]--; // TODO: Verify this subtract - char[] version = new ArraySegment(fileContent, position + 16, 4).Select(b => (char)b).ToArray(); // Begin reading after "VOB ProtectCD" - if (char.IsNumber(version[0]) && char.IsNumber(version[2]) && char.IsNumber(version[3])) - return $"{version[0]}.{version[2]}{version[3]}"; - - return "old"; - } - - public static string GetVersion(string file, byte[] fileContent, List positions) - { - string version = GetVersion(fileContent, --positions[0]); // TODO: Verify this subtract - if (version.Length > 0) - return version; - - version = SearchProtectDiscVersion(file, fileContent); - if (version.Length > 0) - { - if (version.StartsWith("2")) - version = $"6{version.Substring(1)}"; - - return version; - } - - return $"5.9-6.0 {GetBuild(fileContent, positions[0])}"; - } - - private static string GetBuild(byte[] fileContent, int position) - { - if (!char.IsNumber((char)fileContent[position - 13])) - return string.Empty; //Build info removed - - int build = BitConverter.ToInt16(fileContent, position - 4); // Check if this is supposed to be a 4-byte read - return $" (Build {build})"; - } - - private static string GetVersion(byte[] fileContent, int position) - { - if (fileContent[position - 2] == 5) - { - int index = position - 4; - byte subsubVersion = (byte)((fileContent[index] & 0xF0) >> 4); - index++; - byte subVersion = (byte)((fileContent[index] & 0xF0) >> 4); - return $"5.{subVersion}.{subsubVersion}"; - } - - return string.Empty; - } - - // TODO: Analyze this method and figure out if this can be done without attempting execution - private static string SearchProtectDiscVersion(string file, byte[] fileContent) - { - // If the file isn't executable, don't even bother - if (!EVORE.IsPEExecutable(fileContent)) - return string.Empty; - - // Get some of the required paths - string tempexe = EVORE.MakeTempFile(fileContent); - string[] dependentDlls = EVORE.CopyDependentDlls(file, fileContent); - string pdPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ProtectDisc"); - - // Clean up any temp files before attempting to run - Utilities.SafeTempDelete("a*.tmp"); - Utilities.SafeTempDelete("PCD*.sys"); - if (Directory.Exists(pdPath)) - Utilities.SafeDelete(Path.Combine(pdPath, "p*.dll")); - - // Try to safely start the temp executable - Process exe = EVORE.StartSafe(tempexe); - if (exe == null) - return string.Empty; - - string version = ""; - Process[] processes = new Process[0]; - DateTime timestart = DateTime.Now; - do - { - exe.Refresh(); - string[] files = null; - - // Check for ProtectDisc 8.2-x - if (Directory.Exists(pdPath)) - files = Directory.GetFiles(pdPath, "p*.dll"); - - if (files.Any()) - { - string fileVersion = Utilities.GetFileVersion(files[0]); - if (!string.IsNullOrWhiteSpace(fileVersion)) - { - version = fileVersion; - - // ProtectDisc 9 uses a ProtectDisc-Core dll version 8.0.x - if (version.StartsWith("8.0")) - version = string.Empty; - - break; - } - } - - //check for ProtectDisc 7.1-8.1 - files = Directory.GetFiles(Path.GetTempPath(), "a*.tmp"); - if (files.Any()) - { - string fileVersion = Utilities.GetFileVersion(files[0]); - if (!string.IsNullOrWhiteSpace(fileVersion)) - { - version = fileVersion; - break; - } - } - - if (exe.HasExited) - break; - - processes = Process.GetProcessesByName(exe.ProcessName); - if (processes.Length == 2) - { - processes[0].Refresh(); - processes[1].Refresh(); - if (processes[1].WorkingSet64 > exe.WorkingSet64) - exe = processes[1]; - else if (processes[0].WorkingSet64 > exe.WorkingSet64) //else if (processes[0].Modules.Count > exe.Modules.Count) - exe = processes[0]; - } - } while (processes.Length > 0 && DateTime.Now.Subtract(timestart).TotalSeconds < 20); - - Thread.Sleep(500); - if (!exe.HasExited) - { - processes = Process.GetProcessesByName(exe.ProcessName); - if (processes.Length == 2) - { - try - { - processes[0].Kill(); - } - catch { } - processes[0].Close(); - try - { - processes[1].Kill(); - } - catch { } - } - else - { - exe.Refresh(); - try - { - exe.Kill(); - } - catch { } - } - } - - exe.Close(); - Thread.Sleep(500); - - // Clean up any temp files after running - Utilities.SafeDelete(tempexe); - Utilities.SafeTempDelete("a*.tmp"); - Utilities.SafeTempDelete("PCD*.sys"); - if (Directory.Exists(pdPath)) - Utilities.SafeDelete(Path.Combine(pdPath, "p*.dll")); - - if (dependentDlls != null) - { - foreach (string dll in dependentDlls) - { - Utilities.SafeDelete(dll); - } - } - - return version; - } - } -} diff --git a/BurnOutSharp/Tools/EVORE.cs b/BurnOutSharp/Tools/EVORE.cs deleted file mode 100644 index cb021eb3..00000000 --- a/BurnOutSharp/Tools/EVORE.cs +++ /dev/null @@ -1,227 +0,0 @@ -//this file is part of BurnOut -//Copyright (C)2005-2010 Gernot Knippen -//Ported code with augments Copyright (C)2018 Matt Nadareski -// -//This program is free software; you can redistribute it and/or -//modify it under the terms of the GNU General Public License -//as published by the Free Software Foundation; either -//version 2 of the License, or (at your option) any later version. -// -//This program is distributed in the hope that it will be useful, -//but WITHOUT ANY WARRANTY; without even the implied warranty of -//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -//GNU General Public License for more details. -// -//You can get a copy of the GNU General Public License -//by writing to the Free Software -//Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using BurnOutSharp.ExecutableType.Microsoft; -using BurnOutSharp.ExecutableType.Microsoft.Headers; -using BurnOutSharp.ExecutableType.Microsoft.Sections; -using BurnOutSharp.ExecutableType.Microsoft.Tables; - -namespace BurnOutSharp.Tools -{ - internal static class EVORE - { - /// - /// Convert a virtual address to a physical one - /// - /// Virtual address to convert - /// Array of sections to check against - /// Physical address, 0 on error - internal static uint ConvertVirtualAddress(uint virtualAddress, SectionHeader[] sections) - { - // Loop through all of the sections - for (int i = 0; i < sections.Length; i++) - { - // If the section is invalid, just skip it - if (sections[i] == null) - continue; - - // Attempt to derive the physical address from the current section - var section = sections[i]; - if (virtualAddress >= section.VirtualAddress && virtualAddress <= section.VirtualAddress + section.VirtualSize) - return section.PointerToRawData + virtualAddress - section.VirtualAddress; - } - - return 0; - } - - /// - /// Checks if the file contents represent a PE executable - /// - /// File contents to check - /// True if the file is an EXE, false otherwise - internal static bool IsPEExecutable(byte[] fileContent) - { - if (fileContent == null) - return false; - - try - { - PortableExecutable pex = PortableExecutable.Deserialize(fileContent, 0); - return !pex.ImageFileHeader.Characteristics.HasFlag(ImageObjectCharacteristics.IMAGE_FILE_DLL); - } - catch - { - return false; - } - } - - /// - /// Writes the file contents to a temporary file, if possible - /// - /// File contents to write - /// Optional extension for the temporary file, defaults to ".exe" - /// Name of the new temporary file, null on error - internal static string MakeTempFile(byte[] fileContent, string extension = ".exe") - { - // Ensure the extension is set - if (string.IsNullOrWhiteSpace(extension)) - extension = ".exe"; - else if (!extension.StartsWith(".")) - extension = $".{extension}"; - - // Get the temporary path to use - string tempFileName = Guid.NewGuid().ToString(); - string tempPath = Path.Combine(Path.GetTempPath(), "tmp", $"{tempFileName}{extension}"); - - // Create and fill the file, if possible - try - { - Directory.CreateDirectory(Path.GetDirectoryName(tempPath)); - using (BinaryWriter bw = new BinaryWriter(File.OpenWrite(tempPath))) - { - bw.Write(fileContent); - } - - return Path.GetFullPath(tempPath); - } - catch { } - - return null; - } - - /// - /// Copies all required DLLs for a given executable - /// - /// Temporary file path - /// File contents to read - /// Paths for all of the copied DLLs, null on error - internal static string[] CopyDependentDlls(string file, byte[] fileContent) - { - if (fileContent == null) - return null; - - // Process each of the DLLs that it finds - try - { - unsafe - { - // Read all of the executable header information - PortableExecutable pex = PortableExecutable.Deserialize(fileContent, 0); - - // Find the import directory entry - DataDirectoryHeader idei = pex.OptionalHeader.DataDirectories[(byte)ImageDirectory.IMAGE_DIRECTORY_ENTRY_IMPORT]; - - // Set the table index and size - int tableIndex = (int)ConvertVirtualAddress(idei.VirtualAddress, pex.SectionTable); - int tableSize = (int)idei.Size; - if (tableIndex <= 0 || tableSize <= 0) - return null; - - // Load the table from index - ImportDataSection idata = ImportDataSection.Deserialize(fileContent, tableIndex, pex.OptionalHeader.Magic == OptionalHeaderType.PE32Plus, hintCount: 0); - ImportDirectoryTable idt = idata.ImportDirectoryTable; - - // TODO: Use the known layout to determine the names in a more automated way instead of having to iterate - - // Retrieve the table data - byte[] tableData = new byte[tableSize]; - Array.Copy(fileContent, tableIndex, tableData, 0, tableSize); - int entryCount = tableSize / 4; // Each entry is a UInt32 - - // TODO: Validate what this table actually looks like. - // My concern about this is that it seems like each entry might be 16 bytes? - // The original code does a += 12, reads the address, and then moves on. - // That being said, the way that this works _does_ come up with a valid table, - // at least something that looks like a valid table, since it shows up with - // `ntoskrnl.exe` on the dot - // - // Unfortunately, for other programs, this comes up with nonsense data, so it's hard - // to tell if the table is accurate or not. - - // Iterate through the table and get valid DLL names - List dependentDlls = new List(); - for (int i = 0; i < entryCount; i++) - { - // Get and validate the virtual offset - uint entryVirtualOffset = BitConverter.ToUInt32(tableData, i * 4); - if (entryVirtualOffset == 0) - continue; - - // Get the DLL name from the table and add it to the list if possible - string entryDllName = new string(fileContent.Skip((int)entryVirtualOffset).TakeWhile(b => b > 0).Select(b => (char)b).ToArray()); - - try - { - if (File.Exists(Path.Combine(Path.GetDirectoryName(file), entryDllName))) - { - FileInfo fiDLL = new FileInfo(Path.Combine(Path.GetDirectoryName(file), entryDllName)); - dependentDlls.Add(fiDLL.CopyTo(Path.Combine(Path.GetTempPath(), entryDllName), true).FullName); - } - } - catch { } - } - - return dependentDlls.ToArray(); - } - } - catch - { - return null; - } - } - - /// - /// Attempt to run an executable - /// - /// Executable to attempt to run - /// Process representing the running executable, null on error - internal static Process StartSafe(string file) - { - if (file == null || !File.Exists(file)) - return null; - - // Create the process to start safely - Process safeProcess = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = file, - WindowStyle = ProcessWindowStyle.Hidden, - CreateNoWindow = true, - ErrorDialog = false, - } - }; - - // Try to start the process, returning the handle if we can - try - { - safeProcess.Start(); - return safeProcess; - } - catch - { - return null; - } - } - } -} diff --git a/BurnOutSharp/Tools/Utilities.cs b/BurnOutSharp/Tools/Utilities.cs index 548e5f91..469d1acd 100644 --- a/BurnOutSharp/Tools/Utilities.cs +++ b/BurnOutSharp/Tools/Utilities.cs @@ -424,41 +424,5 @@ namespace BurnOutSharp.Tools } #endregion - - #region IO Helpers - - /// - /// Safely attempt to delete a path - /// - /// Path to be deleted - /// True to treat the path as a directory, false for a file - public static void SafeDelete(string path, bool isDirectory = false) - { - // No valid path means we can't delete - if (string.IsNullOrWhiteSpace(path)) - return; - - // Attempt to delete the path - try - { - if (!isDirectory && File.Exists(path)) - File.Delete(path); - else if (isDirectory && Directory.Exists(path)) - Directory.Delete(path, true); - } - catch - { - // Absorb any errors in deletion - } - } - - /// - /// Safely attempt to delete a path in the temp directory - /// - /// Path in the temp directory to be deleted - /// True to treat the path as a directory, false for a file - public static void SafeTempDelete(string path, bool isDirectory = false) => SafeDelete(Path.Combine(Path.GetTempPath(), path), isDirectory); - - #endregion } }