· 9 years ago · Nov 22, 2016, 12:44 AM
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
19namespace 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}