Multipurpose utility for managing Games for Windows - LIVE installs and content. (Mirrored from https://github.com/InvoxiPlayGames/GfWLUtility)

move profile info parsing into UserManager also adds XeKeysUnObfuscate and some PanoramaCrypto however no key yet :(

+2
GfWLUtility.csproj
··· 94 94 <Compile Include="MainWindow.Designer.cs"> 95 95 <DependentUpon>MainWindow.cs</DependentUpon> 96 96 </Compile> 97 + <Compile Include="PanoramaCrypto.cs" /> 97 98 <Compile Include="ProfileInfoExtractor.cs"> 98 99 <SubType>Form</SubType> 99 100 </Compile> ··· 105 106 <Compile Include="TitleManager.cs" /> 106 107 <Compile Include="UserManager.cs" /> 107 108 <Compile Include="UtilityFuncs.cs" /> 109 + <Compile Include="XeKeys.cs" /> 108 110 <EmbeddedResource Include="DownloadForm.resx"> 109 111 <DependentUpon>DownloadForm.cs</DependentUpon> 110 112 </EmbeddedResource>
+1 -21
MainWindow.cs
··· 1 1 using GfWLUtility.Properties; 2 2 using System; 3 - using System.Collections.Generic; 4 - using System.ComponentModel; 5 - using System.Data; 6 3 using System.Diagnostics; 7 4 using System.Drawing; 8 5 using System.Globalization; 9 6 using System.IO; 10 7 using System.Linq; 11 - using System.Reflection; 12 - using System.Runtime.InteropServices; 13 - using System.Text; 14 8 using System.Windows.Forms; 15 9 16 10 namespace GfWLUtility ··· 333 327 if ((xuidInt & 0xE000000000000000) != 0xE000000000000000) 334 328 continue; 335 329 UserManager.FoundUserExists(xuidInt); 336 - // check the account cache 337 - // TODO: move this into code outside of the main form 338 - string accCacheFilename = UtilityFuncs.GetLocalDirectory("ProfileCache") + xuidInt.ToString("x16") + ".bin"; 339 - if (File.Exists(accCacheFilename)) 340 - { 341 - byte[] accBytes = File.ReadAllBytes(accCacheFilename); 342 - XamAccount account = UtilityFuncs.BytesToStructure<XamAccount>(accBytes); 343 - UserManager.FoundUserGamertag(xuidInt, account.Gamertag); 344 - if ((account.Flags1 & 0x20000000) == 0x20000000) 345 - UserManager.KnownUsers[xuidInt].LiveEnabled = true; 346 - UserManager.KnownUsers[xuidInt].OnlineXUID = account.OnlineXUID; 347 - UserManager.KnownUsers[xuidInt].Pnet = account.OnlineServiceID == 0x54524150; 348 - UserManager.KnownUsers[xuidInt].MsaEmail = account.PassportEmail; 349 - UserManager.KnownUsers[xuidInt].HasFullInformation = true; 350 - } 330 + UserManager.PopulateUserInformation(xuidInt, $"{subdir}\\FFFE07D1\\00010000\\{xuidInt:X16}_MountPt\\Account"); 351 331 } 352 332 } 353 333
+135
PanoramaCrypto.cs
··· 1 + using System; 2 + using System.Linq; 3 + using System.Security.Cryptography; 4 + 5 + namespace GfWLUtility 6 + { 7 + internal class DecryptionFailedException : Exception 8 + { 9 + public DecryptionFailedException(string message) : base(message) { } 10 + } 11 + internal class InvalidKeysetException : Exception 12 + { 13 + public InvalidKeysetException(string message) : base(message) { } 14 + } 15 + 16 + internal class PanoramaCrypto 17 + { 18 + private int _keySet; 19 + private ICryptoTransform _encryptor; 20 + private ICryptoTransform _decryptor; 21 + 22 + //public static PanoramaCrypto Stub = new PanoramaCrypto(0); // for testing CBC mode, not in xlive.dll 23 + 24 + public static PanoramaCrypto Obfuscation = new PanoramaCrypto(1); // obfuscation 25 + public static PanoramaCrypto SysLink = new PanoramaCrypto(2); // system link 26 + 27 + private void SetUpAes(byte[] key) 28 + { 29 + Aes aes = Aes.Create(); 30 + aes.Key = key; 31 + aes.Mode = CipherMode.ECB; 32 + _encryptor = aes.CreateEncryptor(); 33 + _decryptor = aes.CreateDecryptor(); 34 + } 35 + 36 + public PanoramaCrypto(int keySet) 37 + { 38 + _keySet = keySet; 39 + // we have the system link key available thanks to x360 40 + // TODO(Emma): get the GfWL obfuscation keyset! 41 + if (_keySet == 2) 42 + SetUpAes(new byte[0x10] { 0x64, 0xFA, 0x1A, 0xC2, 0x0F, 0xD7, 0x58, 0x07, 0xCA, 0xE6, 0x74, 0xBA, 0xA3, 0xB4, 0x78, 0x7F }); 43 + } 44 + 45 + private void XorWithIv(byte[] block, byte[] iv) 46 + { 47 + for (int i = 0; i < 0x10; i++) 48 + block[i] ^= iv[i]; 49 + } 50 + 51 + private static byte[] StubDecrypt(byte[] input_block) 52 + { 53 + byte[] output_block = new byte[0x10]; 54 + for (int i = 0; i < 0x10; i++) 55 + output_block[i] = (byte)~input_block[i]; 56 + return output_block; 57 + } 58 + 59 + private byte[] AesDecrypt(byte[] input_block) 60 + { 61 + byte[] output_block = new byte[0x10]; 62 + _decryptor.TransformBlock(input_block, 0, 0x10, output_block, 0); 63 + return output_block; 64 + } 65 + 66 + private byte[] AesEncrypt(byte[] input_block) 67 + { 68 + byte[] output_block = new byte[0x10]; 69 + _encryptor.TransformBlock(input_block, 0, 0x10, output_block, 0); 70 + return output_block; 71 + } 72 + 73 + private byte[] Decrypt(byte[] block) 74 + { 75 + if (_keySet == 0) 76 + return StubDecrypt(block); 77 + else if (_keySet == 2) 78 + return AesDecrypt(block); 79 + throw new InvalidKeysetException("PanoramaCrypto doesn't support this key set for decryption"); 80 + } 81 + 82 + private byte[] Encrypt(byte[] block) 83 + { 84 + if (_keySet == 0) 85 + return StubDecrypt(block); 86 + else if (_keySet == 2) 87 + return AesEncrypt(block); 88 + throw new InvalidKeysetException("PanoramaCrypto doesn't support this key set for encryption"); 89 + } 90 + 91 + public byte[] DecryptBuffer(byte[] buffer, bool padding = true) 92 + { 93 + byte[] iv = new byte[0x10]; 94 + byte[] currBlock = new byte[0x10]; 95 + byte[] output = new byte[buffer.Length]; 96 + // sanity check 97 + if (buffer.Length % 0x10 != 0) 98 + throw new DecryptionFailedException("Invalid length"); 99 + // decrypt each block of the input data 100 + for (int i = 0; i < buffer.Length; i += 0x10) 101 + { 102 + Buffer.BlockCopy(buffer, i, currBlock, 0, 0x10); 103 + byte[] newBlock = Decrypt(currBlock); 104 + XorWithIv(newBlock, iv); 105 + Buffer.BlockCopy(currBlock, 0, iv, 0, 0x10); 106 + Buffer.BlockCopy(newBlock, 0, output, i, 0x10); 107 + } 108 + // hacky (and slow, inefficient) handling for PKCS#5 padding 109 + if (padding) 110 + { 111 + byte padByte = output[output.Length - 1]; 112 + if (padByte <= 0x10) 113 + { 114 + int num_pad_bytes = (int)padByte; 115 + byte[] padding_bytes = output.Skip(output.Length - num_pad_bytes).ToArray(); 116 + if (padding_bytes.Length == padByte) 117 + { 118 + for (int i = 0; i < padding_bytes.Length; i++) 119 + if (padding_bytes[i] != padByte) 120 + throw new DecryptionFailedException("Invalid padding"); 121 + } else 122 + { 123 + throw new DecryptionFailedException("Invalid padding"); 124 + } 125 + return output.Take(output.Length - num_pad_bytes).ToArray(); 126 + } 127 + else 128 + { 129 + throw new DecryptionFailedException("Invalid padding"); 130 + } 131 + } 132 + return output; 133 + } 134 + } 135 + }
+33 -4
UserManager.cs
··· 1 - using System; 2 - using System.Collections.Generic; 1 + using System.Collections.Generic; 3 2 using System.Drawing; 4 - using System.Linq; 3 + using System.IO; 5 4 using System.Runtime.InteropServices; 6 - using System.Text; 7 5 8 6 namespace GfWLUtility 9 7 { ··· 96 94 { 97 95 FoundUserExists(xuid); 98 96 KnownUsers[xuid].Gamertag = name; 97 + } 98 + 99 + public static void PopulateUserInformation(ulong xuid, string accountFile = null) 100 + { 101 + FoundUserExists(xuid); 102 + byte[] accBytes = null; 103 + // if we're given one, attempt to decrypt profile information directly 104 + if (accountFile != null) 105 + { 106 + byte[] encBytes = File.ReadAllBytes(accountFile); 107 + accBytes = XeKeys.UnObfuscate(encBytes); 108 + } 109 + // otherwise check if we have decrypted profile information in the account cache 110 + if (accBytes == null) 111 + { 112 + string accCacheFilename = UtilityFuncs.GetLocalDirectory("ProfileCache") + xuid.ToString("x16") + ".bin"; 113 + if (File.Exists(accCacheFilename)) 114 + accBytes = File.ReadAllBytes(accCacheFilename); 115 + } 116 + // if we have a byte stream of the right length of a decrypted account file, hooray 117 + if (accBytes != null && accBytes.Length == 0x17C) 118 + { 119 + XamAccount account = UtilityFuncs.BytesToStructure<XamAccount>(accBytes); 120 + KnownUsers[xuid].Gamertag = account.Gamertag; 121 + if ((account.Flags1 & 0x20000000) == 0x20000000) 122 + KnownUsers[xuid].LiveEnabled = true; 123 + KnownUsers[xuid].OnlineXUID = account.OnlineXUID; 124 + KnownUsers[xuid].Pnet = account.OnlineServiceID == 0x54524150; 125 + KnownUsers[xuid].MsaEmail = account.PassportEmail; 126 + KnownUsers[xuid].HasFullInformation = true; 127 + } 99 128 } 100 129 } 101 130 }
+30
XeKeys.cs
··· 1 + using System; 2 + using System.Linq; 3 + 4 + namespace GfWLUtility 5 + { 6 + /* 7 + * For Xbox 360 researchers among us who might've stumbled upon this code, 8 + * this is not what you're looking for, at all. 9 + * 10 + * I recommend checking out emoose's ExCrypt library for a reference of 11 + * Xbox 360 XeKeys, which will be much more useful for you: 12 + * https://github.com/emoose/ExCrypt 13 + */ 14 + 15 + internal class XeKeys 16 + { 17 + public static byte[] UnObfuscate(byte[] input) 18 + { 19 + // skip over the header - this is populated on xenon but not xlive 20 + byte[] buffer = input.Skip(0x18).ToArray(); 21 + try 22 + { 23 + return PanoramaCrypto.Obfuscation.DecryptBuffer(buffer); 24 + } catch (Exception) 25 + { 26 + return null; 27 + } 28 + } 29 + } 30 + }