From 8c88ba51c3c6577edead31a2d0b01ce18b87b6c4 Mon Sep 17 00:00:00 2001 From: atom0s Date: Mon, 23 Jan 2017 02:34:02 -0800 Subject: [PATCH] Steamless.Unpacker.Variant30.x86 - Fixed issue with header size not unpacking properly for certain files. - Fixed issue with TLS callbacks not being unpacked properly when present. Steamless.Unpacker.Variant30.x64 - Added support for 64bit version of SteamStub Variant 3.0. Steamless.Unpacker.Variant31.x64 - Added support for 64bit version of SteamStub Variant 3.1 --- .../Classes/SteamStubDrmFlags.cs | 39 ++ .../Classes/SteamStubHeader.cs | 81 +++ .../Classes/SteamStubHelpers.cs | 122 ++++ Steamless.Unpacker.Variant30.x64/Main.cs | 518 +++++++++++++++++ .../Properties/AssemblyInfo.cs | 40 ++ .../Steamless.Unpacker.Variant30.x64.csproj | 55 ++ Steamless.Unpacker.Variant30.x86/Main.cs | 107 +++- .../Classes/SteamStubDrmFlags.cs | 39 ++ .../Classes/SteamStubHeader.cs | 75 +++ .../Classes/SteamStubHelpers.cs | 122 ++++ Steamless.Unpacker.Variant31.x64/Main.cs | 531 ++++++++++++++++++ .../Properties/AssemblyInfo.cs | 40 ++ .../Steamless.Unpacker.Variant31.x64.csproj | 56 ++ .../Properties/AssemblyInfo.cs | 4 +- Steamless.sln | 12 + 15 files changed, 1811 insertions(+), 30 deletions(-) create mode 100644 Steamless.Unpacker.Variant30.x64/Classes/SteamStubDrmFlags.cs create mode 100644 Steamless.Unpacker.Variant30.x64/Classes/SteamStubHeader.cs create mode 100644 Steamless.Unpacker.Variant30.x64/Classes/SteamStubHelpers.cs create mode 100644 Steamless.Unpacker.Variant30.x64/Main.cs create mode 100644 Steamless.Unpacker.Variant30.x64/Properties/AssemblyInfo.cs create mode 100644 Steamless.Unpacker.Variant30.x64/Steamless.Unpacker.Variant30.x64.csproj create mode 100644 Steamless.Unpacker.Variant31.x64/Classes/SteamStubDrmFlags.cs create mode 100644 Steamless.Unpacker.Variant31.x64/Classes/SteamStubHeader.cs create mode 100644 Steamless.Unpacker.Variant31.x64/Classes/SteamStubHelpers.cs create mode 100644 Steamless.Unpacker.Variant31.x64/Main.cs create mode 100644 Steamless.Unpacker.Variant31.x64/Properties/AssemblyInfo.cs create mode 100644 Steamless.Unpacker.Variant31.x64/Steamless.Unpacker.Variant31.x64.csproj diff --git a/Steamless.Unpacker.Variant30.x64/Classes/SteamStubDrmFlags.cs b/Steamless.Unpacker.Variant30.x64/Classes/SteamStubDrmFlags.cs new file mode 100644 index 0000000..918a2c6 --- /dev/null +++ b/Steamless.Unpacker.Variant30.x64/Classes/SteamStubDrmFlags.cs @@ -0,0 +1,39 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.Unpacker.Variant30.x64.Classes +{ + /// + /// Steam Stub Variant 3.0 DRM Flags + /// + public enum SteamStubDrmFlags + { + NoModuleVerification = 0x02, + NoEncryption = 0x04, + NoOwnershipCheck = 0x10, + NoDebuggerCheck = 0x20, + NoErrorDialog = 0x40 + } +} \ No newline at end of file diff --git a/Steamless.Unpacker.Variant30.x64/Classes/SteamStubHeader.cs b/Steamless.Unpacker.Variant30.x64/Classes/SteamStubHeader.cs new file mode 100644 index 0000000..4c568c1 --- /dev/null +++ b/Steamless.Unpacker.Variant30.x64/Classes/SteamStubHeader.cs @@ -0,0 +1,81 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.Unpacker.Variant30.x64.Classes +{ + using System.Runtime.InteropServices; + + /// + /// SteamStub DRM Variant 3.0 x64 Header + /// + [StructLayout(LayoutKind.Sequential)] + public struct SteamStub64Var30Header + { + public uint XorKey; // The base XOR key, if defined, to unpack the file with. + public uint Signature; // 0xC0DEC0DE signature to validate this header is proper. + public ulong ImageBase; // The base of the image that is protected. + public uint AddressOfEntryPoint; // The entry point that is set from the DRM. + public uint BindSectionOffset; // The starting offset to the bind section data. RVA(AddressOfEntryPoint - BindSectionOffset) + public uint Unknown0000; // [Cyanic: This field is most likely the .bind code size.] + public uint OriginalEntryPoint; // The original entry point of the binary before it was protected. + public uint Unknown0001; // [Cyanic: This field is most likely an offset to a string table.] + public uint PayloadSize; // The size of the payload data. + public uint DRMPDllOffset; // The offset to the SteamDRMP.dll file. + public uint DRMPDllSize; // The size of the SteamDRMP.dll file. + public uint SteamAppId; // The Steam Application ID of this game. + public uint Flags; // The DRM flags used while creating the protected executable. + public uint BindSectionVirtualSize; // The bind section virtual size. + public uint Unknown0002; // [Cyanic: This field is most likely a hash of some sort.] + public uint CodeSectionVirtualAddress; // The cpde section virtual address. + public uint CodeSectionRawSize; // The raw size of the code section. + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)] + public byte[] AES_Key; // The AES encryption key. + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] + public byte[] AES_IV; // The AES encryption IV. + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] + public byte[] CodeSectionStolenData; // The first 16 bytes of the code section stolen. + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x04)] + public uint[] EncryptionKeys; // Encryption keys used for decrypting SteamDRMP.dll file. + + public uint Unknown0003; // [Cyanic: This field is most likely used to flag if the file has Tls data or not.] + public uint Unknown0004; + public uint Unknown0005; + public uint Unknown0006; + public uint Unknown0007; + public uint Unknown0008; + public uint GetModuleHandleA_RVA; // The RVA to GetModuleHandleA. + public uint GetModuleHandleW_RVA; // The RVA to GetModuleHandleW. + public uint LoadLibraryA_RVA; // The RVA to LoadLibraryA. + public uint LoadLibraryW_RVA; // The RVA to LoadLibraryW. + public uint GetProcAddress_RVA; // The RVA to GetProcAddress. + public uint Unknown0009; + public uint Unknown0010; + public uint Unknown0011; + } +} \ No newline at end of file diff --git a/Steamless.Unpacker.Variant30.x64/Classes/SteamStubHelpers.cs b/Steamless.Unpacker.Variant30.x64/Classes/SteamStubHelpers.cs new file mode 100644 index 0000000..b819d7f --- /dev/null +++ b/Steamless.Unpacker.Variant30.x64/Classes/SteamStubHelpers.cs @@ -0,0 +1,122 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.Unpacker.Variant30.x64.Classes +{ + using System; + + public static class SteamStubHelpers + { + /// + /// Xor decrypts the given data starting with the given key, if any. + /// + /// @note If no key is given (0) then the first key is read from the first + /// 4 bytes inside of the data given. + /// + /// The data to xor decode. + /// The size of the data to decode. + /// The starting xor key to decode with. + /// + public static uint SteamXor(ref byte[] data, uint size, uint key = 0) + { + var offset = (uint)0; + + // Read the first key as the base xor key if we had none given.. + if (key == 0) + { + offset += 4; + key = BitConverter.ToUInt32(data, 0); + } + + // Decode the data.. + for (var x = offset; x < size; x += 4) + { + var val = BitConverter.ToUInt32(data, (int)x); + Array.Copy(BitConverter.GetBytes(val ^ key), 0, data, x, 4); + + key = val; + } + + return key; + } + + /// + /// The second pass of decryption for the SteamDRMP.dll file. + /// + /// @note The encryption method here is known as XTEA. + /// + /// The result value buffer to write our returns to. + /// The keys used for the decryption. + /// The first value to decrypt from. + /// The second value to decrypt from. + /// The number of passes to crypt the data with. + public static void SteamDrmpDecryptPass2(ref uint[] res, uint[] keys, uint v1, uint v2, uint n = 32) + { + const uint delta = 0x9E3779B9; + const uint mask = 0xFFFFFFFF; + var sum = (delta * n) & mask; + + for (var x = 0; x < n; x++) + { + v2 = (v2 - (((v1 << 4 ^ v1 >> 5) + v1) ^ (sum + keys[sum >> 11 & 3]))) & mask; + sum = (sum - delta) & mask; + v1 = (v1 - (((v2 << 4 ^ v2 >> 5) + v2) ^ (sum + keys[sum & 3]))) & mask; + } + + res[0] = v1; + res[1] = v2; + } + + /// + /// The first pass of the decryption for the SteamDRMP.dll file. + /// + /// @note The encryption method here is known as XTEA. It is modded to include + /// some basic xor'ing. + /// + /// The data to decrypt. + /// The size of the data to decrypt. + /// The keys used for the decryption. + public static void SteamDrmpDecryptPass1(ref byte[] data, uint size, uint[] keys) + { + var v1 = (uint)0x55555555; + var v2 = (uint)0x55555555; + + for (var x = 0; x < size; x += 8) + { + var d1 = BitConverter.ToUInt32(data, x + 0); + var d2 = BitConverter.ToUInt32(data, x + 4); + + var res = new uint[2]; + SteamDrmpDecryptPass2(ref res, keys, d1, d2); + + Array.Copy(BitConverter.GetBytes(res[0] ^ v1), 0, data, x + 0, 4); + Array.Copy(BitConverter.GetBytes(res[1] ^ v2), 0, data, x + 4, 4); + + v1 = d1; + v2 = d2; + } + } + } +} \ No newline at end of file diff --git a/Steamless.Unpacker.Variant30.x64/Main.cs b/Steamless.Unpacker.Variant30.x64/Main.cs new file mode 100644 index 0000000..988b57b --- /dev/null +++ b/Steamless.Unpacker.Variant30.x64/Main.cs @@ -0,0 +1,518 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.Unpacker.Variant30.x64 +{ + using API; + using API.Crypto; + using API.Events; + using API.Extensions; + using API.Model; + using API.PE64; + using API.Services; + using Classes; + using System; + using System.IO; + using System.Security.Cryptography; + + [SteamlessApiVersion(1, 0)] + public class Main : SteamlessPlugin + { + /// + /// Internal logging service instance. + /// + private LoggingService m_LoggingService; + + /// + /// Gets the author of this plugin. + /// + public override string Author => "atom0s"; + + /// + /// Gets the name of this plugin. + /// + public override string Name => "SteamStub Variant 3.0 Unpacker (x64)"; + + /// + /// Gets the description of this plugin. + /// + public override string Description => "Unpacker for the 64bit SteamStub variant 3.0."; + + /// + /// Gets the version of this plugin. + /// + public override Version Version => new Version(1, 0, 0, 0); + + /// + /// Internal wrapper to log a message. + /// + /// + /// + private void Log(string msg, LogMessageType type) + { + this.m_LoggingService.OnAddLogMessage(this, new LogMessageEventArgs(msg, type)); + } + + /// + /// Initialize function called when this plugin is first loaded. + /// + /// + /// + public override bool Initialize(LoggingService logService) + { + this.m_LoggingService = logService; + return true; + } + + /// + /// Gets the SteamStub header size from the given file. + /// + /// + /// + private uint GetHeaderSize(Pe64File f) + { + // Obtain the bind section data.. + var bind = f.GetSectionData(".bind"); + + // Attempt to locate the known v3.x signature.. + var varient = Pe64Helpers.FindPattern(bind, "E8 00 00 00 00 50 53 51 52 56 57 55 41 50"); + if (varient == 0) return 0; + + // Attempt to determine the varient version.. + var offset = Pe64Helpers.FindPattern(bind, "48 8D 91 ?? ?? ?? ?? 48"); // 3.0 + if (offset == 0) + offset = Pe64Helpers.FindPattern(bind, "48 8D 91 ?? ?? ?? ?? 41"); // 3.1 + + // Ensure a pattern was found.. + if (offset == 0) return 0; + + // Read the header size.. (The header size is only 32bit!) + return (uint)Math.Abs(BitConverter.ToInt32(bind, (int)offset + 3)); + } + + /// + /// Processing function called when a file is being unpacked. Allows plugins to check the file + /// and see if it can handle the file for its intended purpose. + /// + /// + /// + public override bool CanProcessFile(string file) + { + try + { + // Load the file.. + var f = new Pe64File(file); + if (!f.Parse() || !f.IsFile64Bit() || !f.HasSection(".bind")) + return false; + + // Check for the known 3.0 header sizes.. + var headerSize = this.GetHeaderSize(f); + return headerSize == 0xB0 || headerSize == 0xD0; + } + catch + { + return false; + } + } + + /// + /// Processing function called to allow the plugin to process the file. + /// + /// + /// + /// + public override bool ProcessFile(string file, SteamlessOptions options) + { + // Initialize the class members.. + this.TlsAsOep = false; + this.TlsOepRva = 0; + this.Options = options; + this.CodeSectionData = null; + this.CodeSectionIndex = -1; + this.XorKey = 0; + + // Parse the file.. + this.File = new Pe64File(file); + if (!this.File.Parse()) + return false; + + // Announce we are being unpacked with this packer.. + this.Log("File is packed with SteamStub Variant 3.0!", LogMessageType.Information); + + this.Log("Step 1 - Read, decode and validate the SteamStub DRM header.", LogMessageType.Information); + if (!this.Step1()) + return false; + + this.Log("Step 2 - Read, decode and process the payload data.", LogMessageType.Information); + if (!this.Step2()) + return false; + + this.Log("Step 3 - Read, decode and dump the SteamDRMP.dll file.", LogMessageType.Information); + if (!this.Step3()) + return false; + + this.Log("Step 4 - Handle .bind section. Find code section.", LogMessageType.Information); + if (!this.Step4()) + return false; + + this.Log("Step 5 - Read, decrypt and process code section.", LogMessageType.Information); + if (!this.Step5()) + return false; + + this.Log("Step 6 - Rebuild and save the unpacked file.", LogMessageType.Information); + if (!this.Step6()) + return false; + + return true; + } + + /// + /// Step #1 + /// + /// Read, decode and validate the SteamStub DRM header. + /// + /// + private bool Step1() + { + // Obtain the header size.. + var headerSize = this.GetHeaderSize(this.File); + + // Obtain the DRM header data.. + var fileOffset = this.File.GetFileOffsetFromRva(this.File.NtHeaders.OptionalHeader.AddressOfEntryPoint); + var headerData = new byte[headerSize]; + Array.Copy(this.File.FileData, (long)(fileOffset - headerSize), headerData, 0, headerSize); + + // Xor decode the header data.. + this.XorKey = SteamStubHelpers.SteamXor(ref headerData, headerSize); + this.StubHeader = Pe64Helpers.GetStructure(headerData); + + // Validate the structure signature.. + return this.StubHeader.Signature == 0xC0DEC0DE; + } + + /// + /// Step #2 + /// + /// Read, decode and process the payload data. + /// + /// + private bool Step2() + { + // Obtain the payload address and size.. + var payloadAddr = this.File.GetFileOffsetFromRva(this.File.NtHeaders.OptionalHeader.AddressOfEntryPoint - this.StubHeader.BindSectionOffset); + var payloadSize = (this.StubHeader.PayloadSize + 0x0F) & 0xFFFFFFF0; + + // Do nothing if there is no payload.. + if (payloadSize == 0) + return true; + + this.Log(" --> File has payload data!", LogMessageType.Debug); + + // Obtain and decode the payload.. + var payload = new byte[payloadSize]; + Array.Copy(this.File.FileData, (long)payloadAddr, payload, 0, payloadSize); + this.XorKey = SteamStubHelpers.SteamXor(ref payload, payloadSize, this.XorKey); + + try + { + if (this.Options.DumpPayloadToDisk) + { + System.IO.File.WriteAllBytes(this.File.FilePath + ".payload", payload); + this.Log(" --> Saved payload to disk!", LogMessageType.Debug); + } + } + catch + { + // Do nothing here since it doesn't matter if this fails.. + } + + return true; + } + + /// + /// Step #3 + /// + /// Read, decode and dump the SteamDRMP.dll file. + /// + /// + private bool Step3() + { + // Ensure there is a dll to process.. + if (this.StubHeader.DRMPDllSize == 0) + { + this.Log(" --> File does not contain a SteamDRMP.dll file.", LogMessageType.Debug); + return true; + } + + this.Log(" --> File has SteamDRMP.dll file!", LogMessageType.Debug); + + try + { + // Obtain the SteamDRMP.dll file address and data.. + var drmpAddr = this.File.GetFileOffsetFromRva(this.File.NtHeaders.OptionalHeader.AddressOfEntryPoint - this.StubHeader.BindSectionOffset + this.StubHeader.DRMPDllOffset); + var drmpData = new byte[this.StubHeader.DRMPDllSize]; + Array.Copy(this.File.FileData, (long)drmpAddr, drmpData, 0, drmpData.Length); + + // Decrypt the data (xtea decryption).. + SteamStubHelpers.SteamDrmpDecryptPass1(ref drmpData, this.StubHeader.DRMPDllSize, this.StubHeader.EncryptionKeys); + + try + { + if (this.Options.DumpSteamDrmpToDisk) + { + var basePath = Path.GetDirectoryName(this.File.FilePath) ?? string.Empty; + System.IO.File.WriteAllBytes(Path.Combine(basePath, "SteamDRMP.dll"), drmpData); + this.Log(" --> Saved SteamDRMP.dll to disk!", LogMessageType.Debug); + } + } + catch + { + // Do nothing here since it doesn't matter if this fails.. + } + + return true; + } + catch + { + this.Log(" --> Error trying to decrypt the files SteamDRMP.dll data!", LogMessageType.Error); + return false; + } + } + + /// + /// Step #4 + /// + /// Remove the bind section if requested. + /// Find the code section. + /// + /// + private bool Step4() + { + // Remove the bind section if its not requested to be saved.. + if (!this.Options.KeepBindSection) + { + // Obtain the .bind section.. + var bindSection = this.File.GetSection(".bind"); + if (!bindSection.IsValid) + return false; + + // Remove the section.. + this.File.RemoveSection(bindSection); + + // Decrease the header section count.. + var ntHeaders = this.File.NtHeaders; + ntHeaders.FileHeader.NumberOfSections--; + this.File.NtHeaders = ntHeaders; + + this.Log(" --> .bind section was removed from the file.", LogMessageType.Debug); + } + else + this.Log(" --> .bind section was kept in the file.", LogMessageType.Debug); + + // Skip finding the code section if the file is not encrypted.. + if ((this.StubHeader.Flags & (uint)SteamStubDrmFlags.NoEncryption) == (uint)SteamStubDrmFlags.NoEncryption) + return true; + + // Find the code section.. + var codeSection = this.File.GetOwnerSection(this.StubHeader.CodeSectionVirtualAddress); + if (codeSection.PointerToRawData == 0 || codeSection.SizeOfRawData == 0) + return false; + + // Store the code sections index.. + this.CodeSectionIndex = this.File.GetSectionIndex(codeSection); + + return true; + } + + /// + /// Step #5 + /// + /// Read, decrypt and process the code section. + /// + /// + private bool Step5() + { + // Skip decryption if the code section is not encrypted.. + if ((this.StubHeader.Flags & (uint)SteamStubDrmFlags.NoEncryption) == (uint)SteamStubDrmFlags.NoEncryption) + { + this.Log(" --> Code section is not encrypted.", LogMessageType.Debug); + return true; + } + + try + { + // Obtain the code section.. + var codeSection = this.File.Sections[this.CodeSectionIndex]; + this.Log($" --> {codeSection.SectionName} linked as main code section.", LogMessageType.Debug); + this.Log($" --> {codeSection.SectionName} section is encrypted.", LogMessageType.Debug); + + // Obtain the code section data.. + var codeSectionData = new byte[codeSection.SizeOfRawData + this.StubHeader.CodeSectionStolenData.Length]; + Array.Copy(this.StubHeader.CodeSectionStolenData, (long)0, codeSectionData, 0, this.StubHeader.CodeSectionStolenData.Length); + Array.Copy(this.File.FileData, (long)this.File.GetFileOffsetFromRva(codeSection.VirtualAddress), codeSectionData, this.StubHeader.CodeSectionStolenData.Length, codeSection.SizeOfRawData); + + // Create the AES decryption helper.. + var aes = new AesHelper(this.StubHeader.AES_Key, this.StubHeader.AES_IV); + aes.RebuildIv(this.StubHeader.AES_IV); + + // Decrypt the code section data.. + var data = aes.Decrypt(codeSectionData, CipherMode.CBC, PaddingMode.None); + if (data == null) + return false; + + // Set the code section override data.. + this.CodeSectionData = data; + + return true; + } + catch + { + this.Log(" --> Error trying to decrypt the files code section data!", LogMessageType.Error); + return false; + } + } + + /// + /// Step #6 + /// + /// Rebuild and save the unpacked file. + /// + /// + private bool Step6() + { + FileStream fStream = null; + + try + { + // Rebuild the file sections.. + this.File.RebuildSections(); + + // Open the unpacked file for writing.. + var unpackedPath = this.File.FilePath + ".unpacked.exe"; + fStream = new FileStream(unpackedPath, FileMode.Create, FileAccess.ReadWrite); + + // Write the DOS header to the file.. + fStream.WriteBytes(Pe64Helpers.GetStructureBytes(this.File.DosHeader)); + + // Write the DOS stub to the file.. + if (this.File.DosStubSize > 0) + fStream.WriteBytes(this.File.DosStubData); + + // Update the entry point of the file.. + var ntHeaders = this.File.NtHeaders; + ntHeaders.OptionalHeader.AddressOfEntryPoint = this.StubHeader.OriginalEntryPoint; + this.File.NtHeaders = ntHeaders; + + // Write the NT headers to the file.. + fStream.WriteBytes(Pe64Helpers.GetStructureBytes(ntHeaders)); + + // Write the sections to the file.. + for (var x = 0; x < this.File.Sections.Count; x++) + { + var section = this.File.Sections[x]; + var sectionData = this.File.SectionData[x]; + + // Write the section header to the file.. + fStream.WriteBytes(Pe64Helpers.GetStructureBytes(section)); + + // Set the file pointer to the sections raw data.. + var sectionOffset = fStream.Position; + fStream.Position = section.PointerToRawData; + + // Write the sections raw data.. + var sectionIndex = this.File.Sections.IndexOf(section); + if (sectionIndex == this.CodeSectionIndex) + fStream.WriteBytes(this.CodeSectionData ?? sectionData); + else + fStream.WriteBytes(sectionData); + + // Reset the file offset.. + fStream.Position = sectionOffset; + } + + // Set the stream to the end of the file.. + fStream.Position = fStream.Length; + + // Write the overlay data if it exists.. + if (this.File.OverlayData != null) + fStream.WriteBytes(this.File.OverlayData); + + this.Log(" --> Unpacked file saved to disk!", LogMessageType.Success); + this.Log($" --> File Saved As: {unpackedPath}", LogMessageType.Success); + + return true; + } + catch + { + this.Log(" --> Error trying to save unpacked file!", LogMessageType.Error); + return false; + } + finally + { + fStream?.Dispose(); + } + } + + /// + /// Gets or sets if the Tls callback is being used as the Oep. + /// + private bool TlsAsOep { get; set; } + + /// + /// Gets or sets the Tls Oep Rva if it is being used as the Oep. + /// + private ulong TlsOepRva { get; set; } + + /// + /// Gets or sets the Steamless options this file was requested to process with. + /// + private SteamlessOptions Options { get; set; } + + /// + /// Gets or sets the file being processed. + /// + private Pe64File File { get; set; } + + /// + /// Gets or sets the current xor key being used against the file data. + /// + private uint XorKey { get; set; } + + /// + /// Gets or sets the DRM stub header. + /// + private SteamStub64Var30Header StubHeader { get; set; } + + /// + /// Gets or sets the index of the code section. + /// + private int CodeSectionIndex { get; set; } + + /// + /// Gets or sets the decrypted code section data. + /// + private byte[] CodeSectionData { get; set; } + } +} \ No newline at end of file diff --git a/Steamless.Unpacker.Variant30.x64/Properties/AssemblyInfo.cs b/Steamless.Unpacker.Variant30.x64/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..2c2983f --- /dev/null +++ b/Steamless.Unpacker.Variant30.x64/Properties/AssemblyInfo.cs @@ -0,0 +1,40 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Steamless.Unpacker.Variant30.x64")] +[assembly: AssemblyDescription("Steamless SteamStub Variant v3.0 (x64) Unpacker")] +[assembly: AssemblyConfiguration("Release")] +[assembly: AssemblyCompany("atom0s")] +[assembly: AssemblyProduct("Steamless.Unpacker.Variant30.x64")] +[assembly: AssemblyCopyright("Copyright © atom0s 2015 - 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("03621ead-77a7-4208-afdf-4b8292230a71")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/Steamless.Unpacker.Variant30.x64/Steamless.Unpacker.Variant30.x64.csproj b/Steamless.Unpacker.Variant30.x64/Steamless.Unpacker.Variant30.x64.csproj new file mode 100644 index 0000000..989e10d --- /dev/null +++ b/Steamless.Unpacker.Variant30.x64/Steamless.Unpacker.Variant30.x64.csproj @@ -0,0 +1,55 @@ + + + + + Debug + AnyCPU + {03621EAD-77A7-4208-AFDF-4B8292230A71} + Library + Properties + Steamless.Unpacker.Variant30.x64 + Steamless.Unpacker.Variant30.x64 + v4.5.2 + 512 + + + x86 + ..\Steamless\bin\x86\Debug\Plugins\ + + + x86 + ..\Steamless\bin\x86\Release\Plugins\ + true + + + + + + + + + + + + + + + + + + + + + {56c95629-3b34-47fe-b988-04274409294f} + Steamless.API + + + + + \ No newline at end of file diff --git a/Steamless.Unpacker.Variant30.x86/Main.cs b/Steamless.Unpacker.Variant30.x86/Main.cs index c556143..27955c0 100644 --- a/Steamless.Unpacker.Variant30.x86/Main.cs +++ b/Steamless.Unpacker.Variant30.x86/Main.cs @@ -63,7 +63,7 @@ namespace Steamless.Unpacker.Variant30.x86 /// /// Gets the version of this plugin. /// - public override Version Version => new Version(1, 0, 0, 0); + public override Version Version => new Version(1, 0, 0, 1); /// /// Internal wrapper to log a message. @@ -86,6 +86,37 @@ namespace Steamless.Unpacker.Variant30.x86 return true; } + /// + /// Gets the SteamStub header size from the given file. + /// + /// + /// + private uint GetHeaderSize(Pe32File f) + { + // Obtain the bind section data.. + var bind = f.GetSectionData(".bind"); + + // Attempt to locate the known v3.x signature.. + var varient = Pe32Helpers.FindPattern(bind, "E8 00 00 00 00 50 53 51 52 56 57 55 8B 44 24 1C 2D 05 00 00 00 8B CC 83 E4 F0 51 51 51 50"); + if (varient == 0) return 0; + + // Attempt to determine the varient version.. + uint headerSize; + var offset = Pe32Helpers.FindPattern(bind, "55 8B EC 81 EC ?? ?? ?? ?? 53 ?? ?? ?? ?? ?? 68"); + if (offset == 0) + { + offset = Pe32Helpers.FindPattern(bind, "55 8B EC 81 EC ?? ?? ?? ?? 53 ?? ?? ?? ?? ?? 8D 83"); + if (offset == 0) + return 0; + + headerSize = (uint)BitConverter.ToInt32(bind, (int)offset + 22); + } + else + headerSize = (uint)BitConverter.ToInt32(bind, (int)offset + 16); + + return headerSize; + } + /// /// Processing function called when a file is being unpacked. Allows plugins to check the file /// and see if it can handle the file for its intended purpose. @@ -101,27 +132,8 @@ namespace Steamless.Unpacker.Variant30.x86 if (!f.Parse() || f.IsFile64Bit() || !f.HasSection(".bind")) return false; - // Obtain the bind section data.. - var bind = f.GetSectionData(".bind"); - - // Attempt to locate the known v3.x signature.. - var varient = Pe32Helpers.FindPattern(bind, "E8 00 00 00 00 50 53 51 52 56 57 55 8B 44 24 1C 2D 05 00 00 00 8B CC 83 E4 F0 51 51 51 50"); - if (varient == 0) return false; - - // Attempt to determine the varient version.. - int headerSize; - var offset = Pe32Helpers.FindPattern(bind, "55 8B EC 81 EC ?? ?? ?? ?? 53 ?? ?? ?? ?? ?? 68"); - if (offset == 0) - { - offset = Pe32Helpers.FindPattern(bind, "55 8B EC 81 EC ?? ?? ?? ?? 53 ?? ?? ?? ?? ?? 8D 83"); - if (offset == 0) - return false; - - headerSize = BitConverter.ToInt32(bind, (int)offset + 22); - } - else - headerSize = BitConverter.ToInt32(bind, (int)offset + 16); - + // Check for the known 3.0 header sizes.. + var headerSize = this.GetHeaderSize(f); return headerSize == 0xB0 || headerSize == 0xD0; } catch @@ -139,6 +151,8 @@ namespace Steamless.Unpacker.Variant30.x86 public override bool ProcessFile(string file, SteamlessOptions options) { // Initialize the class members.. + this.TlsAsOep = false; + this.TlsOepRva = 0; this.Options = options; this.CodeSectionData = null; this.CodeSectionIndex = -1; @@ -187,17 +201,44 @@ namespace Steamless.Unpacker.Variant30.x86 /// private bool Step1() { + // Obtain the header size.. + var headerSize = this.GetHeaderSize(this.File); + // Obtain the DRM header data.. var fileOffset = this.File.GetFileOffsetFromRva(this.File.NtHeaders.OptionalHeader.AddressOfEntryPoint); - var headerData = new byte[0xD0]; - Array.Copy(this.File.FileData, (int)(fileOffset - 0xD0), headerData, 0, 0xD0); + var headerData = new byte[headerSize]; + Array.Copy(this.File.FileData, (int)(fileOffset - headerSize), headerData, 0, headerSize); // Xor decode the header data.. - this.XorKey = SteamStubHelpers.SteamXor(ref headerData, 0xD0); + this.XorKey = SteamStubHelpers.SteamXor(ref headerData, headerSize); this.StubHeader = Pe32Helpers.GetStructure(headerData); // Validate the structure signature.. - return this.StubHeader.Signature == 0xC0DEC0DE; + if (this.StubHeader.Signature == 0xC0DEC0DE) + return true; + + // Try again using the Tls callback (if any) as the OEP instead.. + if (this.File.TlsCallbacks.Count == 0) + return false; + + // Obtain the DRM header data.. + fileOffset = this.File.GetRvaFromVa(this.File.TlsCallbacks[0]); + fileOffset = this.File.GetFileOffsetFromRva(fileOffset); + headerData = new byte[headerSize]; + Array.Copy(this.File.FileData, (int)(fileOffset - headerSize), headerData, 0, headerSize); + + // Xor decode the header data.. + this.XorKey = SteamStubHelpers.SteamXor(ref headerData, headerSize); + this.StubHeader = Pe32Helpers.GetStructure(headerData); + + // Validate the structure signature.. + if (this.StubHeader.Signature == 0xC0DEC0DE) + return true; + + // Tls was valid for the real oep.. + this.TlsAsOep = true; + this.TlsOepRva = fileOffset; + return true; } /// @@ -209,7 +250,7 @@ namespace Steamless.Unpacker.Variant30.x86 private bool Step2() { // Obtain the payload address and size.. - var payloadAddr = this.File.GetFileOffsetFromRva(this.File.NtHeaders.OptionalHeader.AddressOfEntryPoint - this.StubHeader.BindSectionOffset); + var payloadAddr = this.File.GetFileOffsetFromRva(this.TlsAsOep ? this.TlsOepRva : this.File.NtHeaders.OptionalHeader.AddressOfEntryPoint - this.StubHeader.BindSectionOffset); var payloadSize = (this.StubHeader.PayloadSize + 0x0F) & 0xFFFFFFF0; // Do nothing if there is no payload.. @@ -259,7 +300,7 @@ namespace Steamless.Unpacker.Variant30.x86 try { // Obtain the SteamDRMP.dll file address and data.. - var drmpAddr = this.File.GetFileOffsetFromRva(this.File.NtHeaders.OptionalHeader.AddressOfEntryPoint - this.StubHeader.BindSectionOffset + this.StubHeader.DRMPDllOffset); + var drmpAddr = this.File.GetFileOffsetFromRva(this.TlsAsOep ? this.TlsOepRva : this.File.NtHeaders.OptionalHeader.AddressOfEntryPoint - this.StubHeader.BindSectionOffset + this.StubHeader.DRMPDllOffset); var drmpData = new byte[this.StubHeader.DRMPDllSize]; Array.Copy(this.File.FileData, drmpAddr, drmpData, 0, drmpData.Length); @@ -463,6 +504,16 @@ namespace Steamless.Unpacker.Variant30.x86 } } + /// + /// Gets or sets if the Tls callback is being used as the Oep. + /// + private bool TlsAsOep { get; set; } + + /// + /// Gets or sets the Tls Oep Rva if it is being used as the Oep. + /// + private uint TlsOepRva { get; set; } + /// /// Gets or sets the Steamless options this file was requested to process with. /// diff --git a/Steamless.Unpacker.Variant31.x64/Classes/SteamStubDrmFlags.cs b/Steamless.Unpacker.Variant31.x64/Classes/SteamStubDrmFlags.cs new file mode 100644 index 0000000..47fa7c3 --- /dev/null +++ b/Steamless.Unpacker.Variant31.x64/Classes/SteamStubDrmFlags.cs @@ -0,0 +1,39 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.Unpacker.Variant31.x64.Classes +{ + /// + /// Steam Stub Variant 3.1 DRM Flags + /// + public enum SteamStubDrmFlags + { + NoModuleVerification = 0x02, + NoEncryption = 0x04, + NoOwnershipCheck = 0x10, + NoDebuggerCheck = 0x20, + NoErrorDialog = 0x40 + } +} \ No newline at end of file diff --git a/Steamless.Unpacker.Variant31.x64/Classes/SteamStubHeader.cs b/Steamless.Unpacker.Variant31.x64/Classes/SteamStubHeader.cs new file mode 100644 index 0000000..59d9a33 --- /dev/null +++ b/Steamless.Unpacker.Variant31.x64/Classes/SteamStubHeader.cs @@ -0,0 +1,75 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.Unpacker.Variant31.x64.Classes +{ + using System.Runtime.InteropServices; + + /// + /// SteamStub DRM Variant 3.1 x64 Header + /// + [StructLayout(LayoutKind.Sequential)] + public struct SteamStub64Var31Header + { + public uint XorKey; // The base xor key, if defined, to unpack the file with. + public uint Signature; // The signature to ensure the xor decoding was successful. + public ulong ImageBase; // The base of the image that was protected. + public ulong AddressOfEntryPoint; // The entry point that is set from the DRM. + public uint BindSectionOffset; // The starting offset to the .bind section data. RVA(AddressOfEntryPoint - BindSectionOffset) + public uint Unknown0000; // [Cyanic: This field is most likely the .bind code size.] + public ulong OriginalEntryPoint; // The original entry point of the binary before it was protected. + public uint Unknown0001; // [Cyanic: This field is most likely an offset to a string table.] + public uint PayloadSize; // The size of the payload data. + public uint DRMPDllOffset; // The offset to the SteamDrmp.dll file. + public uint DRMPDllSize; // The size of the SteamDrmp.dll file. + public uint SteamAppId; // The Steam application id of this program. + public uint Flags; // The DRM flags used while protecting this program. + public uint BindSectionVirtualSize; // The .bind section virtual size. + public uint Unknown0002; // [Cyanic: This field is most likely a hash of some sort.] + public ulong CodeSectionVirtualAddress; // The code section virtual address. + public ulong CodeSectionRawSize; // The code section raw size. + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)] + public byte[] AES_Key; // The AES encryption key. + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] + public byte[] AES_IV; // The AES encryption IV. + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] + public byte[] CodeSectionStolenData; // The first 16 bytes of the code section stolen. + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x04)] + public uint[] EncryptionKeys; // Encryption keys used to decrypt the SteamDrmp.dll file. + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x08)] + public uint[] Unknown0003; // Unknown unused data. + + public ulong GetModuleHandleA_Rva; // The rva to GetModuleHandleA. + public ulong GetModuleHandleW_Rva; // The rva to GetModuleHandleW. + public ulong LoadLibraryA_Rva; // The rva to LoadLibraryA. + public ulong LoadLibraryW_Rva; // The rva to LoadLibraryW. + public ulong GetProcAddress_Rva; // The rva to GetProcAddress. + } +} \ No newline at end of file diff --git a/Steamless.Unpacker.Variant31.x64/Classes/SteamStubHelpers.cs b/Steamless.Unpacker.Variant31.x64/Classes/SteamStubHelpers.cs new file mode 100644 index 0000000..edea8ce --- /dev/null +++ b/Steamless.Unpacker.Variant31.x64/Classes/SteamStubHelpers.cs @@ -0,0 +1,122 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.Unpacker.Variant31.x64.Classes +{ + using System; + + public static class SteamStubHelpers + { + /// + /// Xor decrypts the given data starting with the given key, if any. + /// + /// @note If no key is given (0) then the first key is read from the first + /// 4 bytes inside of the data given. + /// + /// The data to xor decode. + /// The size of the data to decode. + /// The starting xor key to decode with. + /// + public static uint SteamXor(ref byte[] data, uint size, uint key = 0) + { + var offset = (uint)0; + + // Read the first key as the base xor key if we had none given.. + if (key == 0) + { + offset += 4; + key = BitConverter.ToUInt32(data, 0); + } + + // Decode the data.. + for (var x = offset; x < size; x += 4) + { + var val = BitConverter.ToUInt32(data, (int)x); + Array.Copy(BitConverter.GetBytes(val ^ key), 0, data, x, 4); + + key = val; + } + + return key; + } + + /// + /// The second pass of decryption for the SteamDRMP.dll file. + /// + /// @note The encryption method here is known as XTEA. + /// + /// The result value buffer to write our returns to. + /// The keys used for the decryption. + /// The first value to decrypt from. + /// The second value to decrypt from. + /// The number of passes to crypt the data with. + public static void SteamDrmpDecryptPass2(ref uint[] res, uint[] keys, uint v1, uint v2, uint n = 32) + { + const uint delta = 0x9E3779B9; + const uint mask = 0xFFFFFFFF; + var sum = (delta * n) & mask; + + for (var x = 0; x < n; x++) + { + v2 = (v2 - (((v1 << 4 ^ v1 >> 5) + v1) ^ (sum + keys[sum >> 11 & 3]))) & mask; + sum = (sum - delta) & mask; + v1 = (v1 - (((v2 << 4 ^ v2 >> 5) + v2) ^ (sum + keys[sum & 3]))) & mask; + } + + res[0] = v1; + res[1] = v2; + } + + /// + /// The first pass of the decryption for the SteamDRMP.dll file. + /// + /// @note The encryption method here is known as XTEA. It is modded to include + /// some basic xor'ing. + /// + /// The data to decrypt. + /// The size of the data to decrypt. + /// The keys used for the decryption. + public static void SteamDrmpDecryptPass1(ref byte[] data, uint size, uint[] keys) + { + var v1 = (uint)0x55555555; + var v2 = (uint)0x55555555; + + for (var x = 0; x < size; x += 8) + { + var d1 = BitConverter.ToUInt32(data, x + 0); + var d2 = BitConverter.ToUInt32(data, x + 4); + + var res = new uint[2]; + SteamDrmpDecryptPass2(ref res, keys, d1, d2); + + Array.Copy(BitConverter.GetBytes(res[0] ^ v1), 0, data, x + 0, 4); + Array.Copy(BitConverter.GetBytes(res[1] ^ v2), 0, data, x + 4, 4); + + v1 = d1; + v2 = d2; + } + } + } +} \ No newline at end of file diff --git a/Steamless.Unpacker.Variant31.x64/Main.cs b/Steamless.Unpacker.Variant31.x64/Main.cs new file mode 100644 index 0000000..6ea4ae9 --- /dev/null +++ b/Steamless.Unpacker.Variant31.x64/Main.cs @@ -0,0 +1,531 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +namespace Steamless.Unpacker.Variant31.x64 +{ + using API; + using API.Crypto; + using API.Events; + using API.Extensions; + using API.Model; + using API.PE64; + using API.Services; + using Classes; + using System; + using System.IO; + using System.Security.Cryptography; + + [SteamlessApiVersion(1, 0)] + public class Main : SteamlessPlugin + { + /// + /// Internal logging service instance. + /// + private LoggingService m_LoggingService; + + /// + /// Gets the author of this plugin. + /// + public override string Author => "atom0s"; + + /// + /// Gets the name of this plugin. + /// + public override string Name => "SteamStub Variant 3.1 Unpacker (x64)"; + + /// + /// Gets the description of this plugin. + /// + public override string Description => "Unpacker for the 64bit SteamStub variant 3.1."; + + /// + /// Gets the version of this plugin. + /// + public override Version Version => new Version(1, 0, 0, 0); + + /// + /// Internal wrapper to log a message. + /// + /// + /// + private void Log(string msg, LogMessageType type) + { + this.m_LoggingService.OnAddLogMessage(this, new LogMessageEventArgs(msg, type)); + } + + /// + /// Initialize function called when this plugin is first loaded. + /// + /// + /// + public override bool Initialize(LoggingService logService) + { + this.m_LoggingService = logService; + return true; + } + + /// + /// Processing function called when a file is being unpacked. Allows plugins to check the file + /// and see if it can handle the file for its intended purpose. + /// + /// + /// + public override bool CanProcessFile(string file) + { + try + { + // Load the file.. + var f = new Pe64File(file); + if (!f.Parse() || !f.IsFile64Bit() || !f.HasSection(".bind")) + return false; + + // Obtain the bind section data.. + var bind = f.GetSectionData(".bind"); + + // Attempt to locate the known v3.x signature.. + var varient = Pe64Helpers.FindPattern(bind, "E8 00 00 00 00 50 53 51 52 56 57 55 41 50"); + if (varient == 0) return false; + + // Attempt to determine the varient version.. + var offset = Pe64Helpers.FindPattern(bind, "48 8D 91 ?? ?? ?? ?? 48"); // 3.0 + if (offset == 0) + offset = Pe64Helpers.FindPattern(bind, "48 8D 91 ?? ?? ?? ?? 41"); // 3.1 + + // Ensure a pattern was found.. + if (offset == 0) + return false; + + // Read the header size.. (The header size is only 32bit!) + var headerSize = Math.Abs(BitConverter.ToInt32(bind, (int)offset + 3)); + + // Check for the known 3.1 header size.. + return headerSize == 0xF0; + } + catch + { + return false; + } + } + + /// + /// Processing function called to allow the plugin to process the file. + /// + /// + /// + /// + public override bool ProcessFile(string file, SteamlessOptions options) + { + // Initialize the class members.. + this.TlsAsOep = false; + this.TlsOepRva = 0; + this.Options = options; + this.CodeSectionData = null; + this.CodeSectionIndex = -1; + this.XorKey = 0; + + // Parse the file.. + this.File = new Pe64File(file); + if (!this.File.Parse()) + return false; + + // Announce we are being unpacked with this packer.. + this.Log("File is packed with SteamStub Variant 3.1 (x64)!", LogMessageType.Information); + + this.Log("Step 1 - Read, decode and validate the SteamStub DRM header.", LogMessageType.Information); + if (!this.Step1()) + return false; + + this.Log("Step 2 - Read, decode and process the payload data.", LogMessageType.Information); + if (!this.Step2()) + return false; + + this.Log("Step 3 - Read, decode and dump the SteamDRMP.dll file.", LogMessageType.Information); + if (!this.Step3()) + return false; + + this.Log("Step 4 - Handle .bind section. Find code section.", LogMessageType.Information); + if (!this.Step4()) + return false; + + this.Log("Step 5 - Read, decrypt and process code section.", LogMessageType.Information); + if (!this.Step5()) + return false; + + this.Log("Step 6 - Rebuild and save the unpacked file.", LogMessageType.Information); + if (!this.Step6()) + return false; + + return true; + } + + /// + /// Step #1 + /// + /// Read, decode and validate the SteamStub DRM header. + /// + /// + private bool Step1() + { + // Obtain the DRM header data.. + var fileOffset = this.File.GetFileOffsetFromRva(this.File.NtHeaders.OptionalHeader.AddressOfEntryPoint); + var headerData = new byte[0xF0]; + Array.Copy(this.File.FileData, (long)(fileOffset - 0xF0), headerData, 0, 0xF0); + + // Xor decode the header data.. + this.XorKey = SteamStubHelpers.SteamXor(ref headerData, 0xF0); + this.StubHeader = Pe64Helpers.GetStructure(headerData); + + // Validate the header signature.. + if (this.StubHeader.Signature == 0xC0DEC0DF) + return true; + + // Try again using the Tls callback (if any) as the OEP instead.. + if (this.File.TlsCallbacks.Count == 0) + return false; + + // Obtain the DRM header data.. + fileOffset = this.File.GetRvaFromVa(this.File.TlsCallbacks[0]); + fileOffset = this.File.GetFileOffsetFromRva(fileOffset); + headerData = new byte[0xF0]; + Array.Copy(this.File.FileData, (long)(fileOffset - 0xF0), headerData, 0, 0xF0); + + // Xor decode the header data.. + this.XorKey = SteamStubHelpers.SteamXor(ref headerData, 0xF0); + this.StubHeader = Pe64Helpers.GetStructure(headerData); + + // Validate the header signature.. + if (this.StubHeader.Signature != 0xC0DEC0DF) + return false; + + // Tls was valid for the real oep.. + this.TlsAsOep = true; + this.TlsOepRva = fileOffset; + return true; + } + + /// + /// Step #2 + /// + /// Read, decode and process the payload data. + /// + /// + private bool Step2() + { + // Obtain the payload address and size.. + var payloadAddr = this.File.GetFileOffsetFromRva(this.TlsAsOep ? this.TlsOepRva : this.File.NtHeaders.OptionalHeader.AddressOfEntryPoint - this.StubHeader.BindSectionOffset); + var payloadSize = (this.StubHeader.PayloadSize + 0x0F) & 0xFFFFFFF0; + + // Do nothing if there is no payload.. + if (payloadSize == 0) + return true; + + this.Log(" --> File has payload data!", LogMessageType.Debug); + + // Obtain and decode the payload.. + var payload = new byte[payloadSize]; + Array.Copy(this.File.FileData, (long)payloadAddr, payload, 0, payloadSize); + this.XorKey = SteamStubHelpers.SteamXor(ref payload, payloadSize, this.XorKey); + + try + { + if (this.Options.DumpPayloadToDisk) + { + System.IO.File.WriteAllBytes(this.File.FilePath + ".payload", payload); + this.Log(" --> Saved payload to disk!", LogMessageType.Debug); + } + } + catch + { + // Do nothing here since it doesn't matter if this fails.. + } + + return true; + } + + /// + /// Step #3 + /// + /// Read, decode and dump the SteamDRMP.dll file. + /// + /// + private bool Step3() + { + // Ensure there is a dll to process.. + if (this.StubHeader.DRMPDllSize == 0) + { + this.Log(" --> File does not contain a SteamDRMP.dll file.", LogMessageType.Debug); + return true; + } + + this.Log(" --> File has SteamDRMP.dll file!", LogMessageType.Debug); + + try + { + // Obtain the SteamDRMP.dll file address and data.. + var drmpAddr = this.File.GetFileOffsetFromRva(this.TlsAsOep ? this.TlsOepRva : this.File.NtHeaders.OptionalHeader.AddressOfEntryPoint - this.StubHeader.BindSectionOffset + this.StubHeader.DRMPDllOffset); + var drmpData = new byte[this.StubHeader.DRMPDllSize]; + Array.Copy(this.File.FileData, (long)drmpAddr, drmpData, 0, drmpData.Length); + + // Decrypt the data (xtea decryption).. + SteamStubHelpers.SteamDrmpDecryptPass1(ref drmpData, this.StubHeader.DRMPDllSize, this.StubHeader.EncryptionKeys); + + try + { + if (this.Options.DumpSteamDrmpToDisk) + { + var basePath = Path.GetDirectoryName(this.File.FilePath) ?? string.Empty; + System.IO.File.WriteAllBytes(Path.Combine(basePath, "SteamDRMP.dll"), drmpData); + this.Log(" --> Saved SteamDRMP.dll to disk!", LogMessageType.Debug); + } + } + catch + { + // Do nothing here since it doesn't matter if this fails.. + } + + return true; + } + catch + { + this.Log(" --> Error trying to decrypt the files SteamDRMP.dll data!", LogMessageType.Error); + return false; + } + } + + /// + /// Step #4 + /// + /// Remove the bind section if requested. + /// Find the code section. + /// + /// + private bool Step4() + { + // Remove the bind section if its not requested to be saved.. + if (!this.Options.KeepBindSection) + { + // Obtain the .bind section.. + var bindSection = this.File.GetSection(".bind"); + if (!bindSection.IsValid) + return false; + + // Remove the section.. + this.File.RemoveSection(bindSection); + + // Decrease the header section count.. + var ntHeaders = this.File.NtHeaders; + ntHeaders.FileHeader.NumberOfSections--; + this.File.NtHeaders = ntHeaders; + + this.Log(" --> .bind section was removed from the file.", LogMessageType.Debug); + } + else + this.Log(" --> .bind section was kept in the file.", LogMessageType.Debug); + + // Skip finding the code section if the file is not encrypted.. + if ((this.StubHeader.Flags & (uint)SteamStubDrmFlags.NoEncryption) == (uint)SteamStubDrmFlags.NoEncryption) + return true; + + // Find the code section.. + var codeSection = this.File.GetOwnerSection(this.StubHeader.CodeSectionVirtualAddress); + if (codeSection.PointerToRawData == 0 || codeSection.SizeOfRawData == 0) + return false; + + // Store the code sections index.. + this.CodeSectionIndex = this.File.GetSectionIndex(codeSection); + + return true; + } + + /// + /// Step #5 + /// + /// Read, decrypt and process the code section. + /// + /// + private bool Step5() + { + // Skip decryption if the code section is not encrypted.. + if ((this.StubHeader.Flags & (uint)SteamStubDrmFlags.NoEncryption) == (uint)SteamStubDrmFlags.NoEncryption) + { + this.Log(" --> Code section is not encrypted.", LogMessageType.Debug); + return true; + } + + try + { + // Obtain the code section.. + var codeSection = this.File.Sections[this.CodeSectionIndex]; + this.Log($" --> {codeSection.SectionName} linked as main code section.", LogMessageType.Debug); + this.Log($" --> {codeSection.SectionName} section is encrypted.", LogMessageType.Debug); + + // Obtain the code section data.. + var codeSectionData = new byte[codeSection.SizeOfRawData + this.StubHeader.CodeSectionStolenData.Length]; + Array.Copy(this.StubHeader.CodeSectionStolenData, (long)0, codeSectionData, 0, this.StubHeader.CodeSectionStolenData.Length); + Array.Copy(this.File.FileData, (long)this.File.GetFileOffsetFromRva(codeSection.VirtualAddress), codeSectionData, this.StubHeader.CodeSectionStolenData.Length, codeSection.SizeOfRawData); + + // Create the AES decryption helper.. + var aes = new AesHelper(this.StubHeader.AES_Key, this.StubHeader.AES_IV); + aes.RebuildIv(this.StubHeader.AES_IV); + + // Decrypt the code section data.. + var data = aes.Decrypt(codeSectionData, CipherMode.CBC, PaddingMode.None); + if (data == null) + return false; + + // Set the code section override data.. + this.CodeSectionData = data; + + return true; + } + catch + { + this.Log(" --> Error trying to decrypt the files code section data!", LogMessageType.Error); + return false; + } + } + + /// + /// Step #6 + /// + /// Rebuild and save the unpacked file. + /// + /// + private bool Step6() + { + FileStream fStream = null; + + try + { + // Rebuild the file sections.. + this.File.RebuildSections(); + + // Open the unpacked file for writing.. + var unpackedPath = this.File.FilePath + ".unpacked.exe"; + fStream = new FileStream(unpackedPath, FileMode.Create, FileAccess.ReadWrite); + + // Write the DOS header to the file.. + fStream.WriteBytes(Pe64Helpers.GetStructureBytes(this.File.DosHeader)); + + // Write the DOS stub to the file.. + if (this.File.DosStubSize > 0) + fStream.WriteBytes(this.File.DosStubData); + + // Update the entry point of the file.. + var ntHeaders = this.File.NtHeaders; + ntHeaders.OptionalHeader.AddressOfEntryPoint = (uint)this.StubHeader.OriginalEntryPoint; + this.File.NtHeaders = ntHeaders; + + // Write the NT headers to the file.. + fStream.WriteBytes(Pe64Helpers.GetStructureBytes(ntHeaders)); + + // Write the sections to the file.. + for (var x = 0; x < this.File.Sections.Count; x++) + { + var section = this.File.Sections[x]; + var sectionData = this.File.SectionData[x]; + + // Write the section header to the file.. + fStream.WriteBytes(Pe64Helpers.GetStructureBytes(section)); + + // Set the file pointer to the sections raw data.. + var sectionOffset = fStream.Position; + fStream.Position = section.PointerToRawData; + + // Write the sections raw data.. + var sectionIndex = this.File.Sections.IndexOf(section); + if (sectionIndex == this.CodeSectionIndex) + fStream.WriteBytes(this.CodeSectionData ?? sectionData); + else + fStream.WriteBytes(sectionData); + + // Reset the file offset.. + fStream.Position = sectionOffset; + } + + // Set the stream to the end of the file.. + fStream.Position = fStream.Length; + + // Write the overlay data if it exists.. + if (this.File.OverlayData != null) + fStream.WriteBytes(this.File.OverlayData); + + this.Log(" --> Unpacked file saved to disk!", LogMessageType.Success); + this.Log($" --> File Saved As: {unpackedPath}", LogMessageType.Success); + + return true; + } + catch + { + this.Log(" --> Error trying to save unpacked file!", LogMessageType.Error); + return false; + } + finally + { + fStream?.Dispose(); + } + } + + /// + /// Gets or sets if the Tls callback is being used as the Oep. + /// + private bool TlsAsOep { get; set; } + + /// + /// Gets or sets the Tls Oep Rva if it is being used as the Oep. + /// + private ulong TlsOepRva { get; set; } + + /// + /// Gets or sets the Steamless options this file was requested to process with. + /// + private SteamlessOptions Options { get; set; } + + /// + /// Gets or sets the file being processed. + /// + private Pe64File File { get; set; } + + /// + /// Gets or sets the current xor key being used against the file data. + /// + private uint XorKey { get; set; } + + /// + /// Gets or sets the DRM stub header. + /// + private SteamStub64Var31Header StubHeader { get; set; } + + /// + /// Gets or sets the index of the code section. + /// + private int CodeSectionIndex { get; set; } + + /// + /// Gets or sets the decrypted code section data. + /// + private byte[] CodeSectionData { get; set; } + } +} \ No newline at end of file diff --git a/Steamless.Unpacker.Variant31.x64/Properties/AssemblyInfo.cs b/Steamless.Unpacker.Variant31.x64/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..259edd1 --- /dev/null +++ b/Steamless.Unpacker.Variant31.x64/Properties/AssemblyInfo.cs @@ -0,0 +1,40 @@ +/** + * Steamless - Copyright (c) 2015 - 2017 atom0s [atom0s@live.com] + * + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ or send a letter to + * Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * By using Steamless, you agree to the above license and its terms. + * + * Attribution - You must give appropriate credit, provide a link to the license and indicate if changes were + * made. You must do so in any reasonable manner, but not in any way that suggests the licensor + * endorses you or your use. + * + * Non-Commercial - You may not use the material (Steamless) for commercial purposes. + * + * No-Derivatives - If you remix, transform, or build upon the material (Steamless), you may not distribute the + * modified material. You are, however, allowed to submit the modified works back to the original + * Steamless project in attempt to have it added to the original project. + * + * You may not apply legal terms or technological measures that legally restrict others + * from doing anything the license permits. + * + * No warranties are given. + */ + +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Steamless.Unpacker.Variant31.x64")] +[assembly: AssemblyDescription("Steamless SteamStub Variant v3.1 (x64) Unpacker")] +[assembly: AssemblyConfiguration("Release")] +[assembly: AssemblyCompany("atom0s")] +[assembly: AssemblyProduct("Steamless.Unpacker.Variant31.x64")] +[assembly: AssemblyCopyright("Copyright © atom0s 2015 - 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: Guid("05f540fb-d14b-4966-8de2-591b76361cf0")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/Steamless.Unpacker.Variant31.x64/Steamless.Unpacker.Variant31.x64.csproj b/Steamless.Unpacker.Variant31.x64/Steamless.Unpacker.Variant31.x64.csproj new file mode 100644 index 0000000..9e1f04c --- /dev/null +++ b/Steamless.Unpacker.Variant31.x64/Steamless.Unpacker.Variant31.x64.csproj @@ -0,0 +1,56 @@ + + + + + Debug + AnyCPU + {05F540FB-D14B-4966-8DE2-591B76361CF0} + Library + Properties + Steamless.Unpacker.Variant31.x64 + Steamless.Unpacker.Variant31.x64 + v4.5.2 + 512 + + + x86 + ..\Steamless\bin\x86\Debug\Plugins\ + TRACE;DEBUG + + + x86 + ..\Steamless\bin\x86\Release\Plugins\ + true + + + + + + + + + + + + + + + + + + + + + {56c95629-3b34-47fe-b988-04274409294f} + Steamless.API + + + + + \ No newline at end of file diff --git a/Steamless.Unpacker.Variant31.x86/Properties/AssemblyInfo.cs b/Steamless.Unpacker.Variant31.x86/Properties/AssemblyInfo.cs index 173a2ed..1fa83a7 100644 --- a/Steamless.Unpacker.Variant31.x86/Properties/AssemblyInfo.cs +++ b/Steamless.Unpacker.Variant31.x86/Properties/AssemblyInfo.cs @@ -36,5 +36,5 @@ using System.Runtime.InteropServices; [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: Guid("0f2fae37-f898-4392-b4f6-711954beeb4f")] -[assembly: AssemblyVersion("1.0.0.1")] -[assembly: AssemblyFileVersion("1.0.0.1")] \ No newline at end of file +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/Steamless.sln b/Steamless.sln index 40b2964..45eac49 100644 --- a/Steamless.sln +++ b/Steamless.sln @@ -15,6 +15,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steamless.Unpacker.Variant3 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steamless.Unpacker.Variant20.x86", "Steamless.Unpacker.Variant20.x86\Steamless.Unpacker.Variant20.x86.csproj", "{A40154CD-A0FD-4371-8099-CE277E0989AF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steamless.Unpacker.Variant31.x64", "Steamless.Unpacker.Variant31.x64\Steamless.Unpacker.Variant31.x64.csproj", "{05F540FB-D14B-4966-8DE2-591B76361CF0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Steamless.Unpacker.Variant30.x64", "Steamless.Unpacker.Variant30.x64\Steamless.Unpacker.Variant30.x64.csproj", "{03621EAD-77A7-4208-AFDF-4B8292230A71}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x86 = Debug|x86 @@ -45,6 +49,14 @@ Global {A40154CD-A0FD-4371-8099-CE277E0989AF}.Debug|x86.Build.0 = Debug|x86 {A40154CD-A0FD-4371-8099-CE277E0989AF}.Release|x86.ActiveCfg = Release|x86 {A40154CD-A0FD-4371-8099-CE277E0989AF}.Release|x86.Build.0 = Release|x86 + {05F540FB-D14B-4966-8DE2-591B76361CF0}.Debug|x86.ActiveCfg = Debug|x86 + {05F540FB-D14B-4966-8DE2-591B76361CF0}.Debug|x86.Build.0 = Debug|x86 + {05F540FB-D14B-4966-8DE2-591B76361CF0}.Release|x86.ActiveCfg = Release|x86 + {05F540FB-D14B-4966-8DE2-591B76361CF0}.Release|x86.Build.0 = Release|x86 + {03621EAD-77A7-4208-AFDF-4B8292230A71}.Debug|x86.ActiveCfg = Debug|x86 + {03621EAD-77A7-4208-AFDF-4B8292230A71}.Debug|x86.Build.0 = Debug|x86 + {03621EAD-77A7-4208-AFDF-4B8292230A71}.Release|x86.ActiveCfg = Release|x86 + {03621EAD-77A7-4208-AFDF-4B8292230A71}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE