mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Add package2 support
This commit is contained in:
parent
087008f7f4
commit
4bfaafc301
10 changed files with 246 additions and 13 deletions
|
@ -14,6 +14,15 @@ namespace LibHac
|
||||||
private readonly Aes128CtrTransform _decryptor;
|
private readonly Aes128CtrTransform _decryptor;
|
||||||
protected readonly byte[] Counter;
|
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>
|
/// <summary>
|
||||||
/// Creates a new stream
|
/// Creates a new stream
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -29,9 +38,11 @@ namespace LibHac
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="baseStream">The base stream</param>
|
/// <param name="baseStream">The base stream</param>
|
||||||
/// <param name="key">The decryption key</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>
|
/// <param name="counter">The initial counter</param>
|
||||||
public Aes128CtrStream(Stream baseStream, byte[] key, byte[] counter)
|
public Aes128CtrStream(Stream baseStream, byte[] key, long offset, long length, byte[] counter)
|
||||||
: base(baseStream, BlockSize)
|
: base(baseStream, BlockSize, 1, offset)
|
||||||
{
|
{
|
||||||
_counterOffset = 0;
|
_counterOffset = 0;
|
||||||
|
|
||||||
|
@ -44,13 +55,11 @@ namespace LibHac
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Length = baseStream.Length;
|
Length = length;
|
||||||
_tempBuffer = new byte[CryptChunkSize];
|
_tempBuffer = new byte[CryptChunkSize];
|
||||||
|
|
||||||
_decryptor = new Aes128CtrTransform(key, counter ?? new byte[0x10], CryptChunkSize);
|
_decryptor = new Aes128CtrTransform(key, counter ?? new byte[0x10], CryptChunkSize);
|
||||||
Counter = _decryptor.Counter;
|
Counter = _decryptor.Counter;
|
||||||
|
|
||||||
baseStream.Position = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -63,11 +63,7 @@ namespace LibHac
|
||||||
|
|
||||||
private void IncrementCounter()
|
private void IncrementCounter()
|
||||||
{
|
{
|
||||||
for (int i = Counter.Length - 1; i >= 0; i--)
|
Util.IncrementByteArray(Counter);
|
||||||
{
|
|
||||||
if (++Counter[i] != 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void XorArrays(byte[] inputBuffer, int inputOffset, byte[] outputBuffer, int outputOffset, byte[] xor, int length)
|
private void XorArrays(byte[] inputBuffer, int inputOffset, byte[] outputBuffer, int outputOffset, byte[] xor, int length)
|
||||||
|
|
154
LibHac/Package2.cs
Normal file
154
LibHac/Package2.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -63,6 +63,7 @@ namespace LibHac.Streams
|
||||||
_offset = offset;
|
_offset = offset;
|
||||||
_keepOpen = keepOpen;
|
_keepOpen = keepOpen;
|
||||||
_maxBufferSize = MaxSectors * SectorSize;
|
_maxBufferSize = MaxSectors * SectorSize;
|
||||||
|
baseStream.Position = offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Flush()
|
public override void Flush()
|
||||||
|
|
|
@ -322,6 +322,15 @@ namespace LibHac
|
||||||
public static int AlignDown(int value, int multiple) => value - value % multiple;
|
public static int AlignDown(int value, int multiple) => value - value % multiple;
|
||||||
public static long AlignDown(long value, long 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)
|
public static void MemDump(this StringBuilder sb, string prefix, byte[] data)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -407,5 +416,6 @@ namespace LibHac
|
||||||
|
|
||||||
return (hi.GetHashCode() * 397) ^ lo.GetHashCode();
|
return (hi.GetHashCode() * 397) ^ lo.GetHashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ Options:
|
||||||
-r, --raw Keep raw data, don't unpack.
|
-r, --raw Keep raw data, don't unpack.
|
||||||
-y, --verify Verify hashes.
|
-y, --verify Verify hashes.
|
||||||
-k, --keyset Load keys from an external file.
|
-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.
|
--titlekeys <file> Load title keys from an external file.
|
||||||
NCA options:
|
NCA options:
|
||||||
--section0 <file> Specify Section 0 file path.
|
--section0 <file> Specify Section 0 file path.
|
||||||
|
@ -51,6 +51,8 @@ XCI options:
|
||||||
--nspout <file> Specify file for the created NSP.
|
--nspout <file> Specify file for the created NSP.
|
||||||
Package1 options:
|
Package1 options:
|
||||||
--outdir <dir> Specify Package1 directory path.
|
--outdir <dir> Specify Package1 directory path.
|
||||||
|
Package2 options:
|
||||||
|
--outdir <dir> Specify Package2 directory path.
|
||||||
Switch FS options:
|
Switch FS options:
|
||||||
--sdseed <seed> Set console unique seed for SD card NAX0 encryption.
|
--sdseed <seed> Set console unique seed for SD card NAX0 encryption.
|
||||||
--listapps List application info.
|
--listapps List application info.
|
||||||
|
|
|
@ -147,7 +147,7 @@ namespace hactoolnet
|
||||||
sb.AppendLine(" -r, --raw Keep raw data, don\'t unpack.");
|
sb.AppendLine(" -r, --raw Keep raw data, don\'t unpack.");
|
||||||
sb.AppendLine(" -y, --verify Verify hashes.");
|
sb.AppendLine(" -y, --verify Verify hashes.");
|
||||||
sb.AppendLine(" -k, --keyset Load keys from an external file.");
|
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(" --titlekeys <file> Load title keys from an external file.");
|
||||||
sb.AppendLine("NCA options:");
|
sb.AppendLine("NCA options:");
|
||||||
sb.AppendLine(" --section0 <file> Specify Section 0 file path.");
|
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(" --nspout <file> Specify file for the created NSP.");
|
||||||
sb.AppendLine("Package1 options:");
|
sb.AppendLine("Package1 options:");
|
||||||
sb.AppendLine(" --outdir <dir> Specify Package1 directory path.");
|
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("Switch FS options:");
|
||||||
sb.AppendLine(" --sdseed <seed> Set console unique seed for SD card NAX0 encryption.");
|
sb.AppendLine(" --sdseed <seed> Set console unique seed for SD card NAX0 encryption.");
|
||||||
sb.AppendLine(" --listapps List application info.");
|
sb.AppendLine(" --listapps List application info.");
|
||||||
|
|
|
@ -47,7 +47,8 @@ namespace hactoolnet
|
||||||
SwitchFs,
|
SwitchFs,
|
||||||
Save,
|
Save,
|
||||||
Keygen,
|
Keygen,
|
||||||
Pk11
|
Pk11,
|
||||||
|
Pk21
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class Context
|
internal class Context
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
using LibHac;
|
using LibHac;
|
||||||
|
using static hactoolnet.Print;
|
||||||
|
|
||||||
namespace hactoolnet
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,9 @@ namespace hactoolnet
|
||||||
case FileType.Pk11:
|
case FileType.Pk11:
|
||||||
ProcessPackage.ProcessPk11(ctx);
|
ProcessPackage.ProcessPk11(ctx);
|
||||||
break;
|
break;
|
||||||
|
case FileType.Pk21:
|
||||||
|
ProcessPackage.ProcessPk21(ctx);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue