Get title keys from a nand dump

This commit is contained in:
Alex Barney 2018-07-13 10:25:39 -05:00
parent f1b660b95f
commit c44659ca17
6 changed files with 250 additions and 20 deletions

View File

@ -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<Ticket>();
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<Ticket>();
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
}
}
}

View File

@ -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)
{

View File

@ -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) { }
/// <summary>
/// Creates a new stream
/// </summary>
/// <param name="baseStream">The base stream</param>
/// <param name="key">The decryption key</param>
/// <param name="counter">The intial counter</param>
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]);
}
/// <summary>
/// Creates a new stream
/// </summary>

View File

@ -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);
}
}
}

View File

@ -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<byte[][]>(4, 0x20);
public byte[] sd_seed { get; set; } = new byte[0x10];
public RsaKey eticket_ext_key_rsa { get; set; }
public Dictionary<byte[], byte[]> TitleKeys { get; } = new Dictionary<byte[], byte[]>(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++)

View File

@ -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
}
}