diff --git a/build/CodeGen/results.csv b/build/CodeGen/results.csv index 95a37997..fa74307a 100644 --- a/build/CodeGen/results.csv +++ b/build/CodeGen/results.csv @@ -467,3 +467,8 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary 428,1031,,InvalidPackage2MetaPayloadsOverlap, 428,1032,,InvalidPackage2MetaEntryPointNotFound, 428,1033,,InvalidPackage2PayloadCorrupted, + +428,1040,1059,InvalidPackage1, +428,1041,,InvalidPackage1SectionSize, +428,1042,,InvalidPackage1MarikoBodySize, +428,1043,,InvalidPackage1Pk11Size, diff --git a/src/LibHac/Boot/Package1.cs b/src/LibHac/Boot/Package1.cs new file mode 100644 index 00000000..f8e04200 --- /dev/null +++ b/src/LibHac/Boot/Package1.cs @@ -0,0 +1,547 @@ +using LibHac.Common; +using LibHac.Fs; +using LibHac.FsSystem; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using LibHac.Diag; + +namespace LibHac.Boot +{ + [StructLayout(LayoutKind.Explicit, Size = 0x170)] + public struct Package1MarikoOemHeader + { + [FieldOffset(0x000)] private byte _aesMac; + [FieldOffset(0x010)] private byte _rsaSig; + [FieldOffset(0x110)] private byte _salt; + [FieldOffset(0x130)] private byte _hash; + [FieldOffset(0x150)] public int Version; + [FieldOffset(0x154)] public int Size; + [FieldOffset(0x158)] public int LoadAddress; + [FieldOffset(0x15C)] public int EntryPoint; + [FieldOffset(0x160)] private byte _reserved; + + public ReadOnlySpan AesMac => SpanHelpers.CreateSpan(ref _aesMac, 0x10); + public ReadOnlySpan RsaSig => SpanHelpers.CreateSpan(ref _rsaSig, 0x100); + public ReadOnlySpan Salt => SpanHelpers.CreateSpan(ref _salt, 0x20); + public ReadOnlySpan Hash => SpanHelpers.CreateSpan(ref _hash, 0x20); + public ReadOnlySpan Reserved => SpanHelpers.CreateSpan(ref _reserved, 0x10); + } + + [StructLayout(LayoutKind.Explicit, Size = 0x20)] + public struct Package1MetaData + { + [FieldOffset(0x00)] public uint LoaderHash; + [FieldOffset(0x04)] public uint SecureMonitorHash; + [FieldOffset(0x08)] public uint BootloaderHash; + [FieldOffset(0x10)] private byte _buildDate; + [FieldOffset(0x1E)] public byte KeyGeneration; + [FieldOffset(0x1F)] public byte Version; + + public U8Span BuildDate => new U8Span(SpanHelpers.CreateSpan(ref _buildDate, 0xE)); + public ReadOnlySpan Iv => SpanHelpers.CreateSpan(ref _buildDate, 0x10); + } + + [StructLayout(LayoutKind.Explicit, Size = 0x20)] + public struct Package1Stage1Footer + { + [FieldOffset(0x00)] public int Pk11Size; + [FieldOffset(0x10)] private byte _iv; + + public ReadOnlySpan Iv => SpanHelpers.CreateSpan(ref _iv, 0x10); + } + + [StructLayout(LayoutKind.Explicit, Size = 0x20)] + public struct Package1Pk11Header + { + public const uint ExpectedMagic = 0x31314B50; // PK11 + + [FieldOffset(0x00)] public uint Magic; + [FieldOffset(0x04)] public int WarmBootSize; + [FieldOffset(0x08)] public int WarmBootOffset; + [FieldOffset(0x10)] public int BootloaderSize; + [FieldOffset(0x14)] public int BootloaderOffset; + [FieldOffset(0x18)] public int SecureMonitorSize; + [FieldOffset(0x1C)] public int SecureMonitorOffset; + } + + public enum Package1Section + { + Bootloader, + SecureMonitor, + WarmBoot + } + + public class Package1 + { + private const int LegacyStage1Size = 0x4000; + private const int ModernStage1Size = 0x7000; + private const int MarikoWarmBootPlainTextSectionSize = 0x330; + + private IStorage BaseStorage { get; set; } + private Keyset Keyset { get; set; } + + public bool IsModern { get; private set; } + public bool IsMariko { get; private set; } + + /// + /// Returns if the package1 can be decrypted. + /// + public bool IsDecrypted { get; private set; } + public byte KeyRevision { get; private set; } + + public int Pk11Size { get; private set; } + private IStorage Pk11Storage { get; set; } + private IStorage BodyStorage { get; set; } + + private Package1MarikoOemHeader _marikoOemHeader; + private Package1MetaData _metaData; + private Package1Stage1Footer _stage1Footer; + private Package1Pk11Header _pk11Header; + private Buffer16 _pk11Mac; + + public ref readonly Package1MarikoOemHeader MarikoOemHeader => ref _marikoOemHeader; + public ref readonly Package1MetaData MetaData => ref _metaData; + public ref readonly Package1Stage1Footer Stage1Footer => ref _stage1Footer; + public ref readonly Package1Pk11Header Pk11Header => ref _pk11Header; + public ref readonly Buffer16 Pk11Mac => ref _pk11Mac; + + public Result Initialize(Keyset keyset, IStorage storage) + { + Keyset = keyset; + BaseStorage = storage; + + // Read what might be a mariko header and check if it actually is a mariko header + Result rc = BaseStorage.Read(0, SpanHelpers.AsByteSpan(ref _marikoOemHeader)); + if (rc.IsFailure()) return rc; + + IsMariko = IsMarikoImpl(); + + if (IsMariko) + { + rc = InitMarikoBodyStorage(); + if (rc.IsFailure()) return rc; + } + else + { + BodyStorage = BaseStorage; + rc = BodyStorage.Read(0, SpanHelpers.AsByteSpan(ref _metaData)); + if (rc.IsFailure()) return rc; + } + + rc = ParseStage1(); + if (rc.IsFailure()) return rc; + + rc = ReadPk11Header(); + if (rc.IsFailure()) return rc; + + if (!IsMariko && IsModern) + { + rc = ReadModernEristaMac(); + if (rc.IsFailure()) return rc; + } + + rc = SetPk11Storage(); + if (rc.IsFailure()) return rc; + + // Make sure the PK11 section sizes add up to the expected size + if (IsDecrypted && !VerifyPk11Sizes()) + { + return ResultLibHac.InvalidPackage1Pk11Size.Log(); + } + + return Result.Success; + } + + /// + /// Read the encrypted section of a Mariko Package1 and try to decrypt it. + /// + /// : The operation was successful.
+ /// : The package1 is + /// too small or the size in the OEM header is incorrect.
+ private Result InitMarikoBodyStorage() + { + // Body must be large enough to hold at least one metadata struct + if (MarikoOemHeader.Size < Unsafe.SizeOf()) + return ResultLibHac.InvalidPackage1MarikoBodySize.Log(); + + // Verify the body storage size is not smaller than the size in the header + Result rc = BaseStorage.GetSize(out long totalSize); + if (rc.IsFailure()) return rc; + + long bodySize = totalSize - Unsafe.SizeOf(); + if (bodySize < MarikoOemHeader.Size) + return ResultLibHac.InvalidPackage1MarikoBodySize.Log(); + + // Create body SubStorage and metadata buffers + var bodySubStorage = new SubStorage(BaseStorage, Unsafe.SizeOf(), bodySize); + + Span metaData = stackalloc Package1MetaData[2]; + Span metaData1 = SpanHelpers.AsByteSpan(ref metaData[0]); + Span metaData2 = SpanHelpers.AsByteSpan(ref metaData[1]); + + // Read both the plaintext metadata and encrypted metadata + rc = bodySubStorage.Read(0, MemoryMarshal.Cast(metaData)); + if (rc.IsFailure()) return rc; + + // Set the body storage and decrypted metadata + _metaData = metaData[0]; + BodyStorage = bodySubStorage; + + // The plaintext metadata is followed by an encrypted copy + // If these two match then the body is already decrypted + IsDecrypted = metaData1.SequenceEqual(metaData2); + + if (IsDecrypted) + { + return Result.Success; + } + + // If encrypted, check if the body can be decrypted + Crypto.Aes.DecryptCbc128(metaData2, metaData2, Keyset.MarikoBek, _metaData.Iv); + IsDecrypted = metaData2.SequenceEqual(SpanHelpers.AsByteSpan(ref _metaData)); + + // Get a decrypted body storage if we have the correct key + if (IsDecrypted) + { + var decStorage = new AesCbcStorage(bodySubStorage, Keyset.MarikoBek, _metaData.Iv, true); + BodyStorage = new CachedStorage(decStorage, 0x4000, 1, true); + } + + return Result.Success; + } + + private Result ParseStage1() + { + // Erista package1ldr is stored unencrypted, so we can always directly read the size + // field at the end of package1ldr. + + // Mariko package1ldr is stored encrypted. If we're able to decrypt it, + // directly read the size field at the end of package1ldr. + + IsModern = !IsLegacyImpl(); + int stage1Size = IsModern ? ModernStage1Size : LegacyStage1Size; + + if (IsMariko && !IsDecrypted) + { + // If we're not able to decrypt the Mariko package1ldr, calculate the size by subtracting + // the known package1ldr size from the total size in the OEM header. + Pk11Size = MarikoOemHeader.Size - stage1Size; + return Result.Success; + } + + // Read the package1ldr footer + int footerOffset = stage1Size - Unsafe.SizeOf(); + + Result rc = BodyStorage.Read(footerOffset, SpanHelpers.AsByteSpan(ref _stage1Footer)); + if (rc.IsFailure()) return rc; + + // Get the PK11 size from the field in the unencrypted stage 1 footer + Pk11Size = _stage1Footer.Pk11Size; + + return Result.Success; + } + + private Result ReadPk11Header() + { + int pk11Offset = IsModern ? ModernStage1Size : LegacyStage1Size; + + return BodyStorage.Read(pk11Offset, SpanHelpers.AsByteSpan(ref _pk11Header)); + } + + private Result ReadModernEristaMac() + { + return BaseStorage.Read(ModernStage1Size + Pk11Size, _pk11Mac.Bytes); + } + + private Result SetPk11Storage() + { + // Read the PK11 header from the body storage + int pk11Offset = IsModern ? ModernStage1Size : LegacyStage1Size; + + Result rc = BodyStorage.Read(pk11Offset, SpanHelpers.AsByteSpan(ref _pk11Header)); + if (rc.IsFailure()) return rc; + + // Check if PK11 is already decrypted, creating the PK11 storage if it is + IsDecrypted = _pk11Header.Magic == Package1Pk11Header.ExpectedMagic; + + if (IsDecrypted) + { + Pk11Storage = new SubStorage(BodyStorage, pk11Offset, Pk11Size); + return Result.Success; + } + + var encPk11Storage = new SubStorage(BodyStorage, pk11Offset, Pk11Size); + + // See if we have an Erista package1 key that can decrypt this PK11 + if (!IsMariko && TryFindEristaKeyRevision()) + { + IsDecrypted = true; + IStorage decPk11Storage; + + if (IsModern) + { + decPk11Storage = new AesCbcStorage(encPk11Storage, Keyset.Package1Keys[KeyRevision], + _stage1Footer.Iv, true); + } + else + { + decPk11Storage = new Aes128CtrStorage(encPk11Storage, Keyset.Package1Keys[KeyRevision], + _stage1Footer.Iv.ToArray(), true); + } + + Pk11Storage = new CachedStorage(decPk11Storage, 0x4000, 1, true); + + return Result.Success; + } + + // We can't decrypt the PK11. Set Pk11Storage to the encrypted PK11 storage + Pk11Storage = encPk11Storage; + return Result.Success; + } + + private delegate void Decryptor(ReadOnlySpan input, Span output, ReadOnlySpan key, + ReadOnlySpan iv, bool preferDotNetCrypto = false); + + private bool TryFindEristaKeyRevision() + { + // Package1 has no indication of which key it's encrypted with, + // so we must test each known key to find one that works + + var decHeader = new Package1Pk11Header(); + + int start = IsModern ? 6 : 0; + int end = IsModern ? 0x20 : 6; + Decryptor decryptor = IsModern ? Crypto.Aes.DecryptCbc128 : (Decryptor)Crypto.Aes.DecryptCtr128; + + for (int i = start; i < end; i++) + { + decryptor(SpanHelpers.AsByteSpan(ref _pk11Header), SpanHelpers.AsByteSpan(ref decHeader), + Keyset.Package1Keys[i], _stage1Footer.Iv); + + if (decHeader.Magic == Package1Pk11Header.ExpectedMagic) + { + KeyRevision = (byte)i; + _pk11Header = decHeader; + return true; + } + } + + return false; + } + + private bool VerifyPk11Sizes() + { + Assert.AssertTrue(IsDecrypted); + + int pk11Size = Unsafe.SizeOf() + GetSectionSize(Package1Section.WarmBoot) + + GetSectionSize(Package1Section.Bootloader) + GetSectionSize(Package1Section.SecureMonitor); + + pk11Size = Utilities.AlignUp(pk11Size, 0x10); + + return pk11Size == Pk11Size; + } + + // MetaData must be read first + private bool IsLegacyImpl() + { + return _metaData.Version < 0xE || StringUtils.Compare(_metaData.BuildDate, LegacyDateCutoff) < 0; + } + + // MarikoOemHeader must be read first + private bool IsMarikoImpl() + { + return MarikoOemHeader.AesMac.IsEmpty() && MarikoOemHeader.Reserved.IsEmpty(); + } + + /// + /// Opens an of the entire package1, decrypting any encrypted data. + /// + /// If the package1 can be decrypted, an + /// of the package1, . + public IStorage OpenDecryptedPackage1Storage() + { + if (!IsDecrypted) + return null; + + var storages = new List(); + + if (IsMariko) + { + int metaSize = Unsafe.SizeOf(); + + // The metadata at the start of the body is unencrypted, so don't take its data from the decrypted + // body storage + storages.Add(new SubStorage(BaseStorage, 0, Unsafe.SizeOf() + metaSize)); + storages.Add(new SubStorage(BodyStorage, metaSize, _marikoOemHeader.Size - metaSize)); + } + else + { + int stage1Size = IsModern ? ModernStage1Size : LegacyStage1Size; + + storages.Add(new SubStorage(BaseStorage, 0, stage1Size)); + storages.Add(Pk11Storage); + + if (IsModern) + { + storages.Add(new MemoryStorage(_pk11Mac.Bytes.ToArray())); + } + } + + return new ConcatenationStorage(storages, true); + } + + /// + /// Opens an of the warmboot section. + /// + /// If the section can be decrypted, an of the + /// warmboot section; otherwise, . + public IStorage OpenWarmBootStorage() => OpenSectionStorage(Package1Section.WarmBoot); + + /// + /// Opens an of the bootloader section. + /// + /// If the section can be decrypted, an of the + /// bootloader section; otherwise, . + public IStorage OpenNxBootloaderStorage() => OpenSectionStorage(Package1Section.Bootloader); + + /// + /// Opens an of the secure monitor section. + /// + /// If the section can be decrypted, an of the + /// secure monitor section; otherwise, . + public IStorage OpenSecureMonitorStorage() => OpenSectionStorage(Package1Section.SecureMonitor); + + /// + /// Opens an for the specified . + /// + /// The section to open. + /// If the section can be decrypted, an of that + /// section; otherwise, . + public IStorage OpenSectionStorage(Package1Section sectionType) + { + if (!IsDecrypted) + return null; + + int offset = Unsafe.SizeOf() + GetSectionOffset(sectionType); + int size = GetSectionSize(sectionType); + + return new SubStorage(Pk11Storage, offset, size); + } + + + public IStorage OpenDecryptedWarmBootStorage() + { + if (!IsDecrypted) + return null; + + IStorage warmBootStorage = OpenWarmBootStorage(); + + // Only Mariko warmboot storage is encrypted + if (!IsMariko) + { + return warmBootStorage; + } + + int size = GetSectionSize(Package1Section.WarmBoot); + int encryptedSectionSize = size - MarikoWarmBootPlainTextSectionSize; + + var plainTextSection = new SubStorage(warmBootStorage, 0, MarikoWarmBootPlainTextSectionSize); + var encryptedSubStorage = + new SubStorage(warmBootStorage, MarikoWarmBootPlainTextSectionSize, encryptedSectionSize); + + var zeroIv = new Buffer16(); + IStorage decryptedSection = new AesCbcStorage(encryptedSubStorage, Keyset.MarikoBek, zeroIv.Bytes, true); + + decryptedSection = new CachedStorage(decryptedSection, 0x200, 1, true); + + return new ConcatenationStorage(new List { plainTextSection, decryptedSection }, true); + } + + public int GetSectionSize(Package1Section sectionType) + { + if (!IsDecrypted) + return 0; + + return sectionType switch + { + Package1Section.Bootloader => _pk11Header.BootloaderSize, + Package1Section.SecureMonitor => _pk11Header.SecureMonitorSize, + Package1Section.WarmBoot => _pk11Header.WarmBootSize, + _ => 0 + }; + } + + public int GetSectionOffset(Package1Section sectionType) + { + if (!IsDecrypted) + return 0; + + switch (GetSectionIndex(sectionType)) + { + case 0: + return 0; + case 1: + return GetSectionSize(GetSectionType(0)); + case 2: + return GetSectionSize(GetSectionType(0)) + GetSectionSize(GetSectionType(1)); + default: + return -1; + } + } + + private int GetSectionIndex(Package1Section sectionType) + { + if (_metaData.Version >= 0x07) + { + return sectionType switch + { + Package1Section.Bootloader => 0, + Package1Section.SecureMonitor => 1, + Package1Section.WarmBoot => 2, + _ => -1, + }; + } + + if (_metaData.Version >= 0x02) + { + return sectionType switch + { + Package1Section.Bootloader => 1, + Package1Section.SecureMonitor => 2, + Package1Section.WarmBoot => 0, + _ => -1, + }; + } + + return sectionType switch + { + Package1Section.Bootloader => 1, + Package1Section.SecureMonitor => 0, + Package1Section.WarmBoot => 2, + _ => -1, + }; + } + + private Package1Section GetSectionType(int index) + { + if (GetSectionIndex(Package1Section.Bootloader) == index) + return Package1Section.Bootloader; + + if (GetSectionIndex(Package1Section.SecureMonitor) == index) + return Package1Section.SecureMonitor; + + if (GetSectionIndex(Package1Section.WarmBoot) == index) + return Package1Section.WarmBoot; + + return (Package1Section)(-1); + } + + private static ReadOnlySpan LegacyDateCutoff => // 20181107 + new[] + { + (byte) '2', (byte) '0', (byte) '1', (byte) '8', (byte) '1', (byte) '1', (byte) '0', (byte) '7' + }; + } +} diff --git a/src/LibHac/Common/ResultLibHac.cs b/src/LibHac/Common/ResultLibHac.cs index f93961b7..7d783f88 100644 --- a/src/LibHac/Common/ResultLibHac.cs +++ b/src/LibHac/Common/ResultLibHac.cs @@ -83,5 +83,14 @@ namespace LibHac.Common public static Result.Base InvalidPackage2MetaEntryPointNotFound => new Result.Base(ModuleLibHac, 1032); /// Error code: 2428-1033; Inner value: 0x813ac public static Result.Base InvalidPackage2PayloadCorrupted => new Result.Base(ModuleLibHac, 1033); + + /// Error code: 2428-1040; Range: 1040-1059; Inner value: 0x821ac + public static Result.Base InvalidPackage1 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1040, 1059); } + /// Error code: 2428-1041; Inner value: 0x823ac + public static Result.Base InvalidPackage1SectionSize => new Result.Base(ModuleLibHac, 1041); + /// Error code: 2428-1042; Inner value: 0x825ac + public static Result.Base InvalidPackage1MarikoBodySize => new Result.Base(ModuleLibHac, 1042); + /// Error code: 2428-1043; Inner value: 0x827ac + public static Result.Base InvalidPackage1Pk11Size => new Result.Base(ModuleLibHac, 1043); } } diff --git a/src/LibHac/FsSystem/AesCbcStorage.cs b/src/LibHac/FsSystem/AesCbcStorage.cs new file mode 100644 index 00000000..0e27753b --- /dev/null +++ b/src/LibHac/FsSystem/AesCbcStorage.cs @@ -0,0 +1,83 @@ +using LibHac.Crypto; +using LibHac.Fs; +using System; + +namespace LibHac.FsSystem +{ + public class AesCbcStorage : SectorStorage + { + private const int BlockSize = 0x10; + + private readonly byte[] _key; + private readonly byte[] _iv; + + private readonly long _size; + + public AesCbcStorage(IStorage baseStorage, ReadOnlySpan key, ReadOnlySpan iv, + bool leaveOpen) : base(baseStorage, BlockSize, leaveOpen) + { + if (key.Length != BlockSize) throw new ArgumentException(nameof(key), $"Key must be {BlockSize} bytes long"); + if (iv.Length != BlockSize) throw new ArgumentException(nameof(iv), $"Counter must be {BlockSize} bytes long"); + + _key = key.ToArray(); + _iv = iv.ToArray(); + + baseStorage.GetSize(out _size).ThrowIfFailure(); + } + + protected override Result DoRead(long offset, Span destination) + { + if (!IsRangeValid(offset, destination.Length, _size)) + return ResultFs.OutOfRange.Log(); + + Result rc = base.DoRead(offset, destination); + if (rc.IsFailure()) return rc; + + rc = GetDecryptor(out ICipher cipher, offset); + if (rc.IsFailure()) return rc; + + cipher.Transform(destination, destination); + + return Result.Success; + } + + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + return ResultFs.UnsupportedOperation.Log(); + } + + protected override Result DoFlush() + { + return Result.Success; + } + + protected override Result DoSetSize(long size) + { + return ResultFs.UnsupportedOperation.Log(); + } + + private Result GetDecryptor(out ICipher decryptor, long offset) + { + if (offset == 0) + { + // Use the IV directly + decryptor = Aes.CreateCbcDecryptor(_key, _iv); + return Result.Success; + } + + decryptor = default; + + // Need to get the output of the previous block + Span prevBlock = stackalloc byte[BlockSize]; + Result rc = BaseStorage.Read(offset - BlockSize, prevBlock); + if (rc.IsFailure()) return rc; + + ICipher tmpDecryptor = Aes.CreateCbcDecryptor(_key, _iv); + + tmpDecryptor.Transform(prevBlock, prevBlock); + + decryptor = tmpDecryptor; + return Result.Success; + } + } +} diff --git a/src/LibHac/FsSystem/CachedStorage.cs b/src/LibHac/FsSystem/CachedStorage.cs index 945a82b3..5f6390f3 100644 --- a/src/LibHac/FsSystem/CachedStorage.cs +++ b/src/LibHac/FsSystem/CachedStorage.cs @@ -145,7 +145,7 @@ namespace LibHac.FsSystem Blocks.AddFirst(node); } - return node.Value; + return node!.Value; } // An inactive node shouldn't be null, but we'll fix it if it is anyway diff --git a/src/LibHac/Keyset.cs b/src/LibHac/Keyset.cs index 20006594..6cb05822 100644 --- a/src/LibHac/Keyset.cs +++ b/src/LibHac/Keyset.cs @@ -59,6 +59,8 @@ namespace LibHac public byte[] BisKekSource { get; } = new byte[0x10]; public byte[][] BisKeySource { get; } = Utilities.CreateJaggedByteArray(4, 0x20); public byte[] SslRsaKek { get; } = new byte[0x10]; + public byte[] MarikoBek { get; } = new byte[0x10]; + public byte[] MarikoKek { get; } = new byte[0x10]; // Device-specific keys public byte[] SecureBootKey { get; } = new byte[0x10]; @@ -697,7 +699,7 @@ namespace LibHac if (keySlot.Group > currentGroup) { - if (currentGroup > 0) sb.AppendLine(); + sb.AppendLine(); currentGroup = keySlot.Group; } @@ -760,6 +762,9 @@ namespace LibHac { new KeyValue("keyblob_mac_key_source", 0x10, 0, set => set.KeyblobMacKeySource), + new KeyValue("mariko_bek", 0x10, 32, set => set.MarikoBek), + new KeyValue("mariko_kek", 0x10, 32, set => set.MarikoKek), + new KeyValue("master_key_source", 0x10, 60, set => set.MasterKeySource), new KeyValue("package2_key_source", 0x10, 60, set => set.Package2KeySource), diff --git a/src/hactoolnet/ProcessPackage.cs b/src/hactoolnet/ProcessPackage.cs index a08e0aeb..40e08814 100644 --- a/src/hactoolnet/ProcessPackage.cs +++ b/src/hactoolnet/ProcessPackage.cs @@ -1,7 +1,9 @@ using System.IO; +using System.Runtime.CompilerServices; using System.Text; using LibHac; using LibHac.Boot; +using LibHac.Common; using LibHac.Fs; using LibHac.FsSystem; using static hactoolnet.Print; @@ -14,21 +16,92 @@ namespace hactoolnet { using (var file = new LocalStorage(ctx.Options.InFile, FileAccess.Read)) { - var package1 = new Package1(ctx.Keyset, file); + var package1 = new LibHac.Boot.Package1(); + package1.Initialize(ctx.Keyset, file).ThrowIfFailure(); + + ctx.Logger.LogMessage(package1.Print()); + string outDir = ctx.Options.OutDir; - if (outDir != null) + if (package1.IsDecrypted && outDir != null) { Directory.CreateDirectory(outDir); - package1.Pk11.OpenWarmboot().WriteAllBytes(Path.Combine(outDir, "Warmboot.bin"), ctx.Logger); - package1.Pk11.OpenNxBootloader().WriteAllBytes(Path.Combine(outDir, "NX_Bootloader.bin"), ctx.Logger); - package1.Pk11.OpenSecureMonitor().WriteAllBytes(Path.Combine(outDir, "Secure_Monitor.bin"), ctx.Logger); - package1.OpenDecryptedPackage().WriteAllBytes(Path.Combine(outDir, "Decrypted.bin"), ctx.Logger); + IStorage decryptedStorage = package1.OpenDecryptedPackage1Storage(); + + WriteFile(decryptedStorage, "Decrypted.bin"); + WriteFile(package1.OpenWarmBootStorage(), "Warmboot.bin"); + WriteFile(package1.OpenNxBootloaderStorage(), "NX_Bootloader.bin"); + WriteFile(package1.OpenSecureMonitorStorage(), "Secure_Monitor.bin"); + + if (package1.IsMariko) + { + WriteFile(package1.OpenDecryptedWarmBootStorage(), "Warmboot_Decrypted.bin"); + + var marikoOemLoader = new SubStorage(decryptedStorage, Unsafe.SizeOf(), + package1.MarikoOemHeader.Size); + + WriteFile(marikoOemLoader, "Mariko_OEM_Bootloader.bin"); + } + } + + void WriteFile(IStorage storage, string filename) + { + string path = Path.Combine(outDir, filename); + ctx.Logger.LogMessage($"Writing {path}..."); + storage.WriteAllBytes(path, ctx.Logger); } } } + private static string Print(this LibHac.Boot.Package1 package1) + { + int colLen = 36; + var sb = new StringBuilder(); + sb.AppendLine(); + + if (package1.IsMariko) + { + sb.AppendLine("Mariko OEM Header:"); + PrintItem(sb, colLen, " Signature:", package1.MarikoOemHeader.RsaSig.ToArray()); + PrintItem(sb, colLen, " Random Salt:", package1.MarikoOemHeader.Salt.ToArray()); + PrintItem(sb, colLen, " OEM Bootloader Hash:", package1.MarikoOemHeader.Hash.ToArray()); + PrintItem(sb, colLen, " OEM Bootloader Version:", $"{package1.MarikoOemHeader.Version:x2}"); + PrintItem(sb, colLen, " OEM Bootloader Size:", $"{package1.MarikoOemHeader.Size:x8}"); + PrintItem(sb, colLen, " OEM Bootloader Load Address:", $"{package1.MarikoOemHeader.LoadAddress:x8}"); + PrintItem(sb, colLen, " OEM Bootloader Entrypoint:", $"{package1.MarikoOemHeader.EntryPoint:x8}"); + } + + sb.AppendLine("Package1 Metadata:"); + PrintItem(sb, colLen, " Build Date:", package1.MetaData.BuildDate.ToString()); + PrintItem(sb, colLen, " Package1ldr Hash:", SpanHelpers.AsReadOnlyByteSpan(in package1.MetaData.LoaderHash).ToArray()); + PrintItem(sb, colLen, " Secure Monitor Hash:", SpanHelpers.AsReadOnlyByteSpan(in package1.MetaData.SecureMonitorHash).ToArray()); + PrintItem(sb, colLen, " NX Bootloader Hash:", SpanHelpers.AsReadOnlyByteSpan(in package1.MetaData.BootloaderHash).ToArray()); + PrintItem(sb, colLen, " Version:", $"{package1.MetaData.Version:x2}"); + + if (!package1.IsMariko && package1.IsModern) + { + PrintItem(sb, colLen, " PK11 MAC:", package1.Pk11Mac); + } + + if (package1.IsDecrypted) + { + sb.AppendLine("PK11:"); + + if (!package1.IsMariko) + { + PrintItem(sb, colLen, " Key Revision:", $"{package1.KeyRevision:x2} ({Utilities.GetKeyRevisionSummary(package1.KeyRevision)})"); + } + + PrintItem(sb, colLen, " PK11 Size:", $"{package1.Pk11Size:x8}"); + PrintItem(sb, colLen, " Warmboot.bin Size:", $"{package1.GetSectionSize(Package1Section.WarmBoot):x8}"); + PrintItem(sb, colLen, " NX_Bootloader.bin Size:", $"{package1.GetSectionSize(Package1Section.Bootloader):x8}"); + PrintItem(sb, colLen, " Secure_Monitor.bin Size:", $"{package1.GetSectionSize(Package1Section.SecureMonitor):x8}"); + } + + return sb.ToString(); + } + public static void ProcessPk21(Context ctx) { using (var file = new CachedStorage(new LocalStorage(ctx.Options.InFile, FileAccess.Read), 0x4000, 4, false))