Extract tickets from nand

This commit is contained in:
Alex Barney 2018-07-09 18:34:46 -05:00
parent d6351cfa4e
commit 5e678ddb06
3 changed files with 204 additions and 40 deletions

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using libhac; using libhac;
using libhac.Nand; using libhac.Nand;
@ -9,16 +10,40 @@ namespace NandReader
{ {
public static void Main(string[] args) 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 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<Ticket>();
var keyset = OpenKeyset(); 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);
}
} }
} }

View file

@ -1,57 +1,76 @@
using System; using System.IO;
using System.IO;
using System.Linq; using System.Linq;
using DiscUtils; using DiscUtils;
using DiscUtils.Fat; using DiscUtils.Fat;
using DiscUtils.Partitions; using DiscUtils.Partitions;
using DiscUtils.Streams; using DiscUtils.Streams;
using libhac.XTSSharp; using libhac.XTSSharp;
using Directory = System.IO.Directory;
namespace libhac.Nand namespace libhac.Nand
{ {
public class 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 disc = new GuidPartitionTable(stream, Geometry.Null);
var partitions = disc.Partitions.Select(x => (GuidPartitionInfo) x).ToArray(); var partitions = disc.Partitions.Select(x => (GuidPartitionInfo)x).ToArray();
var sys = partitions.FirstOrDefault(x => x.Name == "SYSTEM"); ProdInfo = partitions.FirstOrDefault(x => x.Name == "PRODINFO");
var user = partitions.FirstOrDefault(x => x.Name == "USER"); ProdInfoF = partitions.FirstOrDefault(x => x.Name == "PRODINFOF");
var sysStream = sys.Open(); Safe = partitions.FirstOrDefault(x => x.Name == "SAFE");
var xts = XTSSharp.XtsAes128.Create(keyset.bis_keys[2]); System = partitions.FirstOrDefault(x => x.Name == "SYSTEM");
var decStream = new RandomAccessSectorStream(new XtsSectorStream(sysStream, xts, 0x4000, 0), true); 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); FatFileSystem fat = new FatFileSystem(decStream, Ownership.None);
var dirs = fat.GetDirectories("Contents", "*", SearchOption.AllDirectories); return fat;
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);
Directory.CreateDirectory("tickets"); public FatFileSystem OpenSafePartition()
var ticket = new byte[0x400]; {
// brute force it var encStream = Safe.Open();
for (int i = 0; i < save.Length - 16; i += 16) var xts = XtsAes128.Create(Keyset.bis_keys[1]);
{ var decStream = new RandomAccessSectorStream(new XtsSectorStream(encStream, xts, 0x4000, 0), true);
if (save[i] != 0x52 || save[i + 1] != 0x6f || save[i + 2] != 0x6f || save[i + 3] != 0x74) FatFileSystem fat = new FatFileSystem(decStream, Ownership.None);
continue; return fat;
}
Array.Copy(save, i - 0x140, ticket, 0, 0x400); public FatFileSystem OpenSystemPartition()
var titleid = BitConverter.ToString(ticket, 0x2a0, 16).Replace("-", string.Empty); {
File.WriteAllBytes($"tickets/{titleid}.tik", ticket); 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;
}
public FatFileSystem OpenUserPartition()
; {
var s = fat.FileExists("save\\80000000000000E1"); var encStream = User.Open();
var xts = XtsAes128.Create(Keyset.bis_keys[3]);
; var decStream = new RandomAccessSectorStream(new XtsSectorStream(encStream, xts, 0x4000, 0), true);
using (var output = new FileStream("80000000000000E1", FileMode.Create, FileAccess.ReadWrite)) FatFileSystem fat = new FatFileSystem(decStream, Ownership.None);
{ return fat;
f.CopyStream(output, f.Length, logger);
}
;
} }
} }
} }

120
libhac/Ticket.cs Normal file
View file

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