From 60a8a7b2d3569f7b6583e91227a489c968e43994 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sun, 12 Aug 2018 14:45:10 -0600 Subject: [PATCH] Read XCI header --- hactoolnet/Options.cs | 1 + hactoolnet/Program.cs | 11 ++++ libhac/Crypto.cs | 35 ++++++++++-- libhac/Keyset.cs | 8 +-- libhac/Xci.cs | 13 +++++ libhac/XciHeader.cs | 120 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 180 insertions(+), 8 deletions(-) create mode 100644 libhac/Xci.cs create mode 100644 libhac/XciHeader.cs diff --git a/hactoolnet/Options.cs b/hactoolnet/Options.cs index 57c6e375..1313bf30 100644 --- a/hactoolnet/Options.cs +++ b/hactoolnet/Options.cs @@ -36,6 +36,7 @@ namespace hactoolnet Pfs0, Romfs, Nax0, + Xci, SwitchFs, Save } diff --git a/hactoolnet/Program.cs b/hactoolnet/Program.cs index b07d1f4e..8cacc9f5 100644 --- a/hactoolnet/Program.cs +++ b/hactoolnet/Program.cs @@ -45,6 +45,9 @@ namespace hactoolnet case FileType.Save: ProcessSave(ctx); break; + case FileType.Xci: + ProcessXci(ctx); + break; default: throw new ArgumentOutOfRangeException(); } @@ -184,6 +187,14 @@ namespace hactoolnet } } + private static void ProcessXci(Context ctx) + { + using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read)) + { + var xci = new Xci(ctx.Keyset, file); + } + } + private static void OpenKeyset(Context ctx) { var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); diff --git a/libhac/Crypto.cs b/libhac/Crypto.cs index b24ae865..23b285ea 100644 --- a/libhac/Crypto.cs +++ b/libhac/Crypto.cs @@ -8,6 +8,9 @@ namespace libhac { public class Crypto { + internal const int Aes128Size = 0x10; + internal const int Sha256DigestSize = 0x20; + public static void DecryptEcb(byte[] key, byte[] src, int srcIndex, byte[] dest, int destIndex, int length) { using (var aes = Aes.Create()) @@ -29,16 +32,38 @@ namespace libhac public static void DecryptEcb(byte[] key, byte[] src, byte[] dest, int length) => DecryptEcb(key, src, 0, dest, 0, length); + public static void DecryptCbc(byte[] key, byte[] iv, byte[] src, int srcIndex, byte[] dest, int destIndex, int length) + { + using (var aes = Aes.Create()) + { + if (aes == null) throw new CryptographicException("Unable to create AES object"); + aes.Key = key; + aes.IV = iv; + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.None; + var dec = aes.CreateDecryptor(); + using (var ms = new MemoryStream(dest, destIndex, length)) + using (var cs = new CryptoStream(ms, dec, CryptoStreamMode.Write)) + { + cs.Write(src, srcIndex, length); + cs.FlushFinalBlock(); + } + } + } + + public static void DecryptCbc(byte[] key, byte[] iv, byte[] src, byte[] dest, int length) => + DecryptCbc(key, iv, src, 0, dest, 0, length); + public static void GenerateKek(byte[] dst, byte[] src, byte[] masterKey, byte[] kekSeed, byte[] keySeed) { - var kek = new byte[0x10]; - var srcKek = new byte[0x10]; - DecryptEcb(masterKey, kekSeed, kek, 0x10); - DecryptEcb(kek, src, srcKek, 0x10); + var kek = new byte[Aes128Size]; + var srcKek = new byte[Aes128Size]; + DecryptEcb(masterKey, kekSeed, kek, Aes128Size); + DecryptEcb(kek, src, srcKek, Aes128Size); if (keySeed != null) { - DecryptEcb(srcKek, keySeed, dst, 0x10); + DecryptEcb(srcKek, keySeed, dst, Aes128Size); } } diff --git a/libhac/Keyset.cs b/libhac/Keyset.cs index d3b62014..4b78d57f 100644 --- a/libhac/Keyset.cs +++ b/libhac/Keyset.cs @@ -30,8 +30,9 @@ namespace libhac public byte[] sd_card_kek_source { get; set; } = new byte[0x10]; public byte[][] sd_card_key_sources { get; set; } = Util.CreateJaggedArray(2, 0x20); public byte[][] sd_card_key_sources_specific { get; set; } = Util.CreateJaggedArray(2, 0x20); - public byte[] encrypted_header_key { get; set; } = new byte[0x20]; + public byte[] header_key_source { get; set; } = new byte[0x20]; public byte[] header_key { get; set; } = new byte[0x20]; + public byte[] xci_header_key { get; set; } = new byte[0x10]; public byte[][] titlekeks { get; set; } = Util.CreateJaggedArray(0x20, 0x10); public byte[][][] key_area_keys { get; set; } = Util.CreateJaggedArray(0x20, 3, 0x10); public byte[][] sd_card_keys { get; set; } = Util.CreateJaggedArray(2, 0x20); @@ -177,9 +178,10 @@ namespace libhac new KeyValue("key_area_key_system_source", 0x10, set => set.key_area_key_system_source), new KeyValue("titlekek_source", 0x10, set => set.titlekek_source), new KeyValue("header_kek_source", 0x10, set => set.header_kek_source), - new KeyValue("header_key_source", 0x20, set => set.encrypted_header_key), + new KeyValue("header_key_source", 0x20, set => set.header_key_source), new KeyValue("header_key", 0x20, set => set.header_key), - new KeyValue("encrypted_header_key", 0x20, set => set.encrypted_header_key), + new KeyValue("xci_header_key", 0x10, set => set.xci_header_key), + new KeyValue("encrypted_header_key", 0x20, set => set.header_key_source), new KeyValue("package2_key_source", 0x10, set => set.package2_key_source), new KeyValue("sd_card_kek_source", 0x10, set => set.sd_card_kek_source), new KeyValue("sd_card_nca_key_source", 0x20, set => set.sd_card_key_sources[1]), diff --git a/libhac/Xci.cs b/libhac/Xci.cs new file mode 100644 index 00000000..543764bb --- /dev/null +++ b/libhac/Xci.cs @@ -0,0 +1,13 @@ +using System.IO; + +namespace libhac +{ + public class Xci + { + public XciHeader Header { get; set; } + public Xci(Keyset keyset, Stream stream) + { + Header = new XciHeader(keyset, stream); + } + } +} diff --git a/libhac/XciHeader.cs b/libhac/XciHeader.cs new file mode 100644 index 00000000..a88033ee --- /dev/null +++ b/libhac/XciHeader.cs @@ -0,0 +1,120 @@ +using System; +using System.IO; +using System.Text; + +namespace libhac +{ + public class XciHeader + { + private const int SignatureSize = 0x100; + private const string HeaderMagic = "HEAD"; + private const int EncryptedHeaderSize = 0x70; + + public byte[] Signature { get; set; } + public string Magic { get; set; } + public int RomAreaStartPage { get; set; } + public int BackupAreaStartPage { get; set; } + public byte KekIndex { get; set; } + public byte TitleKeyDecIndex { get; set; } + public RomSize RomSize { get; set; } + public byte CardHeaderVersion { get; set; } + public XciFlags Flags { get; set; } + public ulong PackageId { get; set; } + public long ValidDataEndPage { get; set; } + public byte[] AesCbcIv { get; set; } + public long PartitionFsHeaderAddress { get; set; } + public long PartitionFsHeaderSize { get; set; } + public byte[] PartitionFsHeaderHash { get; set; } + public byte[] InitialDataHash { get; set; } + public int SelSec { get; set; } + public int SelT1Key { get; set; } + public int SelKey { get; set; } + public int LimAreaPage { get; set; } + + public ulong FwVersion { get; set; } + public CardClockRate AccCtrl1 { get; set; } + public int Wait1TimeRead { get; set; } + public int Wait2TimeRead { get; set; } + public int Wait1TimeWrite { get; set; } + public int Wait2TimeWrite { get; set; } + public int FwMode { get; set; } + public int UppVersion { get; set; } + public byte[] UppHash { get; set; } + public ulong UppId { get; set; } + + public XciHeader(Keyset keyset, Stream stream) + { + var reader = new BinaryReader(stream, Encoding.Default, true); + Signature = reader.ReadBytes(SignatureSize); + Magic = reader.ReadAscii(4); + if (Magic != HeaderMagic) + { + throw new InvalidDataException("Invalid XCI file: Header magic invalid."); + } + + RomAreaStartPage = reader.ReadInt32(); + BackupAreaStartPage = reader.ReadInt32(); + byte keyIndex = reader.ReadByte(); + KekIndex = (byte)(keyIndex >> 4); + TitleKeyDecIndex = (byte)(keyIndex & 7); + RomSize = (RomSize)reader.ReadByte(); + CardHeaderVersion = reader.ReadByte(); + Flags = (XciFlags)reader.ReadByte(); + PackageId = reader.ReadUInt64(); + ValidDataEndPage = reader.ReadInt64(); + AesCbcIv = reader.ReadBytes(Crypto.Aes128Size); + Array.Reverse(AesCbcIv); + PartitionFsHeaderAddress = reader.ReadInt64(); + PartitionFsHeaderSize = reader.ReadInt64(); + PartitionFsHeaderHash = reader.ReadBytes(Crypto.Sha256DigestSize); + InitialDataHash = reader.ReadBytes(Crypto.Sha256DigestSize); + SelSec = reader.ReadInt32(); + SelT1Key = reader.ReadInt32(); + SelKey = reader.ReadInt32(); + LimAreaPage = reader.ReadInt32(); + + if (keyset.xci_header_key.IsEmpty()) return; + + var encHeader = reader.ReadBytes(EncryptedHeaderSize); + var decHeader = new byte[EncryptedHeaderSize]; + Crypto.DecryptCbc(keyset.xci_header_key, AesCbcIv, encHeader, decHeader, EncryptedHeaderSize); + + reader = new BinaryReader(new MemoryStream(decHeader)); + FwVersion = reader.ReadUInt64(); + AccCtrl1 = (CardClockRate)reader.ReadInt32(); + Wait1TimeRead = reader.ReadInt32(); + Wait2TimeRead = reader.ReadInt32(); + Wait1TimeWrite = reader.ReadInt32(); + Wait2TimeWrite = reader.ReadInt32(); + FwMode = reader.ReadInt32(); + UppVersion = reader.ReadInt32(); + reader.BaseStream.Position += 4; + UppHash = reader.ReadBytes(8); + UppId = reader.ReadUInt64(); + } + } + + public enum RomSize + { + Size1Gb = 0xFA, + Size2Gb = 0xF8, + Size4Gb = 0xF0, + Size8Gb = 0xE0, + Size16Gb = 0xE1, + Size32Gb = 0xE2 + } + + [Flags] + public enum XciFlags + { + AutoBoot = 1 << 0, + HistoryErase = 1 << 1, + RepairTool = 1 << 2 + } + + public enum CardClockRate + { + ClockRate25 = 10551312, + ClockRate50 + } +}