Advertisement
expired6978

Unpacker Variant 4

Nov 21st, 2016
252
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 21.94 KB | None | 0 0
  1. /**
  2.  * Steamless Steam DRM Remover
  3.  * (c) 2015 atom0s [atom0s@live.com]
  4.  *
  5.  * This program is free software: you can redistribute it and/or modify
  6.  * it under the terms of the GNU General Public License as published by
  7.  * the Free Software Foundation, either version 3 of the License, or
  8.  * (at your option) any later version.
  9.  *
  10.  * This program is distributed in the hope that it will be useful,
  11.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13.  * GNU General Public License for more details.
  14.  *
  15.  * You should have received a copy of the GNU General Public License
  16.  * along with this program.  If not, see http://www.gnu.org/licenses/
  17.  */
  18.  
  19. namespace Steamless.NET.Unpackers
  20. {
  21.     using Classes;
  22.     using Extensions;
  23.     using System;
  24.     using System.Collections.Generic;
  25.     using System.IO;
  26.     using System.Linq;
  27.     using System.Runtime.InteropServices;
  28.     using System.Security.Cryptography;
  29.  
  30.     /// <summary>
  31.     /// Steam Stub DRM Unpacker (Variant #4)
  32.     /// </summary>
  33.     [SteamStubUnpacker(
  34.         Author = "gibbed",
  35.         Name = "SteamStub Variant #4",
  36.         Pattern = "E8 00 00 00 00 50 53 51 52 56 57 55 41 50 41 51 41 52 41 53 41 54 41 55 41 56 41 57 48 8B 4C 24 78 48 81 E9 05 00 00 00 48 8B C4 48 83 E4 F0 50 50 48 83 EC 20")]
  37.     public class SteamStubVariant4 : SteamStubUnpacker
  38.     {
  39.         /// <summary>
  40.         /// SteamStub Variant 4 DRM Flags
  41.         /// </summary>
  42.         public enum DrmFlags
  43.         {
  44.             NoModuleVerification = 0x02,
  45.             NoEncryption = 0x04,
  46.             NoOwnershipCheck = 0x10,
  47.             NoDebuggerCheck = 0x20,
  48.             NoErrorDialog = 0x40
  49.         }
  50.  
  51.         /// <summary>
  52.         /// SteamStub Variant 4 DRM Header
  53.         /// </summary>
  54.         [StructLayout(LayoutKind.Sequential)]
  55.         public struct SteamStub64Var4Header
  56.         {
  57.             public uint XorKey; // The base XOR key, if defined, to unpack the file with.
  58.             public uint Signature; // 0xC0DEC0DE signature to validate this header is proper.
  59.             public ulong ImageBase; // The base of the image that is protected.
  60.             public ulong AddressOfEntryPoint; // The entry point that is set from the DRM.
  61.             public uint BindSectionOffset; // The starting offset to the bind section data. RVA(AddressOfEntryPoint - BindSectionOffset)
  62.             public uint Unknown0000; // [Cyanic: This field is most likely the .bind code size.]
  63.             public ulong OriginalEntryPoint; // The original entry point of the binary before it was protected.
  64.             public uint Unknown0001; // [Cyanic: This field is most likely an offset to a string table.]
  65.             public uint PayloadSize; // The size of the payload data.
  66.             public uint DRMPDLLOffset; // The offset to the SteamDRMP.dll file.
  67.             public uint DRMPDLLSize; // The size of the SteamDRMP.dll file.
  68.             public uint SteamAppId; // The Steam Application ID of this game.
  69.             public uint Flags; // The DRM flags used while creating the protected executable.
  70.             public uint BindSectionVirtualSize; // The bind section virtual size.
  71.             public uint Unknown0002; // [Cyanic: This field is most likely a hash of some sort.]
  72.             public ulong TextSectionVirtualAddress; // The text section virtual address.
  73.             public ulong TextSectionRawSize; // The raw size of the text section.
  74.  
  75.             [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)]
  76.             public byte[] AES_Key; // The AES encryption key.
  77.  
  78.             [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)]
  79.             public byte[] AES_IV; // The AES encryption IV.
  80.  
  81.             [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)]
  82.             public byte[] TextSectionStolenData; // The first 16 bytes of the .text section stolen.
  83.  
  84.             [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x04)]
  85.             public uint[] EncryptionKeys; // Encryption keys used for decrypting SteamDRMP.dll file.
  86.  
  87.             public uint Unknown0003; // [Cyanic: This field is most likely used to flag if the file has Tls data or not.]
  88.             public uint Unknown0004;
  89.             public uint Unknown0005;
  90.             public uint Unknown0006;
  91.             public uint Unknown0007;
  92.             public uint Unknown0008;
  93.             public ulong GetModuleHandleA_RVA; // The RVA to GetModuleHandleA.
  94.             public ulong GetModuleHandleW_RVA; // The RVA to GetModuleHandleW.
  95.             public ulong LoadLibraryA_RVA; // The RVA to LoadLibraryA.
  96.             public ulong LoadLibraryW_RVA; // The RVA to LoadLibraryW.
  97.             public ulong GetProcAddress_RVA; // The RVA to GetProcAddress.
  98.             public uint Unknown0009;
  99. //            public uint Unknown0010;
  100.  //           public uint Unknown0011;
  101.             //public uint unk0;
  102.             //public uint unk1;
  103.             //public uint unk2;
  104.             //public uint unk3;
  105.             //public uint unk4;
  106.             //public uint unk5;
  107.             //public uint unk6;
  108.  
  109.         }
  110.  
  111.         /// <summary>
  112.         /// Processes the given file in attempt to unpack the Steam Stub variant 4.
  113.         /// </summary>
  114.         /// <param name="file"></param>
  115.         /// <returns></returns>
  116.         public override bool Process(Pe64File file)
  117.         {
  118.             Program.Output("File is packed with SteamStub Variant #4!", ConsoleOutputType.Info);
  119.  
  120.             // Store the file object being processed..
  121.             this.File = file;
  122.  
  123.             // Step #1 - Read the steam stub header.
  124.             Program.Output("Info: Unpacker Stage #1", ConsoleOutputType.Custom, ConsoleColor.Magenta);
  125.             if (!this.Step1())
  126.                 return false;
  127.  
  128.             // Step #2 - Read the payload.
  129.             Program.Output("Info: Unpacker Stage #2", ConsoleOutputType.Custom, ConsoleColor.Magenta);
  130.             if (!this.Step2())
  131.                 return false;
  132.  
  133.             // Step #3 - Read the SteamDRMP.dll file.
  134.             Program.Output("Info: Unpacker Stage #3", ConsoleOutputType.Custom, ConsoleColor.Magenta);
  135.             if (!this.Step3())
  136.                 return false;
  137.  
  138.             // Step #4 - Read the code section.
  139.             Program.Output("Info: Unpacker Stage #4", ConsoleOutputType.Custom, ConsoleColor.Magenta);
  140.             if (!this.Step4())
  141.                 return false;
  142.  
  143.             // Step #5 - Save the file.
  144.             Program.Output("Info: Unpacker Stage #5", ConsoleOutputType.Custom, ConsoleColor.Magenta);
  145.             if (!this.Step5())
  146.                 return false;
  147.  
  148.             Program.Output("Processed the file successfully!", ConsoleOutputType.Success);
  149.  
  150.             return true;
  151.         }
  152.  
  153.         private static int[] GetIntArrayFromByteAray(byte[] byteArray)
  154.         {
  155.             int[] intArray = new int[byteArray.Length / 4];
  156.             for (int i = 0; i < byteArray.Length; i += 4)
  157.                 intArray[i / 4] = BitConverter.ToInt32(byteArray, i);
  158.             return intArray;
  159.         }
  160.  
  161.         /// <summary>
  162.         /// Step #1
  163.         ///
  164.         /// Reads, decodes and validates the Steam DRM header.
  165.         /// </summary>
  166.         /// <returns></returns>
  167.         private bool Step1()
  168.         {
  169.             // List of stub sizes..
  170.             var stubSizeList = new List<int>
  171.                 {
  172.                     // Default v4 Stub Size (Based on structure above.)
  173.                     Marshal.SizeOf(typeof(SteamStub64Var4Header)),
  174.                 };
  175.  
  176.             Structures.ImageSectionHeader bindHeader = this.File.GetSection(".bind");
  177.             var bindData = GetIntArrayFromByteAray(this.File.GetSectionData(".bind"));
  178.  
  179.             // Loop each stub size and attempt to unpack the stub header..
  180.             foreach (var stubSize in stubSizeList)
  181.             {
  182.                 // Obtain the entry point file offset..
  183.                 var fileOffset = this.File.GetFileOffsetFromRva(this.File.NtHeaders.OptionalHeader.AddressOfEntryPoint);
  184.  
  185.                 // Read the raw header data from the file data..
  186.                 var headerData = new byte[Marshal.SizeOf(typeof(SteamStub64Var4Header))];
  187.                 Array.Copy(this.File.FileData, (int)(fileOffset - stubSize), headerData, 0, Marshal.SizeOf(typeof(SteamStub64Var4Header)));
  188.  
  189.  
  190.                 // Decode and obtain the steam stub header..
  191.                 int xorkeyoffset = (int)(Marshal.OffsetOf(typeof(SteamStub64Var4Header), "XorKey"));
  192.                 uint key = BitConverter.ToUInt32(headerData, xorkeyoffset);
  193.                 this.XorKey = SteamXor(ref headerData, (uint)Marshal.SizeOf(typeof(SteamStub64Var4Header)));
  194.                 this.StubHeader = Helpers.GetStructure<SteamStub64Var4Header>(headerData);
  195.                 var intHeaderData = GetIntArrayFromByteAray(headerData);
  196.  
  197.                 // Validate the header signature..
  198.                 if (this.StubHeader.Signature == 0xC0DEC0DF)
  199.                     return true;
  200.             }
  201.  
  202.             // If we got here, we failed to unpack the header and cannot continue..
  203.             return false;
  204.         }
  205.  
  206.         /// <summary>
  207.         /// Step #2
  208.         ///
  209.         /// Reads, decodes, and processes the payload data.
  210.         /// </summary>
  211.         /// <returns></returns>
  212.         private bool Step2()
  213.         {
  214.             // Obtain the payload address and size..
  215.             var payloadAddr = this.File.GetFileOffsetFromRva(this.File.NtHeaders.OptionalHeader.AddressOfEntryPoint - this.StubHeader.BindSectionOffset);
  216.             var payloadSize = (this.StubHeader.PayloadSize + 0x0F) & 0xFFFFFFF0;
  217.  
  218.             // Do nothing if we have no payload to process..
  219.             if (payloadSize == 0)
  220.                 return true;
  221.  
  222.             // Obtain and decode the payload..
  223.             var payload = new byte[payloadSize];
  224.             Array.Copy(this.File.FileData, payloadAddr, payload, 0, payloadSize);
  225.             this.XorKey = SteamXor(ref payload, payloadSize, this.XorKey);
  226.  
  227.             // TODO: Do something with the payload here..
  228.  
  229.             return true;
  230.         }
  231.  
  232.         /// <summary>
  233.         /// Step #3
  234.         ///
  235.         /// Reads, decodes, and dumps the SteamDRMP.dll file.
  236.         /// </summary>
  237.         /// <returns></returns>
  238.         private bool Step3()
  239.         {
  240.             // Ensure we have a file to process..
  241.             if (this.StubHeader.DRMPDLLSize == 0)
  242.                 return true;
  243.  
  244.             try
  245.             {
  246.                 // Obtain the SteamDRMP.dll file address and data..
  247.                 var drmpAddr = this.File.GetFileOffsetFromRva(this.File.NtHeaders.OptionalHeader.AddressOfEntryPoint - this.StubHeader.BindSectionOffset + this.StubHeader.DRMPDLLOffset);
  248.                 var drmpData = new byte[this.StubHeader.DRMPDLLSize];
  249.                 Array.Copy(this.File.FileData, drmpAddr, drmpData, 0, drmpData.Length);
  250.  
  251.                 // Decrypt the data (XTea Decryption)..
  252.                 SteamDrmpDecryptPass1(ref drmpData, this.StubHeader.DRMPDLLSize, this.StubHeader.EncryptionKeys);
  253.  
  254.                 // Obtain the path of the current file..
  255.                 var basePath = Path.GetDirectoryName(this.File.FilePath);
  256.                 if (string.IsNullOrEmpty(basePath))
  257.                     return false;
  258.  
  259.                 // Attempt to save the SteamDRMP.dll file..
  260.                 var path = Path.Combine(basePath, "SteamDRMP.dll");
  261.                 System.IO.File.WriteAllBytes(path, drmpData);
  262.  
  263.                 return true;
  264.             }
  265.             catch
  266.             {
  267.                 return false;
  268.             }
  269.         }
  270.  
  271.         /// <summary>
  272.         /// Step #4
  273.         ///
  274.         /// Read, decode, and process the code section.
  275.         /// </summary>
  276.         /// <returns></returns>
  277.         private bool Step4()
  278.         {
  279.             // Do nothing if we are not encrypted..
  280.             if ((this.StubHeader.Flags & (uint)DrmFlags.NoEncryption) == (uint)DrmFlags.NoEncryption)
  281.                 return true;
  282.  
  283.             // Obtain the main code section that is encrypted..
  284.             var mainSection = this.File.GetOwnerSection(this.StubHeader.TextSectionVirtualAddress);
  285.             if (mainSection.PointerToRawData == 0 || mainSection.SizeOfRawData == 0)
  286.                 return false;
  287.  
  288.             // Save the code section for later use..
  289.             this.CodeSection = mainSection;
  290.  
  291.             try
  292.             {
  293.                 // Obtain the .text section data..
  294.                 var textSectionData = new byte[mainSection.SizeOfRawData + this.StubHeader.TextSectionStolenData.Length];
  295.                 Array.Copy(this.StubHeader.TextSectionStolenData, 0, textSectionData, 0, this.StubHeader.TextSectionStolenData.Length);
  296.                 Array.Copy(this.File.FileData, this.File.GetFileOffsetFromRva(mainSection.VirtualAddress), textSectionData, this.StubHeader.TextSectionStolenData.Length, mainSection.SizeOfRawData);
  297.  
  298.                 // Create the AES decryption class..
  299.                 var aes = new AesHelper(this.StubHeader.AES_Key, this.StubHeader.AES_IV, CipherMode.ECB, PaddingMode.None);
  300.                 aes.RebuildIv(this.StubHeader.AES_IV);
  301.                 var data = aes.Decrypt(textSectionData, CipherMode.CBC, PaddingMode.None);
  302.                 if (data == null)
  303.                     return false;
  304.  
  305.                 // Set the override section data..
  306.                 this.CodeSectionData = data;
  307.  
  308.                 return true;
  309.             }
  310.             catch
  311.             {
  312.                 return false;
  313.             }
  314.         }
  315.  
  316.         /// <summary>
  317.         /// Step #5
  318.         ///
  319.         /// Save the unpacked file.
  320.         /// </summary>
  321.         /// <returns></returns>
  322.         private bool Step5()
  323.         {
  324.             FileStream fStream = null;
  325.             byte[] overlayData = null;
  326.  
  327.             try
  328.             {
  329.                 // Determine if the file has any overlay data..
  330.                 var lastSection = this.File.Sections.Last();
  331.                 var fileSize = lastSection.SizeOfRawData + lastSection.PointerToRawData;
  332.  
  333.                 if (fileSize < this.File.FileData.Length)
  334.                 {
  335.                     // Overlay exists, copy it..
  336.                     overlayData = new byte[this.File.FileData.Length - fileSize];
  337.                     Array.Copy(this.File.FileData, fileSize, overlayData, 0, this.File.FileData.Length - fileSize);
  338.                 }
  339.             }
  340.             catch
  341.             {
  342.                 return false;
  343.             }
  344.  
  345.             try
  346.             {
  347.                 // Open the unpacked file for writing..
  348.                 var unpackedPath = this.File.FilePath + ".unpacked.exe";
  349.                 fStream = new FileStream(unpackedPath, FileMode.Create, FileAccess.ReadWrite);
  350.  
  351.                 // Write the dos header back to the file..
  352.                 fStream.WriteBytes(Helpers.GetStructureBytes(this.File.DosHeader));
  353.  
  354.                 // Write the dos stub back to the file if it exists..
  355.                 if (this.File.DosStubSize > 0)
  356.                     fStream.WriteBytes(this.File.DosStubData);
  357.  
  358.                 // Determine if we should remove the .bind section..
  359.                 if (!Program.HasArgument("--keepbind"))
  360.                 {
  361.                     // Remove the .bind section from the file..
  362.                     this.File.Sections.Remove(this.File.GetSection(".bind"));
  363.                 }
  364.  
  365.                 // Rebuild the NT headers of the file..
  366.                 var ntHeaders = this.File.NtHeaders;
  367.                 var lastSection = this.File.Sections[this.File.Sections.Count - 1];
  368.                 if (!Program.HasArgument("--keepbind"))
  369.                     ntHeaders.FileHeader.NumberOfSections--;
  370.                 ntHeaders.OptionalHeader.AddressOfEntryPoint = (uint)this.StubHeader.OriginalEntryPoint;
  371.                 ntHeaders.OptionalHeader.SizeOfImage = lastSection.VirtualAddress + lastSection.VirtualSize;
  372.  
  373.                 // Write the Nt headers to the file..
  374.                 fStream.WriteBytes(Helpers.GetStructureBytes(ntHeaders));
  375.  
  376.                 // Write the sections to the file..
  377.                 foreach (var s in this.File.Sections)
  378.                 {
  379.                     // Obtain the sections data from the original file..
  380.                     var sectionData = new byte[s.SizeOfRawData];
  381.                     Array.Copy(this.File.FileData, this.File.GetFileOffsetFromRva(s.VirtualAddress), sectionData, 0, s.SizeOfRawData);
  382.  
  383.                     // Write the section header to the file..
  384.                     fStream.WriteBytes(Helpers.GetStructureBytes(s));
  385.  
  386.                     // Write the section data to the file..
  387.                     var sectionOffset = fStream.Position;
  388.                     fStream.Position = s.PointerToRawData;
  389.  
  390.                     // Determine if this is the code section..
  391.                     if (s.SizeOfRawData == this.CodeSection.SizeOfRawData && s.PointerToRawData == this.CodeSection.PointerToRawData)
  392.                         fStream.WriteBytes(this.CodeSectionData ?? sectionData);
  393.                     else
  394.                         fStream.WriteBytes(sectionData);
  395.  
  396.                     // Reset the file offset..
  397.                     fStream.Position = sectionOffset;
  398.                 }
  399.  
  400.                 // Skip to the end of the stream..
  401.                 fStream.Position = fStream.Length;
  402.  
  403.                 // Write the overlay back to the file if it exists..
  404.                 if (overlayData != null)
  405.                     fStream.WriteBytes(overlayData);
  406.  
  407.                 return true;
  408.             }
  409.             catch
  410.             {
  411.                 return false;
  412.             }
  413.             finally
  414.             {
  415.                 if (fStream != null) fStream.Dispose();
  416.             }
  417.         }
  418.  
  419.         /// <summary>
  420.         /// Xor decrypts the given data starting with the given key, if any.
  421.         ///
  422.         /// @note    If no key is given (0) then the first key is read from the first
  423.         ///          4 bytes inside of the data given.
  424.         /// </summary>
  425.         /// <param name="data">The data to xor decode.</param>
  426.         /// <param name="size">The size of the data to decode.</param>
  427.         /// <param name="key">The starting xor key to decode with.</param>
  428.         /// <returns></returns>
  429.         private static uint SteamXor(ref byte[] data, uint size, uint key = 0)
  430.         {
  431.             var offset = (uint)0;
  432.  
  433.             // Read the first key as the base xor key if we had none given..
  434.             if (key == 0)
  435.             {
  436.                 offset += 4;
  437.                 key = BitConverter.ToUInt32(data, 0);
  438.             }
  439.  
  440.             // Decode the data..
  441.             for (var x = offset; x < size; x += 4)
  442.             {
  443.                 var val = BitConverter.ToUInt32(data, (int)x);
  444.                 Array.Copy(BitConverter.GetBytes(val ^ key), 0, data, x, 4);
  445.  
  446.                 key = val;
  447.             }
  448.  
  449.             return key;
  450.         }
  451.  
  452.         /// <summary>
  453.         /// The second pass of decryption for the SteamDRMP.dll file.
  454.         ///
  455.         /// @note    The encryption method here is known as XTEA.
  456.         /// </summary>
  457.         /// <param name="res">The result value buffer to write our returns to.</param>
  458.         /// <param name="keys">The keys used for the decryption.</param>
  459.         /// <param name="v1">The first value to decrypt from.</param>
  460.         /// <param name="v2">The second value to decrypt from.</param>
  461.         /// <param name="n">The number of passes to crypt the data with.</param>
  462.         private static void SteamDrmpDecryptPass2(ref uint[] res, uint[] keys, uint v1, uint v2, uint n = 32)
  463.         {
  464.             const uint delta = 0x9E3779B9;
  465.             const uint mask = 0xFFFFFFFF;
  466.             var sum = (delta * n) & mask;
  467.  
  468.             for (var x = 0; x < n; x++)
  469.             {
  470.                 v2 = (v2 - (((v1 << 4 ^ v1 >> 5) + v1) ^ (sum + keys[sum >> 11 & 3]))) & mask;
  471.                 sum = (sum - delta) & mask;
  472.                 v1 = (v1 - (((v2 << 4 ^ v2 >> 5) + v2) ^ (sum + keys[sum & 3]))) & mask;
  473.             }
  474.  
  475.             res[0] = v1;
  476.             res[1] = v2;
  477.         }
  478.  
  479.         /// <summary>
  480.         /// The first pass of the decryption for the SteamDRMP.dll file.
  481.         ///
  482.         /// @note    The encryption method here is known as XTEA. It is modded to include
  483.         ///          some basic xor'ing.
  484.         /// </summary>
  485.         /// <param name="data">The data to decrypt.</param>
  486.         /// <param name="size">The size of the data to decrypt.</param>
  487.         /// <param name="keys">The keys used for the decryption.</param>
  488.         private static void SteamDrmpDecryptPass1(ref byte[] data, uint size, uint[] keys)
  489.         {
  490.             var v1 = (uint)0x55555555;
  491.             var v2 = (uint)0x55555555;
  492.  
  493.             for (var x = 0; x < size; x += 8)
  494.             {
  495.                 var d1 = BitConverter.ToUInt32(data, x + 0);
  496.                 var d2 = BitConverter.ToUInt32(data, x + 4);
  497.  
  498.                 var res = new uint[2];
  499.                 SteamDrmpDecryptPass2(ref res, keys, d1, d2);
  500.  
  501.                 Array.Copy(BitConverter.GetBytes(res[0] ^ v1), 0, data, x + 0, 4);
  502.                 Array.Copy(BitConverter.GetBytes(res[1] ^ v2), 0, data, x + 4, 4);
  503.  
  504.                 v1 = d1;
  505.                 v2 = d2;
  506.             }
  507.         }
  508.  
  509.         /// <summary>
  510.         /// Gets or sets the file being processed by this unpacker.
  511.         /// </summary>
  512.         public Pe64File File { get; set; }
  513.  
  514.         /// <summary>
  515.         /// Gets or sets the current xor key.
  516.         /// </summary>
  517.         public uint XorKey { get; set; }
  518.  
  519.         /// <summary>
  520.         /// Gets or sets the steam stub header.
  521.         /// </summary>
  522.         public SteamStub64Var4Header StubHeader { get; set; }
  523.  
  524.         /// <summary>
  525.         /// Gets or sets the code section.
  526.         /// </summary>
  527.         public Structures.ImageSectionHeader CodeSection { get; set; }
  528.  
  529.         /// <summary>
  530.         /// Gets or sets the code section data.
  531.         /// </summary>
  532.         public byte[] CodeSectionData { get; set; }
  533.     }
  534. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement