Combine VOB into ProtectDISC; add notes

This also means that EVORE is no longer relevant to the code and has been fully removed.
This commit is contained in:
Matt Nadareski
2021-09-07 23:53:05 -07:00
parent da01668cbe
commit f8f02a54f6
5 changed files with 123 additions and 504 deletions

View File

@@ -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;
}
/// <summary>
/// Convert a virtual address to a physical one
/// </summary>
/// <param name="virtualAddress">Virtual address to convert</param>
/// <param name="sections">Array of sections to check against</param>
/// <returns>Physical address, 0 on error</returns>
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;
}
}
}

View File

@@ -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
{
/// <inheritdoc/>
@@ -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<ContentMatchSet>
{
// 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<ContentMatchSet>
{
// 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<int> positions)
{
int position = positions[0] + 16; // Begin reading after "VOB ProtectCD"
char[] version = new ArraySegment<byte>(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<byte>(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<int> 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<int> 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}";
}
}
}

View File

@@ -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
{
/// <inheritdoc/>
public List<ContentMatchSet> GetContentMatchSets()
{
return new List<ContentMatchSet>
{
// 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"),
};
}
/// <inheritdoc/>
public string CheckContents(string file, byte[] fileContent, bool includeDebug = false) => null;
/// <inheritdoc/>
public ConcurrentQueue<string> CheckDirectoryPath(string path, IEnumerable<string> files)
{
var matchers = new List<PathMatchSet>
{
new PathMatchSet(new PathMatch("VOB-PCD.KEY", useEndsWith: true), "VOB ProtectCD/DVD"),
};
return MatchUtil.GetAllMatches(files, matchers, any: true);
}
/// <inheritdoc/>
public string CheckFilePath(string path)
{
var matchers = new List<PathMatchSet>
{
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<int> positions)
{
int position = positions[0]--; // TODO: Verify this subtract
char[] version = new ArraySegment<byte>(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<int> 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;
}
}
}

View File

@@ -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
{
/// <summary>
/// Convert a virtual address to a physical one
/// </summary>
/// <param name="virtualAddress">Virtual address to convert</param>
/// <param name="sections">Array of sections to check against</param>
/// <returns>Physical address, 0 on error</returns>
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;
}
/// <summary>
/// Checks if the file contents represent a PE executable
/// </summary>
/// <param name="fileContent">File contents to check</param>
/// <returns>True if the file is an EXE, false otherwise</returns>
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;
}
}
/// <summary>
/// Writes the file contents to a temporary file, if possible
/// </summary>
/// <param name="fileContent">File contents to write</param>
/// <param name="extension">Optional extension for the temporary file, defaults to ".exe"</param>
/// <returns>Name of the new temporary file, null on error</returns>
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;
}
/// <summary>
/// Copies all required DLLs for a given executable
/// </summary>
/// <param name="file">Temporary file path</param>
/// <param name="fileContent">File contents to read</param>
/// <returns>Paths for all of the copied DLLs, null on error</returns>
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<string> dependentDlls = new List<string>();
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;
}
}
/// <summary>
/// Attempt to run an executable
/// </summary>
/// <param name="file">Executable to attempt to run</param>
/// <returns>Process representing the running executable, null on error</returns>
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;
}
}
}
}

View File

@@ -424,41 +424,5 @@ namespace BurnOutSharp.Tools
}
#endregion
#region IO Helpers
/// <summary>
/// Safely attempt to delete a path
/// </summary>
/// <param name="path">Path to be deleted</param>
/// <param name="isDirectory">True to treat the path as a directory, false for a file</param>
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
}
}
/// <summary>
/// Safely attempt to delete a path in the temp directory
/// </summary>
/// <param name="path">Path in the temp directory to be deleted</param>
/// <param name="isDirectory">True to treat the path as a directory, false for a file</param>
public static void SafeTempDelete(string path, bool isDirectory = false) => SafeDelete(Path.Combine(Path.GetTempPath(), path), isDirectory);
#endregion
}
}