mirror of
https://github.com/ReneLergner/WPinternals.git
synced 2026-06-14 03:16:40 +10:00
598 lines
24 KiB
C#
598 lines
24 KiB
C#
// Copyright (c) 2018, Rene Lergner - wpinternals.net - @Heathcliff74xda
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
// copy of this software and associated documentation files (the "Software"),
|
|
// to deal in the Software without restriction, including without limitation
|
|
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
// and/or sell copies of the Software, and to permit persons to whom the
|
|
// Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
// DEALINGS IN THE SOFTWARE.
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Security.AccessControl;
|
|
using System.Security.Cryptography;
|
|
using System.Security.Principal;
|
|
using System.Xml;
|
|
using System.Xml.Serialization;
|
|
|
|
namespace WPinternals
|
|
{
|
|
internal class PatchEngine
|
|
{
|
|
internal List<PatchDefinition> PatchDefinitions = new List<PatchDefinition>();
|
|
internal readonly List<TargetRedirection> TargetRedirections = new List<TargetRedirection>();
|
|
|
|
internal PatchEngine() { }
|
|
|
|
internal PatchEngine(string PatchDefinitionsXmlString)
|
|
{
|
|
XmlSerializer x = new XmlSerializer(PatchDefinitions.GetType(), null, new Type[] { }, new XmlRootAttribute("PatchDefinitions"), "");
|
|
MemoryStream s = new MemoryStream(System.Text.Encoding.ASCII.GetBytes(PatchDefinitionsXmlString));
|
|
PatchDefinitions = (List<PatchDefinition>)x.Deserialize(s);
|
|
}
|
|
|
|
internal void WriteDefinitions(string FilePath)
|
|
{
|
|
XmlSerializer x = new XmlSerializer(PatchDefinitions.GetType(), null, new Type[] { }, new XmlRootAttribute("PatchDefinitions"), "");
|
|
|
|
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
|
|
ns.Add("", "");
|
|
|
|
System.IO.StreamWriter FileWriter = new System.IO.StreamWriter(FilePath);
|
|
XmlWriter XmlWriter = XmlWriter.Create(FileWriter, new XmlWriterSettings() { OmitXmlDeclaration = true, Indent = true, NewLineHandling = NewLineHandling.Entitize });
|
|
|
|
FileWriter.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
|
|
FileWriter.WriteLine("");
|
|
FileWriter.WriteLine("<!--");
|
|
FileWriter.WriteLine("Copyright(c) 2018, Rene Lergner - wpinternals.net - @Heathcliff74xda");
|
|
FileWriter.WriteLine("");
|
|
FileWriter.WriteLine("Permission is hereby granted, free of charge, to any person obtaining a");
|
|
FileWriter.WriteLine("copy of this software and associated documentation files(the \"Software\"),");
|
|
FileWriter.WriteLine("to deal in the Software without restriction, including without limitation");
|
|
FileWriter.WriteLine("the rights to use, copy, modify, merge, publish, distribute, sublicense,");
|
|
FileWriter.WriteLine("and / or sell copies of the Software, and to permit persons to whom the");
|
|
FileWriter.WriteLine("Software is furnished to do so, subject to the following conditions:");
|
|
FileWriter.WriteLine("");
|
|
FileWriter.WriteLine("The above copyright notice and this permission notice shall be included in");
|
|
FileWriter.WriteLine("all copies or substantial portions of the Software.");
|
|
FileWriter.WriteLine("");
|
|
FileWriter.WriteLine("THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR");
|
|
FileWriter.WriteLine("IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,");
|
|
FileWriter.WriteLine("FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE");
|
|
FileWriter.WriteLine("AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER");
|
|
FileWriter.WriteLine("LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING");
|
|
FileWriter.WriteLine("FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER");
|
|
FileWriter.WriteLine("DEALINGS IN THE SOFTWARE.");
|
|
FileWriter.WriteLine("-->");
|
|
FileWriter.WriteLine("");
|
|
|
|
x.Serialize(XmlWriter, PatchDefinitions, ns);
|
|
|
|
FileWriter.Close();
|
|
}
|
|
|
|
private string _TargetPath = null;
|
|
internal string TargetPath
|
|
{
|
|
get
|
|
{
|
|
return _TargetPath;
|
|
}
|
|
set
|
|
{
|
|
_TargetPath = value.TrimEnd(new char[] { '\\' });
|
|
}
|
|
}
|
|
|
|
|
|
private DiscUtils.DiscFileSystem _TargetImage = null;
|
|
internal DiscUtils.DiscFileSystem TargetImage
|
|
{
|
|
get
|
|
{
|
|
return _TargetImage;
|
|
}
|
|
set
|
|
{
|
|
_TargetImage = value;
|
|
_TargetPath = "";
|
|
}
|
|
}
|
|
|
|
internal bool Patch(string PatchDefinition)
|
|
{
|
|
bool Result = false;
|
|
List<FilePatcher> LoadedFiles = new List<FilePatcher>();
|
|
|
|
LogFile.Log("Attempt patch: " + PatchDefinition);
|
|
|
|
// Find a matching TargetVersion
|
|
PatchDefinition Definition = PatchDefinitions.Single(d => string.Compare(d.Name, PatchDefinition, true) == 0);
|
|
TargetVersion MatchedVersion = null;
|
|
int VersionIndex = 0;
|
|
foreach (TargetVersion CurrentVersion in Definition.TargetVersions)
|
|
{
|
|
bool Match = true;
|
|
int FileIndex = 0;
|
|
|
|
foreach (TargetFile CurrentTargetFile in CurrentVersion.TargetFiles)
|
|
{
|
|
// Determine target path
|
|
string TargetPath = null;
|
|
foreach (TargetRedirection CurrentRedirection in TargetRedirections)
|
|
{
|
|
if (CurrentTargetFile.Path.StartsWith(CurrentRedirection.RelativePath, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
TargetPath = Path.Combine(CurrentRedirection.TargetPath + "\\", CurrentTargetFile.Path);
|
|
break;
|
|
}
|
|
}
|
|
if (TargetPath == null)
|
|
TargetPath = Path.Combine(this.TargetPath + "\\", CurrentTargetFile.Path);
|
|
|
|
// Lookup file
|
|
FilePatcher CurrentFile = LoadedFiles.SingleOrDefault(f => string.Compare(f.FilePath, TargetPath, true) == 0);
|
|
if (CurrentFile == null)
|
|
{
|
|
if ((TargetImage != null) && (!TargetPath.Contains(':')))
|
|
CurrentFile = new FilePatcher(TargetPath, TargetImage.OpenFile(TargetPath, FileMode.Open, FileAccess.ReadWrite));
|
|
else
|
|
CurrentFile = new FilePatcher(TargetPath);
|
|
LoadedFiles.Add(CurrentFile);
|
|
}
|
|
|
|
// Compare hash
|
|
if (!StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentTargetFile.HashOriginal) &&
|
|
!StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentTargetFile.HashPatched))
|
|
{
|
|
Match = false;
|
|
|
|
foreach (TargetFile CurrentObsoleteFile in CurrentTargetFile.Obsolete)
|
|
{
|
|
if (StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentObsoleteFile.HashPatched))
|
|
{
|
|
Match = true; // Found match after all. File is patched with an obsolete version of this patch.
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!Match)
|
|
{
|
|
LogFile.Log("Pattern: " + VersionIndex.ToString() + ", " + FileIndex.ToString());
|
|
break;
|
|
}
|
|
}
|
|
|
|
FileIndex++;
|
|
}
|
|
|
|
if (Match)
|
|
{
|
|
MatchedVersion = CurrentVersion;
|
|
break;
|
|
}
|
|
|
|
VersionIndex++;
|
|
}
|
|
|
|
if (MatchedVersion != null)
|
|
{
|
|
LogFile.Log("Apply: " + MatchedVersion.Description);
|
|
|
|
foreach (TargetFile CurrentTargetFile in MatchedVersion.TargetFiles)
|
|
{
|
|
FilePatcher CurrentFile = LoadedFiles.SingleOrDefault(f => string.Compare(f.FilePath, Path.Combine(TargetPath + "\\", CurrentTargetFile.Path), true) == 0);
|
|
|
|
if (!StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentTargetFile.HashPatched))
|
|
{
|
|
CurrentFile.StartPatching();
|
|
|
|
if (!StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentTargetFile.HashOriginal))
|
|
{
|
|
// File is already patched, but with an older version of this patch.
|
|
// First unpatch back to original.
|
|
|
|
foreach (TargetFile CurrentObsoleteFile in CurrentTargetFile.Obsolete)
|
|
{
|
|
if (StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentObsoleteFile.HashPatched))
|
|
{
|
|
foreach (Patch CurrentPatch in CurrentObsoleteFile.Patches)
|
|
{
|
|
CurrentFile.ApplyPatch(CurrentPatch.Address, CurrentPatch.OriginalBytes);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (Patch CurrentPatch in CurrentTargetFile.Patches)
|
|
{
|
|
CurrentFile.ApplyPatch(CurrentPatch.Address, CurrentPatch.PatchedBytes);
|
|
}
|
|
|
|
CurrentFile.FinishPatching();
|
|
}
|
|
}
|
|
|
|
Result = true;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
internal void Restore(string PatchDefinition)
|
|
{
|
|
List<FilePatcher> LoadedFiles = new List<FilePatcher>();
|
|
|
|
try
|
|
{
|
|
// Find a matching TargetVersion
|
|
PatchDefinition Definition = PatchDefinitions.Single(d => string.Compare(d.Name, PatchDefinition, true) == 0);
|
|
TargetVersion MatchedVersion = null;
|
|
foreach (TargetVersion CurrentVersion in Definition.TargetVersions)
|
|
{
|
|
bool Match = true;
|
|
|
|
foreach (TargetFile CurrentTargetFile in CurrentVersion.TargetFiles)
|
|
{
|
|
// Determine target path
|
|
string TargetPath = null;
|
|
foreach (TargetRedirection CurrentRedirection in TargetRedirections)
|
|
{
|
|
if (CurrentTargetFile.Path.StartsWith(CurrentRedirection.RelativePath, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
TargetPath = Path.Combine(CurrentRedirection.TargetPath, CurrentTargetFile.Path);
|
|
break;
|
|
}
|
|
}
|
|
if (TargetPath == null)
|
|
TargetPath = Path.Combine(this.TargetPath, CurrentTargetFile.Path);
|
|
|
|
// Lookup file
|
|
FilePatcher CurrentFile = LoadedFiles.SingleOrDefault(f => string.Compare(f.FilePath, TargetPath, true) == 0);
|
|
if (CurrentFile == null)
|
|
{
|
|
CurrentFile = new FilePatcher(TargetPath);
|
|
LoadedFiles.Add(CurrentFile);
|
|
}
|
|
|
|
// Compare hash
|
|
if (!StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentTargetFile.HashOriginal) &&
|
|
!StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentTargetFile.HashPatched))
|
|
{
|
|
Match = false;
|
|
|
|
foreach (TargetFile CurrentObsoleteFile in CurrentTargetFile.Obsolete)
|
|
{
|
|
if (StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentObsoleteFile.HashPatched))
|
|
{
|
|
Match = true; // Found match after all. File is patched with an obsolete version of this patch.
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!Match)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Match)
|
|
{
|
|
MatchedVersion = CurrentVersion;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (MatchedVersion != null)
|
|
{
|
|
foreach (TargetFile CurrentTargetFile in MatchedVersion.TargetFiles)
|
|
{
|
|
FilePatcher CurrentFile = LoadedFiles.SingleOrDefault(f => string.Compare(f.FilePath, Path.Combine(TargetPath, CurrentTargetFile.Path), true) == 0);
|
|
|
|
if (!StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentTargetFile.HashOriginal))
|
|
{
|
|
CurrentFile.StartPatching();
|
|
|
|
if (StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentTargetFile.HashPatched))
|
|
{
|
|
foreach (Patch CurrentPatch in CurrentTargetFile.Patches)
|
|
{
|
|
CurrentFile.ApplyPatch(CurrentPatch.Address, CurrentPatch.OriginalBytes);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (TargetFile CurrentObsoleteFile in CurrentTargetFile.Obsolete)
|
|
{
|
|
if (StructuralComparisons.StructuralEqualityComparer.Equals(CurrentFile.Hash, CurrentObsoleteFile.HashPatched))
|
|
{
|
|
foreach (Patch CurrentPatch in CurrentObsoleteFile.Patches)
|
|
{
|
|
CurrentFile.ApplyPatch(CurrentPatch.Address, CurrentPatch.OriginalBytes);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
CurrentFile.FinishPatching();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception Ex)
|
|
{
|
|
LogFile.LogException(Ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal class TargetRedirection
|
|
{
|
|
private string _RelativePath;
|
|
private string _TargetPath;
|
|
|
|
internal TargetRedirection(string RelativePath, string TargetPath)
|
|
{
|
|
this.RelativePath = RelativePath;
|
|
this.TargetPath = TargetPath;
|
|
}
|
|
|
|
internal string RelativePath
|
|
{
|
|
get
|
|
{
|
|
return _RelativePath;
|
|
}
|
|
set
|
|
{
|
|
_RelativePath = value.TrimStart(new char[] { '\\' }).TrimEnd(new char[] { '\\' });
|
|
}
|
|
}
|
|
|
|
internal string TargetPath
|
|
{
|
|
get
|
|
{
|
|
return _TargetPath;
|
|
}
|
|
set
|
|
{
|
|
_TargetPath = value.TrimEnd(new char[] { '\\' });
|
|
}
|
|
}
|
|
}
|
|
|
|
internal class FilePatcher
|
|
{
|
|
internal byte[] Hash = null;
|
|
internal string FilePath;
|
|
internal FileSecurity OriginalACL;
|
|
internal Privilege TakeOwnershipPrivilege;
|
|
internal Privilege RestorePrivilege;
|
|
internal Stream Stream = null;
|
|
|
|
internal FilePatcher(string FilePath)
|
|
{
|
|
this.FilePath = FilePath;
|
|
using (FileStream stream = File.OpenRead(FilePath))
|
|
{
|
|
SHA1Managed sha = new SHA1Managed();
|
|
Hash = sha.ComputeHash(stream);
|
|
}
|
|
}
|
|
|
|
internal FilePatcher(string FilePath, Stream FileStream)
|
|
{
|
|
if (!FileStream.CanSeek || !FileStream.CanWrite)
|
|
throw new WPinternalsException("Incorrect filestream");
|
|
|
|
this.FilePath = FilePath;
|
|
this.Stream = FileStream;
|
|
FileStream.Position = 0;
|
|
SHA1Managed sha = new SHA1Managed();
|
|
Hash = sha.ComputeHash(FileStream);
|
|
FileStream.Position = 0;
|
|
}
|
|
|
|
internal void StartPatching()
|
|
{
|
|
if (FilePath.Contains(':'))
|
|
{
|
|
// Enable Take Ownership AND Restore ownership to original owner
|
|
// Take Ownership Privilge is not enough.
|
|
// We need Restore Privilege.
|
|
RestorePrivilege = new Privilege(Privilege.Restore);
|
|
RestorePrivilege.Enable();
|
|
|
|
if ((Environment.OSVersion.Version.Major == 6) && (Environment.OSVersion.Version.Minor <= 1))
|
|
{
|
|
// On Vista or 7
|
|
TakeOwnershipPrivilege = new Privilege(Privilege.TakeOwnership);
|
|
TakeOwnershipPrivilege.Enable();
|
|
}
|
|
|
|
// Backup original owner and ACL
|
|
OriginalACL = File.GetAccessControl(FilePath);
|
|
|
|
// And take the original security to create new security rules.
|
|
FileSecurity NewACL = File.GetAccessControl(FilePath);
|
|
|
|
// Take ownership
|
|
NewACL.SetOwner(WindowsIdentity.GetCurrent().User);
|
|
File.SetAccessControl(FilePath, NewACL);
|
|
|
|
// And create a new access rule
|
|
NewACL.SetAccessRule(new FileSystemAccessRule(WindowsIdentity.GetCurrent().User, FileSystemRights.FullControl, AccessControlType.Allow));
|
|
File.SetAccessControl(FilePath, NewACL);
|
|
|
|
// Open the file for patching
|
|
Stream = new FileStream(FilePath, FileMode.Open, FileAccess.ReadWrite);
|
|
}
|
|
}
|
|
|
|
internal void ApplyPatch(UInt32 Offset, byte[] Bytes)
|
|
{
|
|
Stream.Position = Offset;
|
|
Stream.Write(Bytes, 0, Bytes.Length);
|
|
}
|
|
|
|
internal void FinishPatching()
|
|
{
|
|
// Close file
|
|
Stream.Close();
|
|
|
|
if (FilePath.Contains(':'))
|
|
{
|
|
// Restore original owner and access rules.
|
|
// The OriginalACL cannot be reused directly.
|
|
FileSecurity NewACL = File.GetAccessControl(FilePath);
|
|
NewACL.SetSecurityDescriptorBinaryForm(OriginalACL.GetSecurityDescriptorBinaryForm());
|
|
File.SetAccessControl(FilePath, NewACL);
|
|
|
|
// Revert to self
|
|
RestorePrivilege.Revert();
|
|
RestorePrivilege.Disable();
|
|
|
|
if ((Environment.OSVersion.Version.Major == 6) && (Environment.OSVersion.Version.Minor <= 1))
|
|
{
|
|
// On Vista or 7
|
|
TakeOwnershipPrivilege.Revert();
|
|
TakeOwnershipPrivilege.Disable();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public class PatchDefinition // Must be public to be serializable
|
|
{
|
|
[XmlAttribute]
|
|
public string Name;
|
|
|
|
public List<TargetVersion> TargetVersions = new List<TargetVersion>();
|
|
}
|
|
|
|
public class TargetVersion // Must be public to be serializable
|
|
{
|
|
[XmlAttribute]
|
|
public string Description;
|
|
|
|
public List<TargetFile> TargetFiles = new List<TargetFile>();
|
|
}
|
|
|
|
public class TargetFile // Must be public to be serializable
|
|
{
|
|
private string _Path;
|
|
[XmlAttribute]
|
|
public string Path
|
|
{
|
|
get
|
|
{
|
|
return _Path;
|
|
}
|
|
set
|
|
{
|
|
_Path = value.TrimStart(new char[] { '\\' });
|
|
}
|
|
}
|
|
|
|
[XmlIgnore]
|
|
public byte[] HashOriginal;
|
|
[XmlAttribute("HashOriginal")]
|
|
public string HashOriginalAsString
|
|
{
|
|
get
|
|
{
|
|
return Converter.ConvertHexToString(HashOriginal, "");
|
|
}
|
|
set
|
|
{
|
|
HashOriginal = Converter.ConvertStringToHex(value);
|
|
}
|
|
}
|
|
|
|
[XmlIgnore]
|
|
public byte[] HashPatched;
|
|
[XmlAttribute("HashPatched")]
|
|
public string HashPatchedAsString
|
|
{
|
|
get
|
|
{
|
|
return Converter.ConvertHexToString(HashPatched, "");
|
|
}
|
|
set
|
|
{
|
|
HashPatched = Converter.ConvertStringToHex(value);
|
|
}
|
|
}
|
|
|
|
public List<Patch> Patches = new List<Patch>();
|
|
public List<TargetFile> Obsolete = new List<TargetFile>();
|
|
}
|
|
|
|
public class Patch // Must be public to be serializable
|
|
{
|
|
[XmlIgnore]
|
|
public UInt32 Address;
|
|
[XmlAttribute("Address")]
|
|
public string AddressAsString
|
|
{
|
|
get
|
|
{
|
|
return "0x" + Address.ToString("X8");
|
|
}
|
|
set
|
|
{
|
|
string NewValue = value;
|
|
if (NewValue.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
|
|
NewValue = NewValue.Substring(2);
|
|
Address = Convert.ToUInt32(NewValue, 16);
|
|
}
|
|
}
|
|
|
|
[XmlIgnore]
|
|
public byte[] OriginalBytes;
|
|
[XmlAttribute("OriginalBytes")]
|
|
public string OriginalBytesAsString
|
|
{
|
|
get
|
|
{
|
|
return Converter.ConvertHexToString(OriginalBytes, "");
|
|
}
|
|
set
|
|
{
|
|
OriginalBytes = Converter.ConvertStringToHex(value);
|
|
}
|
|
}
|
|
|
|
[XmlIgnore]
|
|
public byte[] PatchedBytes;
|
|
[XmlAttribute("PatchedBytes")]
|
|
public string PatchedBytesAsString
|
|
{
|
|
get
|
|
{
|
|
return Converter.ConvertHexToString(PatchedBytes, "");
|
|
}
|
|
set
|
|
{
|
|
PatchedBytes = Converter.ConvertStringToHex(value);
|
|
}
|
|
}
|
|
}
|
|
}
|