Add package2 support

This commit is contained in:
Alex Barney 2018-09-16 20:26:16 -05:00
parent 087008f7f4
commit 4bfaafc301
10 changed files with 246 additions and 13 deletions

View file

@ -14,6 +14,15 @@ namespace LibHac
private readonly Aes128CtrTransform _decryptor;
protected readonly byte[] Counter;
/// <summary>
/// Creates a new stream
/// </summary>
/// <param name="baseStream">The base stream</param>
/// <param name="key">The decryption key</param>
/// <param name="counter">The initial counter</param>
public Aes128CtrStream(Stream baseStream, byte[] key, byte[] counter)
: this(baseStream, key, 0, baseStream.Length, counter) { }
/// <summary>
/// Creates a new stream
/// </summary>
@ -29,9 +38,11 @@ namespace LibHac
/// </summary>
/// <param name="baseStream">The base stream</param>
/// <param name="key">The decryption key</param>
/// <param name="offset">Offset to start at in the input stream</param>
/// <param name="length">The length of the created stream</param>
/// <param name="counter">The initial counter</param>
public Aes128CtrStream(Stream baseStream, byte[] key, byte[] counter)
: base(baseStream, BlockSize)
public Aes128CtrStream(Stream baseStream, byte[] key, long offset, long length, byte[] counter)
: base(baseStream, BlockSize, 1, offset)
{
_counterOffset = 0;
@ -44,13 +55,11 @@ namespace LibHac
}
}
Length = baseStream.Length;
Length = length;
_tempBuffer = new byte[CryptChunkSize];
_decryptor = new Aes128CtrTransform(key, counter ?? new byte[0x10], CryptChunkSize);
Counter = _decryptor.Counter;
baseStream.Position = 0;
}
/// <summary>

View file

@ -63,11 +63,7 @@ namespace LibHac
private void IncrementCounter()
{
for (int i = Counter.Length - 1; i >= 0; i--)
{
if (++Counter[i] != 0)
break;
}
Util.IncrementByteArray(Counter);
}
private void XorArrays(byte[] inputBuffer, int inputOffset, byte[] outputBuffer, int outputOffset, byte[] xor, int length)

154
LibHac/Package2.cs Normal file
View file

@ -0,0 +1,154 @@
using System;
using System.IO;
using LibHac.Streams;
namespace LibHac
{
public class Package2
{
private const uint Pk21Magic = 0x31324B50; // PK21
public Package2Header Header { get; }
public int KeyRevision { get; }
public byte[] Key { get; }
public int PackageSize { get; }
public int HeaderVersion { get; }
private SharedStreamSource StreamSource { get; }
public Package2(Keyset keyset, Stream stream)
{
StreamSource = new SharedStreamSource(stream);
SharedStream headerStream = StreamSource.CreateStream(0, 0x200);
KeyRevision = FindKeyGeneration(keyset, headerStream);
Key = keyset.Package2Keys[KeyRevision];
Header = new Package2Header(headerStream, Key);
PackageSize = BitConverter.ToInt32(Header.Counter, 0) ^ BitConverter.ToInt32(Header.Counter, 8) ^
BitConverter.ToInt32(Header.Counter, 12);
HeaderVersion = Header.Counter[4] ^ Header.Counter[6] ^ Header.Counter[7];
if (PackageSize != 0x200 + Header.SectionSizes[0] + Header.SectionSizes[1] + Header.SectionSizes[2])
{
throw new InvalidDataException("Package2 Header is corrupt!");
}
}
public Stream OpenHeaderPart1()
{
return StreamSource.CreateStream(0, 0x110);
}
public Stream OpenHeaderPart2()
{
SharedStream encStream = StreamSource.CreateStream(0x110, 0xF0);
var counter = new byte[0x10];
Array.Copy(Header.Counter, counter, 0x10);
Util.IncrementByteArray(counter);
return new RandomAccessSectorStream(new Aes128CtrStream(encStream, Key, counter));
}
public Stream OpenKernel()
{
int offset = 0x200;
SharedStream encStream = StreamSource.CreateStream(offset, Header.SectionSizes[0]);
return new RandomAccessSectorStream(new Aes128CtrStream(encStream, Key, Header.SectionCounters[0]));
}
public Stream OpenIni1()
{
int offset = 0x200 + Header.SectionSizes[0];
SharedStream encStream = StreamSource.CreateStream(offset, Header.SectionSizes[1]);
return new RandomAccessSectorStream(new Aes128CtrStream(encStream, Key, Header.SectionCounters[1]));
}
private int FindKeyGeneration(Keyset keyset, Stream stream)
{
var counter = new byte[0x10];
var decBuffer = new byte[0x10];
stream.Position = 0x100;
stream.Read(counter, 0, 0x10);
for (int i = 0; i < 0x20; i++)
{
var dec = new Aes128CtrStream(stream, keyset.Package2Keys[i], 0x100, 0x100, counter);
dec.Position = 0x50;
dec.Read(decBuffer, 0, 0x10);
if (BitConverter.ToUInt32(decBuffer, 0) == Pk21Magic)
{
stream.Position = 0;
return i;
}
}
throw new InvalidDataException("Failed to decrypt package2! Is the correct key present?");
}
}
public class Package2Header
{
public byte[] Signature { get; }
public byte[] Counter { get; }
public byte[][] SectionCounters { get; } = new byte[4][];
public int[] SectionSizes { get; } = new int[4];
public int[] SectionOffsets { get; } = new int[4];
public byte[][] SectionHashes { get; } = new byte[4][];
public string Magic { get; }
public int BaseOffset { get; }
public int VersionMax { get; }
public int VersionMin { get; }
public Package2Header(Stream stream, byte[] key)
{
var reader = new BinaryReader(stream);
Signature = reader.ReadBytes(0x100);
Counter = reader.ReadBytes(0x10);
var headerStream = new RandomAccessSectorStream(new Aes128CtrStream(stream, key, 0x100, 0x100, Counter));
headerStream.Position = 0x10;
reader = new BinaryReader(headerStream);
for (int i = 0; i < 4; i++)
{
SectionCounters[i] = reader.ReadBytes(0x10);
}
Magic = reader.ReadAscii(4);
BaseOffset = reader.ReadInt32();
reader.BaseStream.Position += 4;
VersionMax = reader.ReadByte();
VersionMin = reader.ReadByte();
reader.BaseStream.Position += 2;
for (int i = 0; i < 4; i++)
{
SectionSizes[i] = reader.ReadInt32();
}
for (int i = 0; i < 4; i++)
{
SectionOffsets[i] = reader.ReadInt32();
}
for (int i = 0; i < 4; i++)
{
SectionHashes[i] = reader.ReadBytes(0x20);
}
}
}
}

View file

@ -63,6 +63,7 @@ namespace LibHac.Streams
_offset = offset;
_keepOpen = keepOpen;
_maxBufferSize = MaxSectors * SectorSize;
baseStream.Position = offset;
}
public override void Flush()

View file

@ -322,6 +322,15 @@ namespace LibHac
public static int AlignDown(int value, int multiple) => value - value % multiple;
public static long AlignDown(long value, long multiple) => value - value % multiple;
public static void IncrementByteArray(byte[] array)
{
for (int i = array.Length - 1; i >= 0; i--)
{
if (++array[i] != 0)
break;
}
}
public static void MemDump(this StringBuilder sb, string prefix, byte[] data)
{
@ -407,5 +416,6 @@ namespace LibHac
return (hi.GetHashCode() * 397) ^ lo.GetHashCode();
}
}
}

View file

