diff --git a/Steamless.Unpacker.Variant10.x86/Classes/SteamStubHeader.cs b/Steamless.Unpacker.Variant10.x86/Classes/SteamStubHeader.cs new file mode 100644 index 0000000..f3db21d --- /dev/null +++ b/Steamless.Unpacker.Variant10.x86/Classes/SteamStubHeader.cs @@ -0,0 +1,109 @@ +/** + * Steamless - Copyright (c) 2015 - 2022 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.Variant10.x86.Classes +{ + using System.Runtime.InteropServices; + + /// + /// SteamStub DRM Variant 1.0 Header + /// + [StructLayout(LayoutKind.Sequential)] + public struct SteamStub32Var10Header + { + public uint GetModuleHandleA_idata; // The address of GetModuleHandleA inside of the .idata section. + public uint GetProcAddress_idata; // The address of GetProcAddress inside of the .idata section. + public uint BindFunction; // The .bind unpacker function address. + public uint BindCodeSize; // The .bind unpacker function size. + public uint Checksum; // The checksum of the header data after its initialized. (This is done via addition chunking.) + public uint AppId; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x08)] + public byte[] SteamAppIDString; // The SteamAppID of the packed file, in string format. + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] + public byte[] str_kernel32dll; // String: kernel32.dll + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] + public byte[] str_user32dll; // String: user32.dll + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] + public byte[] str_shell32dll; // String: shell32.dll + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] + public byte[] str_loadlibraryexa; // String: LoadLibraryExA + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] + public byte[] str_freelibrary; // String: FreeLibrary + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] + public byte[] str_messageboxa; // String: MessageBoxA + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x18)] + public byte[] str_getmodulefilenamea; // String: GetModuleFileNameA + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] + public byte[] str_lstrlena; // String: lstrlenA + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] + public byte[] str_lstrcata; // String lstrcatA + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] + public byte[] str_exitprocess; // String: ExitProcess + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] + public byte[] str_shellexecutea; // String: ShellExecuteA + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] + public byte[] str_steamerror; // String: Steam Error + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] + public byte[] str_steamdll; // String: Steam.dll + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x18)] + public byte[] str_steamisappsubscribed; // String: SteamIsAppSubscribed + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] + public byte[] str_steamstartup; // String: SteamStartup + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] + public byte[] str_steamcleanup; // String: SteamCleanup + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)] + public byte[] str_failedtofindsteam; // String: Failed to find Steam + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)] + public byte[] str_failedtoloadsteam; // String: Failed to load Steam + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x18)] + public byte[] str_steamstoreurl; // String: steam://store/ + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x18)] + public byte[] str_steamrunurl; // String: steam://run/ + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x08)] + public byte[] str_open; // String: open + } +} \ No newline at end of file diff --git a/Steamless.Unpacker.Variant10.x86/Main.cs b/Steamless.Unpacker.Variant10.x86/Main.cs index a793abe..4874226 100644 --- a/Steamless.Unpacker.Variant10.x86/Main.cs +++ b/Steamless.Unpacker.Variant10.x86/Main.cs @@ -27,10 +27,13 @@ namespace Steamless.Unpacker.Variant10.x86 { using API; using API.Events; + using API.Extensions; using API.Model; using API.PE32; using API.Services; + using Classes; using System; + using System.IO; using System.Reflection; [SteamlessApiVersion(1, 0)] @@ -121,17 +124,213 @@ namespace Steamless.Unpacker.Variant10.x86 /// public override bool ProcessFile(string file, SteamlessOptions options) { + // Initialize the class members.. + this.Options = options; + this.OriginalEntryPoint = 0; + // Parse the file.. this.File = new Pe32File(file); if (!this.File.Parse()) return false; - return false; + // Announce we are being unpacked with this packer.. + this.Log("File is packed with SteamStub Variant 1.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 - Handle .bind section.", LogMessageType.Information); + if (!this.Step2()) + return false; + + this.Log("Step 3 - Rebuild and save the unpacked file.", LogMessageType.Information); + if (!this.Step3()) + return false; + + return true; } + /// + /// Step #1 + /// + /// Read, decode and validate the SteamStub DRM header. + /// + /// + private bool Step1() + { + // Obtain the bind section.. + var section = this.File.GetSection(".bind"); + if (!section.IsValid) + return false; + + // Find the header information from the unpacker call.. + var bind = this.File.GetSectionData(".bind"); + var offset = Pe32Helpers.FindPattern(bind, "60 81 EC 00 10 00 00 BE ?? ?? ?? ?? B9 6A"); + if (offset == -1) + return false; + + // Read the needed header information.. + var headerPointer = BitConverter.ToUInt32(bind, (int)offset + 8); + var headerSize = BitConverter.ToUInt32(bind, (int)offset + 13) * 4; + + // Calculate the file offset from the pointer.. + var fileOffset = this.File.GetFileOffsetFromRva(headerPointer - this.File.NtHeaders.OptionalHeader.ImageBase); + + // Read the header data.. + var headerData = new byte[headerSize]; + Array.Copy(this.File.FileData, fileOffset, headerData, 0, headerSize); + + // Decrypt the header data.. + for (var x = 0; x < headerSize; x++) + headerData[x] ^= (byte)(x * x); + + // Store the header and validate it.. + this.StubHeader = Pe32Helpers.GetStructure(headerData); + + // Validate the header via the unpacker function matching the file entry point.. + if (this.StubHeader.BindFunction - this.File.NtHeaders.OptionalHeader.ImageBase != this.File.NtHeaders.OptionalHeader.AddressOfEntryPoint) + return false; + + // Find the OEP from the unpacker function.. + offset = Pe32Helpers.FindPattern(bind, "61 B8 ?? ?? ?? ?? FF E0"); + if (offset == -1) + return false; + + // Read and store the real OEP.. + this.OriginalEntryPoint = BitConverter.ToUInt32(bind, (int)offset + 2) - this.File.NtHeaders.OptionalHeader.ImageBase; + + return true; + } + + /// + /// Step #2 + /// + /// Remove the bind section if requested. + /// Find the code section. + /// + /// + private bool Step2() + { + // 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); + + return true; + } + + /// + /// Step #3 + /// + /// Rebuild and save the unpacked file. + /// + /// + private bool Step3() + { + FileStream fStream = null; + + try + { + // Rebuild the file sections.. + this.File.RebuildSections(this.Options.DontRealignSections == false); + + // 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(Pe32Helpers.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.OriginalEntryPoint; + this.File.NtHeaders = ntHeaders; + + // Write the NT headers to the file.. + fStream.WriteBytes(Pe32Helpers.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(Pe32Helpers.GetStructureBytes(section)); + + // Set the file pointer to the sections raw data.. + var sectionOffset = fStream.Position; + fStream.Position = section.PointerToRawData; + + // Write the sections raw data.. + 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 the Steamless options this file was requested to process with. + /// + private SteamlessOptions Options { get; set; } + /// /// Gets or sets the file being processed. /// private Pe32File File { get; set; } + + /// + /// Gets or sets the DRM stub header. + /// + private SteamStub32Var10Header StubHeader { get; set; } + + /// + /// Gets or sets the true entry point take from the bind unpacker function. + /// + private uint OriginalEntryPoint { get; set; } } } \ No newline at end of file diff --git a/Steamless.Unpacker.Variant10.x86/Steamless.Unpacker.Variant10.x86.csproj b/Steamless.Unpacker.Variant10.x86/Steamless.Unpacker.Variant10.x86.csproj index 0a4f339..6834d2b 100644 --- a/Steamless.Unpacker.Variant10.x86/Steamless.Unpacker.Variant10.x86.csproj +++ b/Steamless.Unpacker.Variant10.x86/Steamless.Unpacker.Variant10.x86.csproj @@ -42,6 +42,7 @@ +