From c44659ca175ce60969f4f302a93d517de8cd731c Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Fri, 13 Jul 2018 10:25:39 -0500 Subject: [PATCH] Get title keys from a nand dump --- NandReader/Program.cs | 55 +++++++++++---- libhac.Nand/Nand.cs | 2 +- libhac/AesCtrStream.cs | 23 ++++++ libhac/Crypto.cs | 157 ++++++++++++++++++++++++++++++++++++++++- libhac/Keyset.cs | 5 +- libhac/Ticket.cs | 28 +++++++- 6 files changed, 250 insertions(+), 20 deletions(-) diff --git a/NandReader/Program.cs b/NandReader/Program.cs index 298c198e..81214873 100644 --- a/NandReader/Program.cs +++ b/NandReader/Program.cs @@ -15,7 +15,28 @@ namespace NandReader Console.WriteLine("Usage: NandReader raw_nand_dump_file"); return; } - ReadSwitchFs(args[0]); + GetTitleKeys(args[0]); + } + + private static void GetTitleKeys(string nandFile) + { + using (var logger = new ProgressBar()) + using (var stream = new FileStream(nandFile, FileMode.Open, FileAccess.Read)) + { + var keyset = OpenKeyset(); + var nand = new Nand(stream, keyset); + var prodinfo = nand.OpenProdInfo(); + var calibration = new Calibration(prodinfo); + + keyset.eticket_ext_key_rsa = Crypto.DecryptRsaKey(calibration.EticketExtKeyRsa, keyset.eticket_rsa_kek); + var tickets = GetTickets(nand, logger); + + foreach (var ticket in tickets) + { + var key = ticket.GetTitleKey(keyset); + logger.LogMessage($"{ticket.RightsId.ToHexString()},{key.ToHexString()}"); + } + } } private static void ReadSwitchFs(string nandFile) @@ -47,20 +68,9 @@ namespace NandReader using (var logger = new ProgressBar()) using (var stream = new FileStream(nandFile, FileMode.Open, FileAccess.Read)) { - var tickets = new List(); var keyset = OpenKeyset(); var nand = new Nand(stream, keyset); - var system = nand.OpenSystemPartition(); - - logger.LogMessage("Searching save 80000000000000E1"); - var saveE1 = system.OpenFile("save\\80000000000000E1", FileMode.Open, FileAccess.Read); - tickets.AddRange(Ticket.SearchTickets(saveE1, logger)); - - logger.LogMessage("Searching save 80000000000000E2"); - var saveE2 = system.OpenFile("save\\80000000000000E2", FileMode.Open, FileAccess.Read); - tickets.AddRange(Ticket.SearchTickets(saveE2, logger)); - - logger.LogMessage($"Found {tickets.Count} tickets"); + var tickets = GetTickets(nand, logger); Directory.CreateDirectory("tickets"); foreach (var ticket in tickets) @@ -71,6 +81,24 @@ namespace NandReader } } + private static Ticket[] GetTickets(Nand nand, IProgressReport logger = null) + { + var tickets = new List(); + var system = nand.OpenSystemPartition(); + + logger?.LogMessage("Searching save 80000000000000E1"); + var saveE1 = system.OpenFile("save\\80000000000000E1", FileMode.Open, FileAccess.Read); + tickets.AddRange(Ticket.SearchTickets(saveE1, logger)); + + logger?.LogMessage("Searching save 80000000000000E2"); + var saveE2 = system.OpenFile("save\\80000000000000E2", FileMode.Open, FileAccess.Read); + tickets.AddRange(Ticket.SearchTickets(saveE2, logger)); + + logger?.LogMessage($"Found {tickets.Count} tickets"); + + return tickets.ToArray(); + } + private static Keyset OpenKeyset() { var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); @@ -100,3 +128,4 @@ namespace NandReader } } } + diff --git a/libhac.Nand/Nand.cs b/libhac.Nand/Nand.cs index d51341ce..b9ca5804 100644 --- a/libhac.Nand/Nand.cs +++ b/libhac.Nand/Nand.cs @@ -15,7 +15,7 @@ namespace libhac.Nand private GuidPartitionInfo Safe { get; } private GuidPartitionInfo System { get; } private GuidPartitionInfo User { get; } - private Keyset Keyset { get; } + public Keyset Keyset { get; } public Nand(Stream stream, Keyset keyset) { diff --git a/libhac/AesCtrStream.cs b/libhac/AesCtrStream.cs index e26e79b4..5117cbb7 100644 --- a/libhac/AesCtrStream.cs +++ b/libhac/AesCtrStream.cs @@ -26,6 +26,7 @@ using System; using System.IO; +using System.Linq; using System.Security.Cryptography; using libhac.XTSSharp; @@ -56,6 +57,28 @@ namespace libhac public AesCtrStream(Stream baseStream, byte[] key, long counterOffset = 0) : this(baseStream, key, 0, baseStream.Length, counterOffset) { } + /// + /// Creates a new stream + /// + /// The base stream + /// The decryption key + /// The intial counter + public AesCtrStream(Stream baseStream, byte[] key, byte[] counter) + : base(baseStream, 0x10, 0) + { + _initialCounter = counter.ToArray(); + _counterOffset = 0; + Length = baseStream.Length; + _tempBuffer = new byte[0x10]; + + _aes = Aes.Create(); + if (_aes == null) throw new CryptographicException("Unable to create AES object"); + _aes.Key = key; + _aes.Mode = CipherMode.ECB; + _aes.Padding = PaddingMode.None; + Decryptor = new CounterModeCryptoTransform(_aes, _aes.Key, _initialCounter ?? new byte[0x10]); + } + /// /// Creates a new stream /// diff --git a/libhac/Crypto.cs b/libhac/Crypto.cs index 557abd91..b24ae865 100644 --- a/libhac/Crypto.cs +++ b/libhac/Crypto.cs @@ -1,5 +1,8 @@ -using System.IO; +using System; +using System.IO; +using System.Numerics; using System.Security.Cryptography; +using libhac.XTSSharp; namespace libhac { @@ -9,7 +12,7 @@ namespace libhac { using (var aes = Aes.Create()) { - if(aes == null) throw new CryptographicException("Unable to create AES object"); + if (aes == null) throw new CryptographicException("Unable to create AES object"); aes.Key = key; aes.Mode = CipherMode.ECB; aes.Padding = PaddingMode.None; @@ -26,7 +29,7 @@ namespace libhac public static void DecryptEcb(byte[] key, byte[] src, byte[] dest, int length) => DecryptEcb(key, src, 0, dest, 0, length); - public static void GenerateKek(byte[] dst, byte[] src, byte[] masterKey, byte[] kekSeed, byte[] keySeed) + public static void GenerateKek(byte[] dst, byte[] src, byte[] masterKey, byte[] kekSeed, byte[] keySeed) { var kek = new byte[0x10]; var srcKek = new byte[0x10]; @@ -38,5 +41,153 @@ namespace libhac DecryptEcb(srcKek, keySeed, dst, 0x10); } } + + internal static BigInteger GetBigInteger(byte[] bytes) + { + byte[] signPadded = new byte[bytes.Length + 1]; + Buffer.BlockCopy(bytes, 0, signPadded, 1, bytes.Length); + Array.Reverse(signPadded); + return new BigInteger(signPadded); + } + + public static RsaKey DecryptRsaKey(byte[] encryptedKey, byte[] kek) + { + var counter = new byte[0x10]; + Array.Copy(encryptedKey, counter, 0x10); + var body = new byte[0x230]; + Array.Copy(encryptedKey, 0x10, body, 0, 0x230); + var dec = new byte[0x230]; + + using (var streamDec = new RandomAccessSectorStream(new AesCtrStream(new MemoryStream(body), kek, counter))) + { + streamDec.Read(dec, 0, dec.Length); + } + + var d = new byte[0x100]; + var n = new byte[0x100]; + var e = new byte[4]; + Array.Copy(dec, 0, d, 0, 0x100); + Array.Copy(dec, 0x100, n, 0, 0x100); + Array.Copy(dec, 0x200, e, 0, 4); + + var dInt = GetBigInteger(d); + var nInt = GetBigInteger(n); + var eInt = GetBigInteger(e); + + var test = new BigInteger(12345678); + var testEnc = BigInteger.ModPow(test, dInt, nInt); + var testDec = BigInteger.ModPow(testEnc, eInt, nInt); + + if (test != testDec) + { + throw new InvalidDataException("Could not verify RSA key pair"); + } + + return new RsaKey(n, e, d); + } + + public static byte[] DecryptTitleKey(byte[] titleKeyblock, RsaKey rsaKey) + { + if (rsaKey == null) return new byte[0x10]; + + var tikInt = GetBigInteger(titleKeyblock); + var decInt = BigInteger.ModPow(tikInt, rsaKey.DInt, rsaKey.NInt); + var decBytes = decInt.ToByteArray(); + Array.Reverse(decBytes); + var decBlock = new byte[0x100]; + Array.Copy(decBytes, 0, decBlock, decBlock.Length - decBytes.Length, decBytes.Length); + return UnwrapTitleKey(decBlock); + } + + public static byte[] UnwrapTitleKey(byte[] data) + { + var expectedLabelHash = Ticket.LabelHash; + var salt = new byte[0x20]; + var db = new byte[0xdf]; + Array.Copy(data, 1, salt, 0, salt.Length); + Array.Copy(data, 0x21, db, 0, db.Length); + + CalculateMgf1AndXor(salt, db); + CalculateMgf1AndXor(db, salt); + + for (int i = 0; i < 0x20; i++) + { + if (expectedLabelHash[i] != db[i]) + { + return null; + } + } + + int keyOffset = 0x20; + while (keyOffset < 0xdf) + { + var value = db[keyOffset++]; + if (value == 1) + { + break; + } + if (value != 0) + { + return null; + } + } + + if (keyOffset + 0x10 > db.Length) return null; + + var key = new byte[0x10]; + Array.Copy(db, keyOffset, key, 0, 0x10); + + return key; + } + + private static void CalculateMgf1AndXor(byte[] masked, byte[] seed) + { + SHA256 hash = SHA256.Create(); + var hashBuf = new byte[seed.Length + 4]; + Array.Copy(seed, hashBuf, seed.Length); + + int maskedSize = masked.Length; + int roundNum = 0; + int pOut = 0; + + while (maskedSize != 0) + { + hashBuf[hashBuf.Length - 4] = (byte)(roundNum >> 24); + hashBuf[hashBuf.Length - 3] = (byte)(roundNum >> 16); + hashBuf[hashBuf.Length - 2] = (byte)(roundNum >> 8); + hashBuf[hashBuf.Length - 1] = (byte)roundNum; + roundNum++; + + byte[] mask = hash.ComputeHash(hashBuf); + int curSize = Math.Min(maskedSize, 0x20); + + for (int i = 0; i < curSize; i++, pOut++) + { + masked[pOut] ^= mask[i]; + } + + maskedSize -= curSize; + } + } + } + + public class RsaKey + { + public byte[] N { get; } + public byte[] E { get; } + public byte[] D { get; } + public BigInteger NInt { get; } + public BigInteger EInt { get; } + public BigInteger DInt { get; } + + public RsaKey(byte[] n, byte[] e, byte[] d) + { + N = n; + E = e; + D = d; + NInt = Crypto.GetBigInteger(n); + EInt = Crypto.GetBigInteger(e); + DInt = Crypto.GetBigInteger(d); + } } } diff --git a/libhac/Keyset.cs b/libhac/Keyset.cs index 8754df45..d3b62014 100644 --- a/libhac/Keyset.cs +++ b/libhac/Keyset.cs @@ -38,12 +38,14 @@ namespace libhac public byte[] nca_hdr_fixed_key_modulus { get; set; } = new byte[0x100]; public byte[] acid_fixed_key_modulus { get; set; } = new byte[0x100]; public byte[] package2_fixed_key_modulus { get; set; } = new byte[0x100]; + public byte[] eticket_rsa_kek { get; set; } = new byte[0x10]; public byte[] secure_boot_key { get; set; } = new byte[0x10]; public byte[] tsec_key { get; set; } = new byte[0x10]; public byte[] device_key { get; set; } = new byte[0x10]; public byte[][] bis_keys { get; set; } = Util.CreateJaggedArray(4, 0x20); public byte[] sd_seed { get; set; } = new byte[0x10]; + public RsaKey eticket_ext_key_rsa { get; set; } public Dictionary TitleKeys { get; } = new Dictionary(new ByteArray128BitComparer()); @@ -187,7 +189,8 @@ namespace libhac new KeyValue("secure_boot_key", 0x10, set => set.secure_boot_key), new KeyValue("tsec_key", 0x10, set => set.tsec_key), new KeyValue("device_key", 0x10, set => set.device_key), - new KeyValue("sd_seed", 0x10, set => set.sd_seed) + new KeyValue("sd_seed", 0x10, set => set.sd_seed), + new KeyValue("eticket_rsa_kek", 0x10, set => set.eticket_rsa_kek ) }; for (int slot = 0; slot < 0x20; slot++) diff --git a/libhac/Ticket.cs b/libhac/Ticket.cs index fe85b527..e3fb9c2d 100644 --- a/libhac/Ticket.cs +++ b/libhac/Ticket.cs @@ -12,7 +12,7 @@ namespace libhac public byte[] Signature { get; } public string Issuer { get; } public byte[] TitleKeyBlock { get; } - public byte TitleKeyType { get; } + public TitleKeyType TitleKeyType { get; } public byte CryptoType { get; } public ulong TicketId { get; } public ulong DeviceId { get; } @@ -21,6 +21,12 @@ namespace libhac public int Length { get; } // Not completely sure about this one public byte[] File { get; } + internal static readonly byte[] LabelHash = + { + 0xE3, 0xB0, 0xC4, 0x42, 0x98, 0xFC, 0x1C, 0x14, 0x9A, 0xFB, 0xF4, 0xC8, 0x99, 0x6F, 0xB9, 0x24, + 0x27, 0xAE, 0x41, 0xE4, 0x64, 0x9B, 0x93, 0x4C, 0xA4, 0x95, 0x99, 0x1B, 0x78, 0x52, 0xB8, 0x55 + }; + public Ticket(BinaryReader reader) { var fileStart = reader.BaseStream.Position; @@ -53,7 +59,7 @@ namespace libhac reader.BaseStream.Position = dataStart + 0x40; TitleKeyBlock = reader.ReadBytes(0x100); reader.BaseStream.Position = dataStart + 0x141; - TitleKeyType = reader.ReadByte(); + TitleKeyType = (TitleKeyType)reader.ReadByte(); reader.BaseStream.Position = dataStart + 0x145; CryptoType = reader.ReadByte(); reader.BaseStream.Position = dataStart + 0x150; @@ -106,6 +112,18 @@ namespace libhac logger?.SetTotal(0); return tickets.Values.ToArray(); } + + public byte[] GetTitleKey(Keyset keyset) + { + if (TitleKeyType == TitleKeyType.Common) + { + var commonKey = new byte[0x10]; + Array.Copy(TitleKeyBlock, commonKey, commonKey.Length); + return commonKey; + } + + return Crypto.DecryptTitleKey(TitleKeyBlock, keyset.eticket_ext_key_rsa); + } } public enum TicketSigType @@ -117,4 +135,10 @@ namespace libhac Rsa2048Sha256, EcdsaSha256 } + + public enum TitleKeyType + { + Common, + Personalized + } }