@ -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, romfs, pk11, switchfs, save, keygen]
-t, --intype=type Specify input file type [nca, xci, romfs, pk11, pk21, switchfs, save, keygen]
--titlekeys <file> Load title keys from an external file.
NCA options:
--section0 <file> Specify Section 0 file path.
@ -51,6 +51,8 @@ XCI options:
--nspout <file> Specify file for the created NSP.
Package1 options:
--outdir <dir> Specify Package1 directory path.
Package2 options:
--outdir <dir> Specify Package2 directory path.
Switch FS options:
--sdseed <seed> Set console unique seed for SD card NAX0 encryption.
--listapps List application info.

View file

@ -147,7 +147,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, romfs, pk11, switchfs, save, keygen]");
sb.AppendLine(" -t, --intype=type Specify input file type [nca, xci, romfs, pk11, pk21, switchfs, save, keygen]");
sb.AppendLine(" --titlekeys <file> Load title keys from an external file.");
sb.AppendLine("NCA options:");
sb.AppendLine(" --section0 <file> Specify Section 0 file path.");
@ -181,6 +181,8 @@ namespace hactoolnet
sb.AppendLine(" --nspout <file> Specify file for the created NSP.");
sb.AppendLine("Package1 options:");
sb.AppendLine(" --outdir <dir> Specify Package1 directory path.");
sb.AppendLine("Package2 options:");
sb.AppendLine(" --outdir <dir> Specify Package2 directory path.");
sb.AppendLine("Switch FS options:");
sb.AppendLine(" --sdseed <seed> Set console unique seed for SD card NAX0 encryption.");
sb.AppendLine(" --listapps List application info.");

View file

@ -47,7 +47,8 @@ namespace hactoolnet
SwitchFs,
Save,
Keygen,
Pk11
Pk11,
Pk21
}
internal class Context

View file

@ -1,5 +1,7 @@
using System.IO;
using System.Text;
using LibHac;
using static hactoolnet.Print;
namespace hactoolnet
{
@ -28,5 +30,58 @@ namespace hactoolnet
}
}
}
public static void ProcessPk21(Context ctx)
{
using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read))
{
var package2 = new Package2(ctx.Keyset, file);
ctx.Logger.LogMessage(package2.Print());
string outDir = ctx.Options.OutDir;
if (outDir != null)
{
Directory.CreateDirectory(outDir);
package2.OpenKernel().WriteAllBytes(Path.Combine(outDir, "Kernel.bin"), ctx.Logger);
package2.OpenIni1().WriteAllBytes(Path.Combine(outDir, "INI1.bin"), ctx.Logger);
using (var decFile = new FileStream(Path.Combine(outDir, "Decrypted.bin"), FileMode.Create))
{
package2.OpenHeaderPart1().CopyTo(decFile);
package2.OpenHeaderPart2().CopyTo(decFile);
package2.OpenKernel().CopyTo(decFile);
package2.OpenIni1().CopyTo(decFile);
}
}
}
}
private static readonly string[] Package2SectionNames = { "Kernel", "INI1", "Empty" };
private static string Print(this Package2 package2)
{
int colLen = 36;
var sb = new StringBuilder();
sb.AppendLine();
sb.AppendLine("PK21:");
PrintItem(sb, colLen, "Signature:", package2.Header.Signature);
PrintItem(sb, colLen, "Header Version:", $"{package2.HeaderVersion:x2}");
for (int i = 0; i < 3; i++)
{
sb.AppendLine($"Section {i} ({Package2SectionNames[i]}):");
PrintItem(sb, colLen, " Hash:", package2.Header.SectionHashes[i]);
PrintItem(sb, colLen, " CTR:", package2.Header.SectionCounters[i]);
PrintItem(sb, colLen, " Load Address:", $"{package2.Header.SectionOffsets[i] + 0x80000000:x8}");
PrintItem(sb, colLen, " Size:", $"{package2.Header.SectionSizes[i]:x8}");
}
return sb.ToString();
}
}
}

View file

@ -53,6 +53,9 @@ namespace hactoolnet
case FileType.Pk11:
ProcessPackage.ProcessPk11(ctx);
break;
case FileType.Pk21:
ProcessPackage.ProcessPk21(ctx);
break;
default:
throw new ArgumentOutOfRangeException();
}