Read XCI header

This commit is contained in:
Alex Barney 2018-08-12 14:45:10 -06:00
parent 55031755a8
commit 60a8a7b2d3
6 changed files with 180 additions and 8 deletions

View file

@ -36,6 +36,7 @@ namespace hactoolnet
Pfs0,
Romfs,
Nax0,
Xci,
SwitchFs,
Save
}

View file

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

View file

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

View file

@ -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<byte[][]>(2, 0x20);
public byte[][] sd_card_key_sources_specific { get; set; } = Util.CreateJaggedArray<byte[][]>(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<byte[][]>(0x20, 0x10);
public byte[][][] key_area_keys { get; set; } = Util.CreateJaggedArray<byte[][][]>(0x20, 3, 0x10);
public byte[][] sd_card_keys { get; set; } = Util.CreateJaggedArray<byte[][]>(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]),

13
libhac/Xci.cs Normal file
View file

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

120
libhac/XciHeader.cs Normal file
View file

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