mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2025-02-09 13:14:46 +01:00
Rewrite the Package1 class, updating it to handle newer package1s
This commit is contained in:
parent
5f755bc76f
commit
61ce892697
@ -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,
|
||||
|
Can't render this file because it has a wrong number of fields in line 203.
|
547
src/LibHac/Boot/Package1.cs
Normal file
547
src/LibHac/Boot/Package1.cs
Normal 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'
|
||||
};
|
||||
}
|
||||
}
|
@ -83,5 +83,14 @@ namespace LibHac.Common
|
||||
public static Result.Base InvalidPackage2MetaEntryPointNotFound => new Result.Base(ModuleLibHac, 1032);
|
||||
/// <summary>Error code: 2428-1033; Inner value: 0x813ac</summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
83
src/LibHac/FsSystem/AesCbcStorage.cs
Normal file
83
src/LibHac/FsSystem/AesCbcStorage.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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),
|
||||
|
||||
|
@ -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<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)
|
||||
{
|
||||
using (var file = new CachedStorage(new LocalStorage(ctx.Options.InFile, FileAccess.Read), 0x4000, 4, false))
|
||||
|
Loading…
x
Reference in New Issue
Block a user