diff --git a/BurnOutSharp/EVORE.cs b/BurnOutSharp/EVORE.cs index d74420e9..e5eef670 100644 --- a/BurnOutSharp/EVORE.cs +++ b/BurnOutSharp/EVORE.cs @@ -17,8 +17,10 @@ //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 System.Runtime.InteropServices; using BurnOutSharp.ExecutableType.Microsoft; @@ -27,35 +29,48 @@ namespace BurnOutSharp internal static class EVORE { /// - /// Checks if the file contents that represent a PE is a DLL or an EXE + /// Checks if the file contents represent a PE executable /// /// File contents to check - /// True if the file is an EXE, false if it's a DLL - internal static bool IsEXE(byte[] fileContent) + /// True if the file is an EXE, false otherwise + internal static bool IsPEExecutable(byte[] fileContent) { - int PEHeaderOffset = BitConverter.ToInt32(fileContent, 60); - short Characteristics = BitConverter.ToInt16(fileContent, PEHeaderOffset + 22); + if (fileContent == null) + return false; - // Check if file is dll - return (Characteristics & 0x2000) != 0x2000; + try + { + IMAGE_DOS_HEADER idh = IMAGE_DOS_HEADER.Deserialize(fileContent, 0); + IMAGE_FILE_HEADER ifh = IMAGE_FILE_HEADER.Deserialize(fileContent, idh.NewExeHeaderAddr); + + // Check if file is dll + return (ifh.Characteristics & 0x2000) != 0x2000; + } + catch + { + return false; + } } /// /// Writes the file contents to a temporary file, if possible /// /// File contents to write - /// Optional extension for the temproary file, defaults to ".exe" + /// 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 sExtension = ".exe") + internal static string MakeTempFile(byte[] fileContent, string extension = ".exe") { - string filei = Guid.NewGuid().ToString(); - string tempPath = Path.Combine(Path.GetTempPath(), "tmp", $"{filei}{sExtension}"); - try - { - File.Delete(tempPath); - } - catch { } + // 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)); @@ -79,60 +94,73 @@ namespace BurnOutSharp /// Paths for all of the copied DLLs, null on error internal static string[] CopyDependentDlls(string file, byte[] fileContent) { - var sections = ReadSections(fileContent); + if (fileContent == null) + return null; - long lastPosition; - string[] saDependentDLLs = null; - int index = 60; - int PEHeaderOffset = BitConverter.ToInt32(fileContent, index); - index = PEHeaderOffset + 120 + 8; //120 Bytes till IMAGE_DATA_DIRECTORY array,8 Bytes=size of IMAGE_DATA_DIRECTORY - uint ImportTableRVA = BitConverter.ToUInt32(fileContent, index); - index += 4; - uint ImportTableSize = BitConverter.ToUInt32(fileContent, index); - index = (int)RVA2Offset(ImportTableRVA, sections); - index += 12; - uint DllNameRVA = BitConverter.ToUInt32(fileContent, index); - index += 4; - while (DllNameRVA != 0) + // Process each of the DLLs that it finds + try { - string sDllName = ""; - byte bChar; - lastPosition = index; - uint DLLNameOffset = RVA2Offset(DllNameRVA, sections); - if (DLLNameOffset > 0) + unsafe { - index = (int)DLLNameOffset; - if ((char)fileContent[index] > -1) + // Read all of the executable header information + IMAGE_DOS_HEADER idh = IMAGE_DOS_HEADER.Deserialize(fileContent, 0); + IMAGE_FILE_HEADER ifh = IMAGE_FILE_HEADER.Deserialize(fileContent, idh.NewExeHeaderAddr); + IMAGE_OPTIONAL_HEADER ioh = IMAGE_OPTIONAL_HEADER.Deserialize(fileContent, idh.NewExeHeaderAddr + Marshal.SizeOf(ifh)); + + // Find the import directory entry + IMAGE_DATA_DIRECTORY idei = ioh.DataDirectory[Constants.IMAGE_DIRECTORY_ENTRY_IMPORT]; + + // Set the table index and size + int tableIndex = (int)idei.VirtualAddress; + int tableSize = (int)idei.Size; + if (tableIndex <= 0 || tableSize <= 0) + return null; + + // 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++) { - do - { - bChar = fileContent[index]; - index++; - sDllName += (char)bChar; - } while (bChar != 0 && (char)fileContent[index] > -1); + // Get and validate the virtual offset + uint entryVirtualOffset = BitConverter.ToUInt32(tableData, i * 4); + if (entryVirtualOffset == 0) + continue; - sDllName = sDllName.Remove(sDllName.Length - 1, 1); - if (File.Exists(Path.Combine(Path.GetDirectoryName(file), sDllName))) - { - if (saDependentDLLs == null) - saDependentDLLs = new string[0]; - else - saDependentDLLs = new string[saDependentDLLs.Length]; + // 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()); - FileInfo fiDLL = new FileInfo(Path.Combine(Path.GetDirectoryName(file), sDllName)); - saDependentDLLs[saDependentDLLs.Length - 1] = fiDLL.CopyTo(Path.GetTempPath() + sDllName, true).FullName; + 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 { } } - index = (int)lastPosition; + return dependentDlls.ToArray(); } - - index += 4 + 12; - DllNameRVA = BitConverter.ToUInt32(fileContent, index); - index += 4; } - - return saDependentDLLs; + catch + { + return null; + } } /// @@ -140,45 +168,77 @@ namespace BurnOutSharp /// /// Executable to attempt to run /// Process representing the running executable, null on error - internal static Process StartSafe(string file) + public static Process StartSafe(string file) { if (file == null || !File.Exists(file)) return null; - Process startingprocess = new Process(); - startingprocess.StartInfo.FileName = file; - startingprocess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; - startingprocess.StartInfo.CreateNoWindow = true; - startingprocess.StartInfo.ErrorDialog = false; + // 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 { - startingprocess.Start(); + safeProcess.Start(); + return safeProcess; } catch { return null; } - - return startingprocess; } + /// + /// Read all section headers from a PE executable + /// + /// Byte array representing the executable + /// Pointer to the location in the array to read from + /// An array of section headers, null on error private static IMAGE_SECTION_HEADER[] ReadSections(byte[] fileContent) { if (fileContent == null) return null; - uint PEHeaderOffset = BitConverter.ToUInt32(fileContent, 60); - ushort NumberOfSections = BitConverter.ToUInt16(fileContent, (int)PEHeaderOffset + 6); - var sections = new IMAGE_SECTION_HEADER[NumberOfSections]; - int index = (int)PEHeaderOffset + 120 + 16 * 8; - for (int i = 0; i < NumberOfSections; i++) + try { - sections[i] = ReadSection(fileContent, index); - } + unsafe + { + IMAGE_DOS_HEADER idh = IMAGE_DOS_HEADER.Deserialize(fileContent, 0); + IMAGE_FILE_HEADER ifh = IMAGE_FILE_HEADER.Deserialize(fileContent, idh.NewExeHeaderAddr); - return sections; + var sections = new IMAGE_SECTION_HEADER[ifh.NumberOfSections]; + + int index = idh.NewExeHeaderAddr + Marshal.SizeOf(ifh) + ifh.SizeOfOptionalHeader; + for (int i = 0; i < ifh.NumberOfSections; i++) + { + sections[i] = ReadSection(fileContent, index); index += 40; + } + + return sections; + } + + } + catch + { + return null; + } } + /// + /// Read a single section header from a PE executable + /// + /// Byte array representing the executable + /// Pointer to the location in the array to read from + /// Section header, null on error private static IMAGE_SECTION_HEADER ReadSection(byte[] fileContent, int ptr) { try @@ -226,20 +286,5 @@ namespace BurnOutSharp return null; } } - - private static uint RVA2Offset(uint RVA, IMAGE_SECTION_HEADER[] sections) - { - for (int i = 0; i < sections.Length; i++) - { - if (sections[i] == null) - continue; - - var section = sections[i]; - if (section.VirtualAddress <= RVA && section.VirtualAddress + section.PhysicalAddress > RVA) - return RVA - section.VirtualAddress + section.PointerToRawData; - } - - return 0; - } } } diff --git a/BurnOutSharp/ExecutableType/Microsoft/IMAGE_DATA_DIRECTORY.cs b/BurnOutSharp/ExecutableType/Microsoft/IMAGE_DATA_DIRECTORY.cs index feb0a14b..634931ce 100644 --- a/BurnOutSharp/ExecutableType/Microsoft/IMAGE_DATA_DIRECTORY.cs +++ b/BurnOutSharp/ExecutableType/Microsoft/IMAGE_DATA_DIRECTORY.cs @@ -10,6 +10,7 @@ * http://csn.ul.ie/~caolan/pub/winresdump/winresdump/newexe.h */ +using System; using System.IO; using System.Runtime.InteropServices; @@ -30,5 +31,15 @@ namespace BurnOutSharp.ExecutableType.Microsoft return idd; } + + public static IMAGE_DATA_DIRECTORY Deserialize(byte[] content, int offset) + { + var idd = new IMAGE_DATA_DIRECTORY(); + + idd.VirtualAddress = BitConverter.ToUInt32(content, offset); offset += 4; + idd.Size = BitConverter.ToUInt32(content, offset); offset += 4; + + return idd; + } } } \ No newline at end of file diff --git a/BurnOutSharp/ExecutableType/Microsoft/IMAGE_DOS_HEADER.cs b/BurnOutSharp/ExecutableType/Microsoft/IMAGE_DOS_HEADER.cs index cfe4cde0..df7fdaa1 100644 --- a/BurnOutSharp/ExecutableType/Microsoft/IMAGE_DOS_HEADER.cs +++ b/BurnOutSharp/ExecutableType/Microsoft/IMAGE_DOS_HEADER.cs @@ -10,6 +10,7 @@ * http://csn.ul.ie/~caolan/pub/winresdump/winresdump/newexe.h */ +using System; using System.IO; using System.Runtime.InteropServices; @@ -77,5 +78,40 @@ namespace BurnOutSharp.ExecutableType.Microsoft return idh; } + + public static IMAGE_DOS_HEADER Deserialize(byte[] content, int offset) + { + IMAGE_DOS_HEADER idh = new IMAGE_DOS_HEADER(); + + idh.Magic = BitConverter.ToUInt16(content, offset); offset += 2; + idh.LastPageBytes = BitConverter.ToUInt16(content, offset); offset += 2; + idh.Pages = BitConverter.ToUInt16(content, offset); offset += 2; + idh.Relocations = BitConverter.ToUInt16(content, offset); offset += 2; + idh.HeaderParagraphSize = BitConverter.ToUInt16(content, offset); offset += 2; + idh.MinimumExtraParagraphs = BitConverter.ToUInt16(content, offset); offset += 2; + idh.MaximumExtraParagraphs = BitConverter.ToUInt16(content, offset); offset += 2; + idh.InitialSSValue = BitConverter.ToUInt16(content, offset); offset += 2; + idh.InitialSPValue = BitConverter.ToUInt16(content, offset); offset += 2; + idh.Checksum = BitConverter.ToUInt16(content, offset); offset += 2; + idh.InitialIPValue = BitConverter.ToUInt16(content, offset); offset += 2; + idh.InitialCSValue = BitConverter.ToUInt16(content, offset); offset += 2; + idh.RelocationTableAddr = BitConverter.ToUInt16(content, offset); offset += 2; + idh.OverlayNumber = BitConverter.ToUInt16(content, offset); offset += 2; + idh.Reserved1 = new ushort[Constants.ERES1WDS]; + for (int i = 0; i < Constants.ERES1WDS; i++) + { + idh.Reserved1[i] = BitConverter.ToUInt16(content, offset); offset += 2; + } + idh.OEMIdentifier = BitConverter.ToUInt16(content, offset); offset += 2; + idh.OEMInformation = BitConverter.ToUInt16(content, offset); offset += 2; + idh.Reserved2 = new ushort[Constants.ERES2WDS]; + for (int i = 0; i < Constants.ERES2WDS; i++) + { + idh.Reserved2[i] = BitConverter.ToUInt16(content, offset); offset += 2; + } + idh.NewExeHeaderAddr = BitConverter.ToInt32(content, offset); offset += 4; + + return idh; + } } } diff --git a/BurnOutSharp/ExecutableType/Microsoft/IMAGE_FILE_HEADER.cs b/BurnOutSharp/ExecutableType/Microsoft/IMAGE_FILE_HEADER.cs index 291e82b1..aad994b2 100644 --- a/BurnOutSharp/ExecutableType/Microsoft/IMAGE_FILE_HEADER.cs +++ b/BurnOutSharp/ExecutableType/Microsoft/IMAGE_FILE_HEADER.cs @@ -10,6 +10,7 @@ * http://csn.ul.ie/~caolan/pub/winresdump/winresdump/newexe.h */ +using System; using System.IO; using System.Runtime.InteropServices; @@ -18,6 +19,7 @@ namespace BurnOutSharp.ExecutableType.Microsoft [StructLayout(LayoutKind.Sequential)] internal class IMAGE_FILE_HEADER { + public uint Signature; public ushort Machine; public ushort NumberOfSections; public uint TimeDateStamp; @@ -30,6 +32,7 @@ namespace BurnOutSharp.ExecutableType.Microsoft { var ifh = new IMAGE_FILE_HEADER(); + ifh.Signature = stream.ReadUInt32(); ifh.Machine = stream.ReadUInt16(); ifh.NumberOfSections = stream.ReadUInt16(); ifh.TimeDateStamp = stream.ReadUInt32(); @@ -40,5 +43,21 @@ namespace BurnOutSharp.ExecutableType.Microsoft return ifh; } + + public static IMAGE_FILE_HEADER Deserialize(byte[] content, int offset) + { + var ifh = new IMAGE_FILE_HEADER(); + + ifh.Signature = BitConverter.ToUInt32(content, offset); offset += 4; + ifh.Machine = BitConverter.ToUInt16(content, offset); offset += 2; + ifh.NumberOfSections = BitConverter.ToUInt16(content, offset); offset += 2; + ifh.TimeDateStamp = BitConverter.ToUInt32(content, offset); offset += 4; + ifh.PointerToSymbolTable = BitConverter.ToUInt32(content, offset); offset += 4; + ifh.NumberOfSymbols = BitConverter.ToUInt32(content, offset); offset += 4; + ifh.SizeOfOptionalHeader = BitConverter.ToUInt16(content, offset); offset += 2; + ifh.Characteristics = BitConverter.ToUInt16(content, offset); offset += 2; + + return ifh; + } } } \ No newline at end of file diff --git a/BurnOutSharp/ExecutableType/Microsoft/IMAGE_OPTIONAL_HEADER.cs b/BurnOutSharp/ExecutableType/Microsoft/IMAGE_OPTIONAL_HEADER.cs index 1e5d4662..d74cfb35 100644 --- a/BurnOutSharp/ExecutableType/Microsoft/IMAGE_OPTIONAL_HEADER.cs +++ b/BurnOutSharp/ExecutableType/Microsoft/IMAGE_OPTIONAL_HEADER.cs @@ -10,6 +10,7 @@ * http://csn.ul.ie/~caolan/pub/winresdump/winresdump/newexe.h */ +using System; using System.IO; using System.Runtime.InteropServices; @@ -99,5 +100,49 @@ namespace BurnOutSharp.ExecutableType.Microsoft return ioh; } + + public static IMAGE_OPTIONAL_HEADER Deserialize(byte[] content, int offset) + { + var ioh = new IMAGE_OPTIONAL_HEADER(); + + ioh.Magic = BitConverter.ToUInt16(content, offset); offset += 2; + ioh.MajorLinkerVersion = content[offset]; offset++; + ioh.MinorLinkerVersion = content[offset]; offset++; + ioh.SizeOfCode = BitConverter.ToUInt32(content, offset); offset += 4; + ioh.SizeOfInitializedData = BitConverter.ToUInt32(content, offset); offset += 4; + ioh.SizeOfUninitializedData = BitConverter.ToUInt32(content, offset); offset += 4; + ioh.AddressOfEntryPoint = BitConverter.ToUInt32(content, offset); offset += 4; + ioh.BaseOfCode = BitConverter.ToUInt32(content, offset); offset += 4; + ioh.BaseOfData = BitConverter.ToUInt32(content, offset); offset += 4; + + ioh.ImageBase = BitConverter.ToUInt32(content, offset); offset += 4; + ioh.SectionAlignment = BitConverter.ToUInt32(content, offset); offset += 4; + ioh.FileAlignment = BitConverter.ToUInt32(content, offset); offset += 4; + ioh.MajorOperatingSystemVersion = BitConverter.ToUInt16(content, offset); offset += 2; + ioh.MinorOperatingSystemVersion = BitConverter.ToUInt16(content, offset); offset += 2; + ioh.MajorImageVersion = BitConverter.ToUInt16(content, offset); offset += 2; + ioh.MinorImageVersion = BitConverter.ToUInt16(content, offset); offset += 2; + ioh.MajorSubsystemVersion = BitConverter.ToUInt16(content, offset); offset += 2; + ioh.MinorSubsystemVersion = BitConverter.ToUInt16(content, offset); offset += 2; + ioh.Reserved1 = BitConverter.ToUInt32(content, offset); offset += 4; + ioh.SizeOfImage = BitConverter.ToUInt32(content, offset); offset += 4; + ioh.SizeOfHeaders = BitConverter.ToUInt32(content, offset); offset += 4; + ioh.CheckSum = BitConverter.ToUInt32(content, offset); offset += 4; + ioh.Subsystem = BitConverter.ToUInt16(content, offset); offset += 2; + ioh.DllCharacteristics = BitConverter.ToUInt16(content, offset); offset += 2; + ioh.SizeOfStackReserve = BitConverter.ToUInt32(content, offset); offset += 4; + ioh.SizeOfStackCommit = BitConverter.ToUInt32(content, offset); offset += 4; + ioh.SizeOfHeapReserve = BitConverter.ToUInt32(content, offset); offset += 4; + ioh.SizeOfHeapCommit = BitConverter.ToUInt32(content, offset); offset += 4; + ioh.LoaderFlags = BitConverter.ToUInt32(content, offset); offset += 4; + ioh.NumberOfRvaAndSizes = BitConverter.ToUInt32(content, offset); offset += 4; + ioh.DataDirectory = new IMAGE_DATA_DIRECTORY[Constants.IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; + for (int i = 0; i < Constants.IMAGE_NUMBEROF_DIRECTORY_ENTRIES; i++) + { + ioh.DataDirectory[i] = IMAGE_DATA_DIRECTORY.Deserialize(content, offset); offset += 8; + } + + return ioh; + } } } \ No newline at end of file diff --git a/BurnOutSharp/ProtectionType/ProtectDisc.cs b/BurnOutSharp/ProtectionType/ProtectDisc.cs index ab8ed2df..54565cfa 100644 --- a/BurnOutSharp/ProtectionType/ProtectDisc.cs +++ b/BurnOutSharp/ProtectionType/ProtectDisc.cs @@ -186,7 +186,7 @@ namespace BurnOutSharp.ProtectionType private static string SearchProtectDiscVersion(string file, byte[] fileContent) { // If the file isn't executable, don't even bother - if (!EVORE.IsEXE(fileContent)) + if (!EVORE.IsPEExecutable(fileContent)) return string.Empty; // Get some of the required paths diff --git a/BurnOutSharp/ProtectionType/SafeDisc.cs b/BurnOutSharp/ProtectionType/SafeDisc.cs index 79d4a9da..029f2920 100644 --- a/BurnOutSharp/ProtectionType/SafeDisc.cs +++ b/BurnOutSharp/ProtectionType/SafeDisc.cs @@ -224,7 +224,7 @@ namespace BurnOutSharp.ProtectionType private static string SearchSafeDiscVersion(string file, byte[] fileContent) { // If the file isn't executable, don't even bother - if (!EVORE.IsEXE(fileContent)) + if (!EVORE.IsPEExecutable(fileContent)) return string.Empty; // Get some of the required paths diff --git a/BurnOutSharp/ProtectionType/VOBProtectCDDVD.cs b/BurnOutSharp/ProtectionType/VOBProtectCDDVD.cs index dfc5d9a9..0fd81edd 100644 --- a/BurnOutSharp/ProtectionType/VOBProtectCDDVD.cs +++ b/BurnOutSharp/ProtectionType/VOBProtectCDDVD.cs @@ -110,7 +110,7 @@ namespace BurnOutSharp.ProtectionType private static string SearchProtectDiscVersion(string file, byte[] fileContent) { // If the file isn't executable, don't even bother - if (!EVORE.IsEXE(fileContent)) + if (!EVORE.IsPEExecutable(fileContent)) return string.Empty; // Get some of the required paths