diff --git a/LibHac/Aes128CtrStream.cs b/LibHac/Aes128CtrStream.cs index 333c473c..18a9e588 100644 --- a/LibHac/Aes128CtrStream.cs +++ b/LibHac/Aes128CtrStream.cs @@ -49,6 +49,8 @@ namespace LibHac _decryptor = new Aes128CtrTransform(key, counter ?? new byte[0x10], CryptChunkSize); Counter = _decryptor.Counter; + + baseStream.Position = 0; } /// @@ -76,6 +78,8 @@ namespace LibHac _decryptor = new Aes128CtrTransform(key, initialCounter, CryptChunkSize); Counter = _decryptor.Counter; UpdateCounter(_counterOffset + base.Position); + + baseStream.Position = offset; } private void UpdateCounter(long offset) diff --git a/LibHac/Package1.cs b/LibHac/Package1.cs new file mode 100644 index 00000000..06659d2f --- /dev/null +++ b/LibHac/Package1.cs @@ -0,0 +1,113 @@ +using System; +using System.IO; +using LibHac.Streams; + +namespace LibHac +{ + public class Package1 + { + private const uint Pk11Magic = 0x31314B50; // PK11 + + public byte[] BuildHash { get; } + public string BuildDate { get; } + public int Field1E { get; } + public int Pk11Size { get; } + public byte[] Counter { get; } + public int KeyRevision { get; } + public Pk11 Pk11 { get; } + + private SharedStreamSource StreamSource { get; } + + public Package1(Keyset keyset, Stream stream) + { + StreamSource = new SharedStreamSource(stream); + var reader = new BinaryReader(stream); + + BuildHash = reader.ReadBytes(0x10); + BuildDate = reader.ReadAscii(0xE); + Field1E = reader.ReadUInt16(); + + reader.BaseStream.Position = 0x3FE0; + Pk11Size = reader.ReadInt32(); + + reader.BaseStream.Position += 0xC; + Counter = reader.ReadBytes(0x10); + + // Try decrypting the PK11 blob with all known package1 keys + Stream encStream = StreamSource.CreateStream(0x4000, Pk11Size); + var decBuffer = new byte[0x10]; + + for (int i = 0; i < 0x20; i++) + { + var dec = new Aes128CtrStream(encStream, keyset.package1_keys[i], Counter); + dec.Read(decBuffer, 0, 0x10); + + if (BitConverter.ToUInt32(decBuffer, 0) == Pk11Magic) + { + KeyRevision = i; + + dec.Position = 0; + Pk11 = new Pk11(new RandomAccessSectorStream(dec)); + + return; + } + } + + throw new InvalidDataException("Failed to decrypt PK11! Is the correct key present?"); + } + + public Stream OpenPackage1Ldr() => StreamSource.CreateStream(0, 0x4000); + } + + public class Pk11 + { + private const int DataOffset = 0x20; + + public string Magic { get; } + public int[] SectionSizes { get; } = new int[3]; + public int[] SectionOffsets { get; } = new int[3]; + + private SharedStreamSource StreamSource { get; } + + public Pk11(Stream stream) + { + StreamSource = new SharedStreamSource(stream); + var reader = new BinaryReader(stream); + + Magic = reader.ReadAscii(4); + SectionSizes[0] = reader.ReadInt32(); + SectionOffsets[0] = reader.ReadInt32(); + + reader.BaseStream.Position += 4; + SectionSizes[1] = reader.ReadInt32(); + SectionOffsets[1] = reader.ReadInt32(); + SectionSizes[2] = reader.ReadInt32(); + SectionOffsets[2] = reader.ReadInt32(); + + SectionOffsets[0] = DataOffset; + SectionOffsets[1] = SectionOffsets[0] + SectionSizes[0]; + SectionOffsets[2] = SectionOffsets[1] + SectionSizes[1]; + } + + public Stream OpenSection(int index) + { + if (index < 0 || index > 2) + { + throw new ArgumentOutOfRangeException(nameof(index), "Section index must be one of: 0, 1, 2"); + } + + return StreamSource.CreateStream(SectionOffsets[index], SectionSizes[index]); + } + + public Stream OpenDecryptedPk11() => StreamSource.CreateStream(); + + public Stream OpenWarmboot() => OpenSection(GetWarmbootSection()); + public Stream OpenNxBootloader() => OpenSection(GetNxBootloaderSection()); + public Stream OpenSecureMonitor() => OpenSection(GetSecureMonitorSection()); + + // todo: Handle the old layout from before 2.0.0 + private int GetWarmbootSection() => 0; + private int GetNxBootloaderSection() => 1; + private int GetSecureMonitorSection() => 2; + } +} diff --git a/README.md b/README.md index d612cf2d..5b53ba5f 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Options: -r, --raw Keep raw data, don't unpack. -y, --verify Verify hashes. -k, --keyset Load keys from an external file. - -t, --intype=type Specify input file type [nca, xci, switchfs, save, keygen] + -t, --intype=type Specify input file type [nca, xci, romfs, pk11, switchfs, save, keygen] --titlekeys Load title keys from an external file. NCA options: --section0 Specify Section 0 file path. @@ -34,6 +34,9 @@ NCA options: --romfsdir Specify RomFS directory path. --listromfs List files in RomFS. --basenca Set Base NCA to use with update partitions. +RomFS options: + --romfsdir Specify RomFS directory path. + --listromfs List files in RomFS. XCI options: --rootdir Specify root XCI directory path. --updatedir Specify update XCI directory path. @@ -46,6 +49,8 @@ XCI options: --romfs Specify main RomFS file path. --romfsdir Specify main RomFS directory path. --nspout Specify file for the created NSP. +Package1 options: + --outdir Specify Package1 directory path. Switch FS options: --sdseed Set console unique seed for SD card NAX0 encryption. --listapps List application info. diff --git a/hactoolnet/CliParser.cs b/hactoolnet/CliParser.cs index 15654aaf..466860e8 100644 --- a/hactoolnet/CliParser.cs +++ b/hactoolnet/CliParser.cs @@ -146,7 +146,7 @@ namespace hactoolnet sb.AppendLine(" -r, --raw Keep raw data, don\'t unpack."); sb.AppendLine(" -y, --verify Verify hashes."); sb.AppendLine(" -k, --keyset Load keys from an external file."); - sb.AppendLine(" -t, --intype=type Specify input file type [nca, xci, switchfs, save, keygen]"); + sb.AppendLine(" -t, --intype=type Specify input file type [nca, xci, romfs, pk11, switchfs, save, keygen]"); sb.AppendLine(" --titlekeys Load title keys from an external file."); sb.AppendLine("NCA options:"); sb.AppendLine(" --section0 Specify Section 0 file path."); @@ -163,6 +163,9 @@ namespace hactoolnet sb.AppendLine(" --romfsdir Specify RomFS directory path."); sb.AppendLine(" --listromfs List files in RomFS."); sb.AppendLine(" --basenca Set Base NCA to use with update partitions."); + sb.AppendLine("RomFS options:"); + sb.AppendLine(" --romfsdir Specify RomFS directory path."); + sb.AppendLine(" --listromfs List files in RomFS."); sb.AppendLine("XCI options:"); sb.AppendLine(" --rootdir Specify root XCI directory path."); sb.AppendLine(" --updatedir Specify update XCI directory path."); @@ -175,6 +178,8 @@ namespace hactoolnet sb.AppendLine(" --romfs Specify main RomFS file path."); sb.AppendLine(" --romfsdir Specify main RomFS directory path."); sb.AppendLine(" --nspout Specify file for the created NSP."); + sb.AppendLine("Package1 options:"); + sb.AppendLine(" --outdir Specify Package1 directory path."); sb.AppendLine("Switch FS options:"); sb.AppendLine(" --sdseed Set console unique seed for SD card NAX0 encryption."); sb.AppendLine(" --listapps List application info."); @@ -190,7 +195,6 @@ namespace hactoolnet sb.AppendLine(" --outdir Specify directory path to save contents to."); sb.AppendLine(" --debugoutdir Specify directory path to save intermediate data to for debugging."); - return sb.ToString(); } diff --git a/hactoolnet/Options.cs b/hactoolnet/Options.cs index f3ba3f67..f4ec7bf0 100644 --- a/hactoolnet/Options.cs +++ b/hactoolnet/Options.cs @@ -45,7 +45,8 @@ namespace hactoolnet Xci, SwitchFs, Save, - Keygen + Keygen, + Pk11 } internal class Context diff --git a/hactoolnet/Program.cs b/hactoolnet/Program.cs index 72cfaa60..12fc3d03 100644 --- a/hactoolnet/Program.cs +++ b/hactoolnet/Program.cs @@ -52,6 +52,9 @@ namespace hactoolnet case FileType.Keygen: ProcessKeygen(ctx); break; + case FileType.Pk11: + ProcessPk11(ctx); + break; default: throw new ArgumentOutOfRangeException(); } @@ -509,6 +512,30 @@ namespace hactoolnet Console.WriteLine(ExternalKeys.PrintKeys(ctx.Keyset)); } + private static void ProcessPk11(Context ctx) + { + using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read)) + { + var package1 = new Package1(ctx.Keyset, file); + string outDir = ctx.Options.OutDir; + + if (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); + + using (var decFile = new FileStream(Path.Combine(outDir, "Decrypted.bin"), FileMode.Create)) + { + package1.OpenPackage1Ldr().CopyTo(decFile); + package1.Pk11.OpenDecryptedPk11().CopyTo(decFile); + } + } + } + } + // For running random stuff // ReSharper disable once UnusedParameter.Local private static void CustomTask(Context ctx)