From 6bab1d9273ca48fec08cf49d1959119f5ab56e0d Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Mon, 10 Aug 2020 20:07:52 -0700 Subject: [PATCH] Support extracting INI binaries embedded in the kernel --- build/CodeGen/results.csv | 2 + src/LibHac/Boot/Package2StorageReader.cs | 50 +++++++++ src/LibHac/Common/ResultLibHac.cs | 2 + src/LibHac/Kernel/IniExtract.cs | 100 ++++++++++++++++++ .../Kernel/InitialProcessBinaryReader.cs | 2 +- src/hactoolnet/CliParser.cs | 4 + src/hactoolnet/Options.cs | 2 + src/hactoolnet/ProcessKip.cs | 34 +++--- src/hactoolnet/ProcessPackage.cs | 17 ++- 9 files changed, 196 insertions(+), 17 deletions(-) create mode 100644 src/LibHac/Kernel/IniExtract.cs diff --git a/build/CodeGen/results.csv b/build/CodeGen/results.csv index bfbc7048..1d445faa 100644 --- a/build/CodeGen/results.csv +++ b/build/CodeGen/results.csv @@ -387,6 +387,8 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary 428,51,,ServiceNotInitialized, +428,101,,NotImplemented, + 428,1000,1999,InvalidData, 428,1001,1019,InvalidInitialProcessData, 428,1002,1009,InvalidKip, diff --git a/src/LibHac/Boot/Package2StorageReader.cs b/src/LibHac/Boot/Package2StorageReader.cs index e3a9cce6..cfd2709e 100644 --- a/src/LibHac/Boot/Package2StorageReader.cs +++ b/src/LibHac/Boot/Package2StorageReader.cs @@ -5,6 +5,7 @@ using LibHac.Common; using LibHac.Crypto; using LibHac.Fs; using LibHac.FsSystem; +using LibHac.Kernel; namespace LibHac.Boot { @@ -13,6 +14,9 @@ namespace LibHac.Boot /// public class Package2StorageReader { + private const int KernelPayloadIndex = 0; + private const int IniPayloadIndex = 1; + private IStorage _storage; private Package2Header _header; private Keyset _keyset; @@ -69,6 +73,47 @@ namespace LibHac.Boot return Result.Success; } + /// + /// Opens an of the kernel payload. + /// + /// If the method returns successfully, contains an + /// of the kernel payload. + /// The of the operation. + public Result OpenKernel(out IStorage kernelStorage) + { + return OpenPayload(out kernelStorage, KernelPayloadIndex); + } + + /// + /// Opens an of the initial process binary. If the binary is embedded in + /// the kernel, this method will attempt to locate and return the embedded binary. + /// + /// If the method returns successfully, contains an + /// of the initial process binary. + /// The of the operation. + public Result OpenIni(out IStorage iniStorage) + { + if (HasIniPayload()) + { + return OpenPayload(out iniStorage, IniPayloadIndex); + } + + // Ini is embedded in the kernel + iniStorage = default; + + Result rc = OpenKernel(out IStorage kernelStorage); + if (rc.IsFailure()) return rc; + + if (!IniExtract.TryGetIni1Offset(out int offset, out int size, kernelStorage)) + { + // Unable to find the ini. Could be a new, unsupported layout. + return ResultLibHac.NotImplemented.Log(); + } + + iniStorage = new SubStorage(kernelStorage, offset, size); + return Result.Success; + } + /// /// Verifies the signature, metadata and payloads in the package. /// @@ -216,5 +261,10 @@ namespace LibHac.Boot // Copy the IV to the output because the IV field will be garbage after "decrypting" it Unsafe.As(ref dest) = iv; } + + private bool HasIniPayload() + { + return _header.Meta.PayloadSizes[IniPayloadIndex] != 0; + } } } diff --git a/src/LibHac/Common/ResultLibHac.cs b/src/LibHac/Common/ResultLibHac.cs index c6c8b7e8..f93961b7 100644 --- a/src/LibHac/Common/ResultLibHac.cs +++ b/src/LibHac/Common/ResultLibHac.cs @@ -28,6 +28,8 @@ namespace LibHac.Common /// Error code: 2428-0051; Inner value: 0x67ac public static Result.Base ServiceNotInitialized => new Result.Base(ModuleLibHac, 51); + /// Error code: 2428-0101; Inner value: 0xcbac + public static Result.Base NotImplemented => new Result.Base(ModuleLibHac, 101); /// Error code: 2428-1000; Range: 1000-1999; Inner value: 0x7d1ac public static Result.Base InvalidData { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleLibHac, 1000, 1999); } diff --git a/src/LibHac/Kernel/IniExtract.cs b/src/LibHac/Kernel/IniExtract.cs new file mode 100644 index 00000000..ad22c006 --- /dev/null +++ b/src/LibHac/Kernel/IniExtract.cs @@ -0,0 +1,100 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using LibHac.Common; +using LibHac.Fs; + +namespace LibHac.Kernel +{ + public static class IniExtract + { + /// + /// Locates and returns the offset and size of the initial process binary embedded in the kernel. + /// The INI is only embedded in the kernel in system versions >= 8.0.0. + /// + /// When this method returns, contains the offset of + /// the INI inside the kernel if it was found. + /// When this method returns, contains the size of the INI if it was found. + /// An containing the kernel to search. + /// if the embedded INI was found. + public static bool TryGetIni1Offset(out int offset, out int size, IStorage kernelStorage) + { + offset = 0; + size = 0; + + if (kernelStorage.GetSize(out long kernelSizeLong).IsFailure()) + return false; + + uint kernelSize = (uint)kernelSizeLong; + + using (var array = new RentedArray(0x1000 + Unsafe.SizeOf())) + { + // The kernel map should be in the first 0x1000 bytes + if (kernelStorage.Read(0, array.Span).IsFailure()) + return false; + + ref byte start = ref array.Span[0]; + + // Search every 4 bytes for a valid kernel map + for (int i = 0; i < 0x1000; i += sizeof(int)) + { + ref KernelMap map = ref Unsafe.As(ref Unsafe.Add(ref start, i)); + + if (IsValidKernelMap(in map, kernelSize)) + { + // Verify the ini header at the offset in the found map + var header = new InitialProcessBinaryReader.IniHeader(); + + if (kernelStorage.Read(map.Ini1StartOffset, SpanHelpers.AsByteSpan(ref header)).IsFailure()) + return false; + + if (header.Magic != InitialProcessBinaryReader.ExpectedMagic) + return false; + + offset = (int)map.Ini1StartOffset; + size = header.Size; + return true; + } + } + + return false; + } + } + + private static bool IsValidKernelMap(in KernelMap map, uint maxSize) + { + if (map.TextStartOffset != 0) return false; + if (map.TextStartOffset >= map.TextEndOffset) return false; + if ((map.TextEndOffset & 0xFFF) != 0) return false; + if (map.TextEndOffset > map.RodataStartOffset) return false; + if ((map.RodataStartOffset & 0xFFF) != 0) return false; + if (map.RodataStartOffset >= map.RodataEndOffset) return false; + if ((map.RodataEndOffset & 0xFFF) != 0) return false; + if (map.RodataEndOffset > map.DataStartOffset) return false; + if ((map.DataStartOffset & 0xFFF) != 0) return false; + if (map.DataStartOffset >= map.DataEndOffset) return false; + if (map.DataEndOffset > map.BssStartOffset) return false; + if (map.BssStartOffset > map.BssEndOffset) return false; + if (map.BssEndOffset > map.Ini1StartOffset) return false; + if (map.Ini1StartOffset > maxSize - Unsafe.SizeOf()) return false; + + return true; + } + + [StructLayout(LayoutKind.Sequential)] + private struct KernelMap + { + public uint TextStartOffset; + public uint TextEndOffset; + public uint RodataStartOffset; + public uint RodataEndOffset; + public uint DataStartOffset; + public uint DataEndOffset; + public uint BssStartOffset; + public uint BssEndOffset; + public uint Ini1StartOffset; + public uint DynamicOffset; + public uint InitArrayStartOffset; + public uint InitArrayEndOffset; + } + } +} diff --git a/src/LibHac/Kernel/InitialProcessBinaryReader.cs b/src/LibHac/Kernel/InitialProcessBinaryReader.cs index 15b729a6..2081ee09 100644 --- a/src/LibHac/Kernel/InitialProcessBinaryReader.cs +++ b/src/LibHac/Kernel/InitialProcessBinaryReader.cs @@ -8,7 +8,7 @@ namespace LibHac.Kernel { public class InitialProcessBinaryReader { - private const uint ExpectedMagic = 0x31494E49; // INI1 + internal const uint ExpectedMagic = 0x31494E49; // INI1 private const int MaxProcessCount = 80; private IStorage _storage; diff --git a/src/hactoolnet/CliParser.cs b/src/hactoolnet/CliParser.cs index 2260fb9a..48af55cb 100644 --- a/src/hactoolnet/CliParser.cs +++ b/src/hactoolnet/CliParser.cs @@ -36,6 +36,7 @@ namespace hactoolnet new CliOption("debugoutdir", 1, (o, a) => o.DebugOutDir = a[0]), new CliOption("savedir", 1, (o, a) => o.SaveOutDir = a[0]), new CliOption("outdir", 1, (o, a) => o.OutDir = a[0]), + new CliOption("ini1dir", 1, (o, a) => o.Ini1OutDir = a[0]), new CliOption("outfile", 1, (o, a) => o.OutFile = a[0]), new CliOption("plaintext", 1, (o, a) => o.PlaintextOut = a[0]), new CliOption("ciphertext", 1, (o, a) => o.CiphertextOut = a[0]), @@ -60,6 +61,7 @@ namespace hactoolnet new CliOption("trim", 0, (o, a) => o.TrimSave = true), new CliOption("readbench", 0, (o, a) => o.ReadBench = true), new CliOption("hashedfs", 0, (o, a) => o.BuildHfs = true), + new CliOption("extractini1", 0, (o, a) => o.ExtractIni1 = true), new CliOption("title", 1, (o, a) => o.TitleId = ParseTitleId(a[0])), new CliOption("bench", 1, (o, a) => o.BenchType = a[0]), new CliOption("cpufreq", 1, (o, a) => o.CpuFrequencyGhz = ParseDouble(a[0])), @@ -254,6 +256,8 @@ namespace hactoolnet sb.AppendLine(" --outdir Specify Package1 directory path."); sb.AppendLine("Package2 options:"); sb.AppendLine(" --outdir Specify Package2 directory path."); + sb.AppendLine(" --extractini1 Enable INI1 extraction to default directory (redundant with --ini1dir set)."); + sb.AppendLine(" --ini1dir Specify INI1 directory path. Overrides default path, if present."); sb.AppendLine("INI1 options:"); sb.AppendLine(" --outdir Specify INI1 directory path."); sb.AppendLine("Switch FS options:"); diff --git a/src/hactoolnet/Options.cs b/src/hactoolnet/Options.cs index a0acdc89..0791c14b 100644 --- a/src/hactoolnet/Options.cs +++ b/src/hactoolnet/Options.cs @@ -27,6 +27,7 @@ namespace hactoolnet public string RomfsOutDir; public string DebugOutDir; public string SaveOutDir; + public string Ini1OutDir; public string OutDir; public string OutFile; public string PlaintextOut; @@ -54,6 +55,7 @@ namespace hactoolnet public bool TrimSave; public bool ReadBench; public bool BuildHfs; + public bool ExtractIni1; public ulong TitleId; public string BenchType; public double CpuFrequencyGhz; diff --git a/src/hactoolnet/ProcessKip.cs b/src/hactoolnet/ProcessKip.cs index f7103a52..aad3c771 100644 --- a/src/hactoolnet/ProcessKip.cs +++ b/src/hactoolnet/ProcessKip.cs @@ -29,27 +29,31 @@ namespace hactoolnet { using (var file = new LocalStorage(ctx.Options.InFile, FileAccess.Read)) { - - var ini1 = new InitialProcessBinaryReader(); - ini1.Initialize(file).ThrowIfFailure(); - string outDir = ctx.Options.OutDir; if (outDir != null) { - Directory.CreateDirectory(outDir); - var kipReader = new KipReader(); - - for (int i = 0; i < ini1.ProcessCount; i++) - { - ini1.OpenKipStorage(out IStorage kipStorage, i).ThrowIfFailure(); - - kipReader.Initialize(kipStorage).ThrowIfFailure(); - - kipStorage.WriteAllBytes(Path.Combine(outDir, $"{kipReader.Name.ToString()}.kip1")); - } + ExtractIni1(file, outDir); } } } + + public static void ExtractIni1(IStorage iniStorage, string outDir) + { + var ini1 = new InitialProcessBinaryReader(); + ini1.Initialize(iniStorage).ThrowIfFailure(); + + Directory.CreateDirectory(outDir); + var kipReader = new KipReader(); + + for (int i = 0; i < ini1.ProcessCount; i++) + { + ini1.OpenKipStorage(out IStorage kipStorage, i).ThrowIfFailure(); + + kipReader.Initialize(kipStorage).ThrowIfFailure(); + + kipStorage.WriteAllBytes(Path.Combine(outDir, $"{kipReader.Name.ToString()}.kip1")); + } + } } } diff --git a/src/hactoolnet/ProcessPackage.cs b/src/hactoolnet/ProcessPackage.cs index ce781ea8..a08e0aeb 100644 --- a/src/hactoolnet/ProcessPackage.cs +++ b/src/hactoolnet/ProcessPackage.cs @@ -39,6 +39,12 @@ namespace hactoolnet ctx.Logger.LogMessage(package2.Print()); string outDir = ctx.Options.OutDir; + string iniDir = ctx.Options.Ini1OutDir; + + if (iniDir == null && ctx.Options.ExtractIni1) + { + iniDir = Path.Combine(outDir, "INI1"); + } if (outDir != null) { @@ -47,12 +53,21 @@ namespace hactoolnet package2.OpenPayload(out IStorage kernelStorage, 0).ThrowIfFailure(); kernelStorage.WriteAllBytes(Path.Combine(outDir, "Kernel.bin"), ctx.Logger); - package2.OpenPayload(out IStorage ini1Storage, 1).ThrowIfFailure(); + package2.OpenIni(out IStorage ini1Storage).ThrowIfFailure(); ini1Storage.WriteAllBytes(Path.Combine(outDir, "INI1.bin"), ctx.Logger); package2.OpenDecryptedPackage(out IStorage decPackageStorage).ThrowIfFailure(); decPackageStorage.WriteAllBytes(Path.Combine(outDir, "Decrypted.bin"), ctx.Logger); } + + if (iniDir != null) + { + Directory.CreateDirectory(iniDir); + + package2.OpenIni(out IStorage ini1Storage).ThrowIfFailure(); + + ProcessKip.ExtractIni1(ini1Storage, iniDir); + } } }