mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Extract tickets from nand
This commit is contained in:
parent
d6351cfa4e
commit
5e678ddb06
3 changed files with 204 additions and 40 deletions
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
120
libhac/Ticket.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue