diff --git a/NandReader/Program.cs b/NandReader/Program.cs index fdba62ab..2187a18e 100644 --- a/NandReader/Program.cs +++ b/NandReader/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using libhac; using libhac.Nand; @@ -9,16 +10,40 @@ namespace NandReader { public static void Main(string[] args) { - DumpTickets(); + if (args.Length != 1) + { + Console.WriteLine("Usage: NandReader raw_nand_dump_file"); + return; + } + DumpTickets(args[0]); } - private static void DumpTickets() + private static void DumpTickets(string nandFile) { using (var logger = new ProgressBar()) - using (var stream = new FileStream(@"F:\rawnand.bin", FileMode.Open, FileAccess.Read)) + using (var stream = new FileStream(nandFile, FileMode.Open, FileAccess.Read)) { + var tickets = new List(); var keyset = OpenKeyset(); - var nand = new Nand(stream, keyset, logger); + 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"); + + Directory.CreateDirectory("tickets"); + foreach (var ticket in tickets) + { + var filename = Path.Combine("tickets", $"{ticket.RightsId.ToHexString()}.tik"); + File.WriteAllBytes(filename, ticket.File); + } } } diff --git a/libhac.Nand/Nand.cs b/libhac.Nand/Nand.cs index 124f3cc6..bcb8b8e0 100644 --- a/libhac.Nand/Nand.cs +++ b/libhac.Nand/Nand.cs @@ -1,57 +1,76 @@ -using System; -using System.IO; +using System.IO; using System.Linq; using DiscUtils; using DiscUtils.Fat; using DiscUtils.Partitions; using DiscUtils.Streams; using libhac.XTSSharp; -using Directory = System.IO.Directory; namespace libhac.Nand { public class Nand { - public Nand(Stream stream, Keyset keyset, IProgressReport logger) + private GuidPartitionInfo ProdInfo { get; } + private GuidPartitionInfo ProdInfoF { get; } + private GuidPartitionInfo Safe { get; } + private GuidPartitionInfo System { get; } + private GuidPartitionInfo User { get; } + private Keyset Keyset { get; } + + public Nand(Stream stream, Keyset keyset) { var disc = new GuidPartitionTable(stream, Geometry.Null); - var partitions = disc.Partitions.Select(x => (GuidPartitionInfo) x).ToArray(); - var sys = partitions.FirstOrDefault(x => x.Name == "SYSTEM"); - var user = partitions.FirstOrDefault(x => x.Name == "USER"); - var sysStream = sys.Open(); - var xts = XTSSharp.XtsAes128.Create(keyset.bis_keys[2]); - var decStream = new RandomAccessSectorStream(new XtsSectorStream(sysStream, xts, 0x4000, 0), true); + var partitions = disc.Partitions.Select(x => (GuidPartitionInfo)x).ToArray(); + ProdInfo = partitions.FirstOrDefault(x => x.Name == "PRODINFO"); + ProdInfoF = partitions.FirstOrDefault(x => x.Name == "PRODINFOF"); + Safe = partitions.FirstOrDefault(x => x.Name == "SAFE"); + System = partitions.FirstOrDefault(x => x.Name == "SYSTEM"); + User = partitions.FirstOrDefault(x => x.Name == "USER"); + Keyset = keyset; + } + public Stream OpenProdInfo() + { + var encStream = ProdInfo.Open(); + var xts = XtsAes128.Create(Keyset.bis_keys[0]); + var decStream = new RandomAccessSectorStream(new XtsSectorStream(encStream, xts, 0x4000, 0), true); + return decStream; + } + + public FatFileSystem OpenProdInfoF() + { + var encStream = ProdInfoF.Open(); + var xts = XtsAes128.Create(Keyset.bis_keys[0]); + var decStream = new RandomAccessSectorStream(new XtsSectorStream(encStream, xts, 0x4000, 0), true); FatFileSystem fat = new FatFileSystem(decStream, Ownership.None); - var dirs = fat.GetDirectories("Contents", "*", SearchOption.AllDirectories); - var files = fat.GetFiles("save"); - var f = fat.OpenFile("save\\80000000000000E2", FileMode.Open, FileAccess.Read); - var save = new byte[f.Length]; - f.Read(save, 0, save.Length); + return fat; + } - Directory.CreateDirectory("tickets"); - var ticket = new byte[0x400]; - // brute force it - for (int i = 0; i < save.Length - 16; i += 16) - { - if (save[i] != 0x52 || save[i + 1] != 0x6f || save[i + 2] != 0x6f || save[i + 3] != 0x74) - continue; + public FatFileSystem OpenSafePartition() + { + var encStream = Safe.Open(); + var xts = XtsAes128.Create(Keyset.bis_keys[1]); + var decStream = new RandomAccessSectorStream(new XtsSectorStream(encStream, xts, 0x4000, 0), true); + FatFileSystem fat = new FatFileSystem(decStream, Ownership.None); + return fat; + } - Array.Copy(save, i - 0x140, ticket, 0, 0x400); - var titleid = BitConverter.ToString(ticket, 0x2a0, 16).Replace("-", string.Empty); - File.WriteAllBytes($"tickets/{titleid}.tik", ticket); - } + public FatFileSystem OpenSystemPartition() + { + var encStream = System.Open(); + var xts = XtsAes128.Create(Keyset.bis_keys[2]); + var decStream = new RandomAccessSectorStream(new XtsSectorStream(encStream, xts, 0x4000, 0), true); + FatFileSystem fat = new FatFileSystem(decStream, Ownership.None); + return fat; + } - - ; - var s = fat.FileExists("save\\80000000000000E1"); - - ; - using (var output = new FileStream("80000000000000E1", FileMode.Create, FileAccess.ReadWrite)) - { - f.CopyStream(output, f.Length, logger); - } - ; + public FatFileSystem OpenUserPartition() + { + var encStream = User.Open(); + var xts = XtsAes128.Create(Keyset.bis_keys[3]); + var decStream = new RandomAccessSectorStream(new XtsSectorStream(encStream, xts, 0x4000, 0), true); + FatFileSystem fat = new FatFileSystem(decStream, Ownership.None); + return fat; } } } diff --git a/libhac/Ticket.cs b/libhac/Ticket.cs new file mode 100644 index 00000000..fe85b527 --- /dev/null +++ b/libhac/Ticket.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace libhac +{ + public class Ticket + { + public TicketSigType SignatureType { get; } + public byte[] Signature { get; } + public string Issuer { get; } + public byte[] TitleKeyBlock { get; } + public byte TitleKeyType { get; } + public byte CryptoType { get; } + public ulong TicketId { get; } + public ulong DeviceId { get; } + public byte[] RightsId { get; } + public uint AccountId { get; } + public int Length { get; } // Not completely sure about this one + public byte[] File { get; } + + public Ticket(BinaryReader reader) + { + var fileStart = reader.BaseStream.Position; + SignatureType = (TicketSigType)reader.ReadUInt32(); + + switch (SignatureType) + { + case TicketSigType.Rsa4096Sha1: + case TicketSigType.Rsa4096Sha256: + Signature = reader.ReadBytes(0x200); + reader.BaseStream.Position += 0x3c; + break; + case TicketSigType.Rsa2048Sha1: + case TicketSigType.Rsa2048Sha256: + Signature = reader.ReadBytes(0x100); + reader.BaseStream.Position += 0x3c; + break; + case TicketSigType.EcdsaSha1: + case TicketSigType.EcdsaSha256: + Signature = reader.ReadBytes(0x3c); + reader.BaseStream.Position += 0x40; + break; + default: + throw new ArgumentOutOfRangeException(); + } + + var dataStart = reader.BaseStream.Position; + + Issuer = reader.ReadUtf8Z(); + reader.BaseStream.Position = dataStart + 0x40; + TitleKeyBlock = reader.ReadBytes(0x100); + reader.BaseStream.Position = dataStart + 0x141; + TitleKeyType = reader.ReadByte(); + reader.BaseStream.Position = dataStart + 0x145; + CryptoType = reader.ReadByte(); + reader.BaseStream.Position = dataStart + 0x150; + TicketId = reader.ReadUInt64(); + DeviceId = reader.ReadUInt64(); + RightsId = reader.ReadBytes(0x10); + AccountId = reader.ReadUInt32(); + reader.BaseStream.Position = dataStart + 0x178; + Length = reader.ReadInt32(); + + reader.BaseStream.Position = fileStart; + File = reader.ReadBytes(Length); + } + + public static Ticket[] SearchTickets(Stream file, IProgressReport logger = null) + { + var reader = new BinaryReader(file, Encoding.Default, true); + file.Position += 0x140; + var tickets = new Dictionary(); + + logger?.SetTotal(file.Length); + + // Ticket starts occur at multiples of 0x400 + while (file.Position + 0x800 < file.Length) + { + var position = file.Position; + logger?.Report(position); + + if (reader.ReadUInt32() != 0x746f6f52) // Root + { + file.Position = position + 0x400; + continue; + } + + file.Position -= 0x144; + var sigType = (TicketSigType)reader.ReadUInt32(); + if (sigType < TicketSigType.Rsa4096Sha1 || sigType > TicketSigType.EcdsaSha256) + { + file.Position = position + 0x400; + continue; + } + + file.Position -= 4; + + var ticket = new Ticket(reader); + tickets[ticket.RightsId.ToHexString()] = ticket; + file.Position = position + 0x400; + } + + logger?.SetTotal(0); + return tickets.Values.ToArray(); + } + } + + public enum TicketSigType + { + Rsa4096Sha1 = 0x10000, + Rsa2048Sha1, + EcdsaSha1, + Rsa4096Sha256, + Rsa2048Sha256, + EcdsaSha256 + } +}