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;
|
||||
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>
|
||||
|
|
|
@ -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
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;
|
||||
_keepOpen = keepOpen;
|
||||
_maxBufferSize = MaxSectors * SectorSize;
|
||||
baseStream.Position = offset;
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -47,7 +47,8 @@ namespace hactoolnet
|
|||
SwitchFs,
|
||||
Save,
|
||||
Keygen,
|
||||
Pk11
|
||||
Pk11,
|
||||
Pk21
|
||||
}
|
||||
|
||||
internal class Context
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,9 @@ namespace hactoolnet
|
|||
case FileType.Pk11:
|
||||
ProcessPackage.ProcessPk11(ctx);
|
||||
break;
|
||||
case FileType.Pk21:
|
||||
ProcessPackage.ProcessPk21(ctx);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue