Rewrite the Package1 class, updating it to handle newer package1s

This commit is contained in:
Alex Barney 2020-09-28 01:18:15 -07:00
parent 5f755bc76f
commit 61ce892697
7 changed files with 730 additions and 8 deletions

View File

@ -467,3 +467,8 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
428,1031,,InvalidPackage2MetaPayloadsOverlap, 428,1031,,InvalidPackage2MetaPayloadsOverlap,
428,1032,,InvalidPackage2MetaEntryPointNotFound, 428,1032,,InvalidPackage2MetaEntryPointNotFound,
428,1033,,InvalidPackage2PayloadCorrupted, 428,1033,,InvalidPackage2PayloadCorrupted,
428,1040,1059,InvalidPackage1,
428,1041,,InvalidPackage1SectionSize,
428,1042,,InvalidPackage1MarikoBodySize,
428,1043,,InvalidPackage1Pk11Size,

Can't render this file because it has a wrong number of fields in line 203.

547
src/LibHac/Boot/Package1.cs Normal file
View File

@ -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<byte> AesMac => SpanHelpers.CreateSpan(ref _aesMac, 0x10);
public ReadOnlySpan<byte> RsaSig => SpanHelpers.CreateSpan(ref _rsaSig, 0x100);
public ReadOnlySpan<byte> Salt => SpanHelpers.CreateSpan(ref _salt, 0x20);
public ReadOnlySpan<byte> Hash => SpanHelpers.CreateSpan(ref _hash, 0x20);
public ReadOnlySpan<byte> 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<byte> 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<byte> 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; }
/// <summary>
/// Returns <see langword="true"/> if the package1 can be decrypted.
/// </summary>
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;
}
/// <summary>
/// Read the encrypted section of a Mariko Package1 and try to decrypt it.
/// </summary>
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
/// <see cref="ResultLibHac.InvalidPackage1MarikoBodySize"/>: The package1 is
/// too small or the size in the OEM header is incorrect.</returns>
private Result InitMarikoBodyStorage()
{
// Body must be large enough to hold at least one metadata struct
if (MarikoOemHeader.Size < Unsafe.SizeOf<Package1MetaData>())
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<Package1MarikoOemHeader>();
if (bodySize < MarikoOemHeader.Size)
return ResultLibHac.InvalidPackage1MarikoBodySize.Log();
// Create body SubStorage and metadata buffers
var bodySubStorage = new SubStorage(BaseStorage, Unsafe.SizeOf<Package1MarikoOemHeader>(), bodySize);
Span<Package1MetaData> metaData = stackalloc Package1MetaData[2];
Span<byte> metaData1 = SpanHelpers.AsByteSpan(ref metaData[0]);
Span<byte> metaData2 = SpanHelpers.AsByteSpan(ref metaData[1]);
// Read both the plaintext metadata and encrypted metadata
rc = bodySubStorage.Read(0, MemoryMarshal.Cast<Package1MetaData, byte>(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<Package1Stage1Footer>();
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<byte> input, Span<byte> output, ReadOnlySpan<byte> key,
ReadOnlySpan<byte> 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<Package1Pk11Header>() + 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();
}
/// <summary>
/// Opens an <see cref="IStorage"/> of the entire package1, decrypting any encrypted data.
/// </summary>
/// <returns>If the package1 can be decrypted, an <see cref="IStorage"/>
/// of the package1, <see langword="null"/>.</returns>
public IStorage OpenDecryptedPackage1Storage()
{
if (!IsDecrypted)
return null;
var storages = new List<IStorage>();
if (IsMariko)
{
int metaSize = Unsafe.SizeOf<Package1MetaData>();
// 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<Package1MarikoOemHeader>() + 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);
}
/// <summary>
/// Opens an <see cref="IStorage"/> of the warmboot section.
/// </summary>
/// <returns>If the section can be decrypted, an <see cref="IStorage"/>of the
/// warmboot section; otherwise, <see langword="null"/>.</returns>
public IStorage OpenWarmBootStorage() => OpenSectionStorage(Package1Section.WarmBoot);
/// <summary>
/// Opens an <see cref="IStorage"/> of the bootloader section.
/// </summary>
/// <returns>If the section can be decrypted, an <see cref="IStorage"/>of the
/// bootloader section; otherwise, <see langword="null"/>.</returns>
public IStorage OpenNxBootloaderStorage() => OpenSectionStorage(Package1Section.Bootloader);
/// <summary>
/// Opens an <see cref="IStorage"/> of the secure monitor section.
/// </summary>
/// <returns>If the section can be decrypted, an <see cref="IStorage"/>of the
/// secure monitor section; otherwise, <see langword="null"/>.</returns>
public IStorage OpenSecureMonitorStorage() => OpenSectionStorage(Package1Section.SecureMonitor);
/// <summary>
/// Opens an <see cref="IStorage"/> for the specified <see cref="Package1Section"/>.
/// </summary>
/// <param name="sectionType">The section to open.</param>
/// <returns>If the section can be decrypted, an <see cref="IStorage"/>of that
/// section; otherwise, <see langword="null"/>.</returns>
public IStorage OpenSectionStorage(Package1Section sectionType)
{
if (!IsDecrypted)
return null;
int offset = Unsafe.SizeOf<Package1Pk11Header>() + 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<IStorage> { 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<byte> LegacyDateCutoff => // 20181107
new[]
{
(byte) '2', (byte) '0', (byte) '1', (byte) '8', (byte) '1', (byte) '1', (byte) '0', (byte) '7'
};
}
}

View File

@ -83,5 +83,14 @@ namespace LibHac.Common
public static Result.Base InvalidPackage2MetaEntryPointNotFound => new Result.Base(ModuleLibHac, 1032); public static Result.Base InvalidPackage2MetaEntryPointNotFound => new Result.Base(ModuleLibHac, 1032);
/// <summary>Error code: 2428-1033; Inner value: 0x813ac</summary> /// <summary>Error code: 2428-1033; Inner value: 0x813ac</summary>
public static Result.Base InvalidPackage2PayloadCorrupted => new Result.Base(ModuleLibHac, 1033); public static Result.Base InvalidPackage2PayloadCorrupted => new Result.Base(ModuleLibHac, 1033);
/// <summary>Error code: 2428-1040; Range: 1040-1059; Inner value: 0x821ac</summary>
public static Result.Base InvalidPackage1 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1040, 1059); }
/// <summary>Error code: 2428-1041; Inner value: 0x823ac</summary>
public static Result.Base InvalidPackage1SectionSize => new Result.Base(ModuleLibHac, 1041);
/// <summary>Error code: 2428-1042; Inner value: 0x825ac</summary>
public static Result.Base InvalidPackage1MarikoBodySize => new Result.Base(ModuleLibHac, 1042);
/// <summary>Error code: 2428-1043; Inner value: 0x827ac</summary>
public static Result.Base InvalidPackage1Pk11Size => new Result.Base(ModuleLibHac, 1043);
} }
} }

View File

@ -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<byte> key, ReadOnlySpan<byte> 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<byte> 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<byte> 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<byte> 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;
}
}
}

View File

@ -145,7 +145,7 @@ namespace LibHac.FsSystem
Blocks.AddFirst(node); 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 // An inactive node shouldn't be null, but we'll fix it if it is anyway

View File

@ -59,6 +59,8 @@ namespace LibHac
public byte[] BisKekSource { get; } = new byte[0x10]; public byte[] BisKekSource { get; } = new byte[0x10];
public byte[][] BisKeySource { get; } = Utilities.CreateJaggedByteArray(4, 0x20); public byte[][] BisKeySource { get; } = Utilities.CreateJaggedByteArray(4, 0x20);
public byte[] SslRsaKek { get; } = new byte[0x10]; public byte[] SslRsaKek { get; } = new byte[0x10];
public byte[] MarikoBek { get; } = new byte[0x10];
public byte[] MarikoKek { get; } = new byte[0x10];
// Device-specific keys // Device-specific keys
public byte[] SecureBootKey { get; } = new byte[0x10]; public byte[] SecureBootKey { get; } = new byte[0x10];
@ -697,7 +699,7 @@ namespace LibHac
if (keySlot.Group > currentGroup) if (keySlot.Group > currentGroup)
{ {
if (currentGroup > 0) sb.AppendLine(); sb.AppendLine();
currentGroup = keySlot.Group; currentGroup = keySlot.Group;
} }
@ -760,6 +762,9 @@ namespace LibHac
{ {
new KeyValue("keyblob_mac_key_source", 0x10, 0, set => set.KeyblobMacKeySource), 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("master_key_source", 0x10, 60, set => set.MasterKeySource),
new KeyValue("package2_key_source", 0x10, 60, set => set.Package2KeySource), new KeyValue("package2_key_source", 0x10, 60, set => set.Package2KeySource),

View File

@ -1,7 +1,9 @@
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using LibHac; using LibHac;
using LibHac.Boot; using LibHac.Boot;
using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
using LibHac.FsSystem; using LibHac.FsSystem;
using static hactoolnet.Print; using static hactoolnet.Print;
@ -14,21 +16,92 @@ namespace hactoolnet
{ {
using (var file = new LocalStorage(ctx.Options.InFile, FileAccess.Read)) 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; string outDir = ctx.Options.OutDir;
if (outDir != null) if (package1.IsDecrypted && outDir != null)
{ {
Directory.CreateDirectory(outDir); Directory.CreateDirectory(outDir);
package1.Pk11.OpenWarmboot().WriteAllBytes(Path.Combine(outDir, "Warmboot.bin"), ctx.Logger); IStorage decryptedStorage = package1.OpenDecryptedPackage1Storage();
package1.Pk11.OpenNxBootloader().WriteAllBytes(Path.Combine(outDir, "NX_Bootloader.bin"), ctx.Logger);
package1.Pk11.OpenSecureMonitor().WriteAllBytes(Path.Combine(outDir, "Secure_Monitor.bin"), ctx.Logger); WriteFile(decryptedStorage, "Decrypted.bin");
package1.OpenDecryptedPackage().WriteAllBytes(Path.Combine(outDir, "Decrypted.bin"), ctx.Logger); 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<Package1MarikoOemHeader>(),
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) public static void ProcessPk21(Context ctx)
{ {
using (var file = new CachedStorage(new LocalStorage(ctx.Options.InFile, FileAccess.Read), 0x4000, 4, false)) using (var file = new CachedStorage(new LocalStorage(ctx.Options.InFile, FileAccess.Read), 0x4000, 4, false))