Apply section CTR and do a pfs0 superblock validity check

This commit is contained in:
Alex Barney 2018-06-28 15:02:23 -05:00
parent d336c2e58d
commit 95ee755774
5 changed files with 108 additions and 25 deletions

View file

@ -9,8 +9,8 @@ namespace hactoolnet
{ {
static void Main(string[] args) static void Main(string[] args)
{ {
ReadNca(); //ReadNca();
//ListSdfs(args); ListSdfs(args);
} }
private static void ListSdfs(string[] args) private static void ListSdfs(string[] args)
@ -24,16 +24,16 @@ namespace hactoolnet
ListTitles(sdfs); ListTitles(sdfs);
//DecryptNax0(sdfs, "C0628FB07A89E9050BDA258F74868E8D"); //DecryptNax0(sdfs, "C0628FB07A89E9050BDA258F74868E8D");
DecryptTitle(sdfs, 0x010025400AECE000); //DecryptTitle(sdfs, 0x010023900AEE0000);
} }
static void ReadNca() static void ReadNca()
{ {
var keyset = ExternalKeys.ReadKeyFile("keys.txt", "titlekeys.txt"); var keyset = ExternalKeys.ReadKeyFile("keys.txt", "titlekeys.txt");
using (var file = new FileStream("acc9da939a8e0b339aa3be3d409d9ada.nca", FileMode.Open, FileAccess.Read)) using (var file = new FileStream("bf8b106a2e68df3c3f1694636423585a.nca", FileMode.Open, FileAccess.Read))
{ {
var nca = new Nca(keyset, file, false); var nca = new Nca(keyset, file, false);
var romfs = nca.OpenSection(0); var romfs = nca.OpenSection(0, false);
using (var output = new FileStream("romfs_net", FileMode.Create)) using (var output = new FileStream("romfs_net", FileMode.Create))
{ {
@ -77,7 +77,7 @@ namespace hactoolnet
static SdFs LoadSdFs(string[] args) static SdFs LoadSdFs(string[] args)
{ {
var keyset = ExternalKeys.ReadKeyFile(args[0]); var keyset = ExternalKeys.ReadKeyFile(args[0], "titlekeys.txt");
keyset.SetSdSeed(args[1].ToBytes()); keyset.SetSdSeed(args[1].ToBytes());
var sdfs = new SdFs(keyset, args[2]); var sdfs = new SdFs(keyset, args[2]);
return sdfs; return sdfs;
@ -103,6 +103,16 @@ namespace hactoolnet
$" {content.NcaId.ToHexString()}.nca {content.Type} {Util.GetBytesReadable(content.Size)}"); $" {content.NcaId.ToHexString()}.nca {content.Type} {Util.GetBytesReadable(content.Size)}");
} }
foreach (var nca in title.Ncas)
{
Console.WriteLine($" {nca.HasRightsId} {nca.NcaId} {nca.Header.ContentType}");
foreach (var sect in nca.Sections)
{
Console.WriteLine($" {sect.SectionNum} {sect.Type} {sect.Header.CryptType} {sect.SuperblockHashValidity}");
}
}
Console.WriteLine(""); Console.WriteLine("");
} }
} }

View file

@ -24,6 +24,7 @@
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
using System;
using System.IO; using System.IO;
using System.Security.Cryptography; using System.Security.Cryptography;
using libhac.XTSSharp; using libhac.XTSSharp;
@ -40,6 +41,7 @@ namespace libhac
/// </summary> /// </summary>
public const int DefaultSectorSize = 16; public const int DefaultSectorSize = 16;
private readonly byte[] _initialCounter;
private readonly long _counterOffset; private readonly long _counterOffset;
private readonly byte[] _tempBuffer; private readonly byte[] _tempBuffer;
private readonly Aes _aes; private readonly Aes _aes;
@ -62,9 +64,16 @@ namespace libhac
/// <param name="offset">Offset to start at in the input stream</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="length">The length of the created stream</param>
/// <param name="counterOffset">Offset to add to the counter</param> /// <param name="counterOffset">Offset to add to the counter</param>
public AesCtrStream(Stream baseStream, byte[] key, long offset, long length, long counterOffset) /// <param name="ctrHi">The value of the upper 64 bits of the counter</param>
public AesCtrStream(Stream baseStream, byte[] key, long offset, long length, long counterOffset, byte[] ctrHi = null)
: base(baseStream, 0x10, offset) : base(baseStream, 0x10, offset)
{ {
_initialCounter = new byte[0x10];
if (ctrHi != null)
{
Array.Copy(ctrHi, _initialCounter, 8);
}
_counterOffset = counterOffset; _counterOffset = counterOffset;
Length = length; Length = length;
_tempBuffer = new byte[0x10]; _tempBuffer = new byte[0x10];
@ -80,7 +89,7 @@ namespace libhac
private CounterModeCryptoTransform CreateDecryptor() private CounterModeCryptoTransform CreateDecryptor()
{ {
var dec = new CounterModeCryptoTransform(_aes, _aes.Key, new byte[0x10]); var dec = new CounterModeCryptoTransform(_aes, _aes.Key, _initialCounter ?? new byte[0x10]);
dec.UpdateCounter(_counterOffset + Position); dec.UpdateCounter(_counterOffset + Position);
return dec; return dec;
} }

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Security.Cryptography;
using libhac.XTSSharp; using libhac.XTSSharp;
namespace libhac namespace libhac
@ -34,15 +35,24 @@ namespace libhac
{ {
DecryptKeyArea(keyset); DecryptKeyArea(keyset);
} }
else
{
if (keyset.TitleKeys.TryGetValue(Header.RightsId, out var titleKey))
{
Crypto.DecryptEcb(keyset.titlekeks[CryptoType], titleKey, DecryptedKeys[2], 0x10);
}
}
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
var section = ParseSection(i); var section = ParseSection(i);
if (section != null) Sections.Add(section); if (section == null) continue;
Sections.Add(section);
ValidateSuperblockHash(i);
} }
} }
public Stream OpenSection(int index) public Stream OpenSection(int index, bool raw)
{ {
if (index >= Sections.Count) throw new ArgumentOutOfRangeException(nameof(index)); if (index >= Sections.Count) throw new ArgumentOutOfRangeException(nameof(index));
var sect = Sections[index]; var sect = Sections[index];
@ -50,18 +60,22 @@ namespace libhac
long offset = sect.Offset; long offset = sect.Offset;
long size = sect.Size; long size = sect.Size;
switch (sect.Header.FsType) if (!raw)
{ {
case SectionFsType.Pfs0: switch (sect.Header.FsType)
offset = sect.Offset + sect.Pfs0.Pfs0Offset; {
size = sect.Pfs0.Pfs0Size; case SectionFsType.Pfs0:
break; offset = sect.Offset + sect.Pfs0.Pfs0Offset;
case SectionFsType.Romfs: size = sect.Pfs0.Pfs0Size;
offset = sect.Offset + (long)sect.Header.Romfs.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1].LogicalOffset; break;
size = (long)sect.Header.Romfs.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1].HashDataSize; case SectionFsType.Romfs:
break; offset = sect.Offset + (long)sect.Header.Romfs.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1]
default: .LogicalOffset;
throw new ArgumentOutOfRangeException(); size = (long)sect.Header.Romfs.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1].HashDataSize;
break;
default:
throw new ArgumentOutOfRangeException();
}
} }
Stream.Position = offset; Stream.Position = offset;
@ -73,7 +87,7 @@ namespace libhac
case SectionCryptType.XTS: case SectionCryptType.XTS:
break; break;
case SectionCryptType.CTR: case SectionCryptType.CTR:
return new RandomAccessSectorStream(new AesCtrStream(Stream, DecryptedKeys[2], offset, size, offset), false); return new RandomAccessSectorStream(new AesCtrStream(Stream, DecryptedKeys[2], offset, size, offset, sect.Header.Ctr), false);
case SectionCryptType.BKTR: case SectionCryptType.BKTR:
break; break;
default: default:
@ -128,6 +142,47 @@ namespace libhac
return sect; return sect;
} }
private void ValidateSuperblockHash(int index)
{
if (index >= Sections.Count) throw new ArgumentOutOfRangeException(nameof(index));
var sect = Sections[index];
var stream = OpenSection(index, true);
byte[] expected = null;
byte[] actual;
long offset = 0;
long size = 0;
switch (sect.Type)
{
case SectionType.Invalid:
break;
case SectionType.Pfs0:
var pfs0 = sect.Header.Pfs0;
expected = pfs0.MasterHash;
offset = pfs0.HashTableOffset;
size = pfs0.HashTableSize;
break;
case SectionType.Romfs:
break;
case SectionType.Bktr:
break;
}
if (expected == null) return;
var hashTable = new byte[size];
stream.Position = offset;
stream.Read(hashTable, 0, hashTable.Length);
using (SHA256 hash = SHA256.Create())
{
actual = hash.ComputeHash(hashTable);
}
sect.SuperblockHashValidity = Util.ArraysEqual(expected, actual) ? Validity.Valid : Validity.Invalid;
}
public void Dispose() public void Dispose()
{ {
if (!KeepOpen) if (!KeepOpen)
@ -145,6 +200,7 @@ namespace libhac
public int SectionNum { get; set; } public int SectionNum { get; set; }
public long Offset { get; set; } public long Offset { get; set; }
public long Size { get; set; } public long Size { get; set; }
public Validity SuperblockHashValidity { get; set; }
public Pfs0Superblock Pfs0 { get; set; } public Pfs0Superblock Pfs0 { get; set; }
} }

View file

@ -1,4 +1,5 @@
using System.IO; using System.IO;
using System.Linq;
namespace libhac namespace libhac
{ {
@ -96,7 +97,7 @@ namespace libhac
public Pfs0Superblock Pfs0; public Pfs0Superblock Pfs0;
public RomfsSuperblock Romfs; public RomfsSuperblock Romfs;
public BktrSuperblock Bktr; public BktrSuperblock Bktr;
public ulong Ctr; public byte[] Ctr;
public NcaFsHeader(BinaryReader reader) public NcaFsHeader(BinaryReader reader)
{ {
@ -126,7 +127,7 @@ namespace libhac
} }
} }
Ctr = reader.ReadUInt64(); Ctr = reader.ReadBytes(8).Reverse().ToArray();
reader.BaseStream.Position += 184; reader.BaseStream.Position += 184;
} }
} }
@ -280,4 +281,11 @@ namespace libhac
Romfs, Romfs,
Bktr Bktr
} }
public enum Validity
{
Unchecked,
Invalid,
Valid
}
} }

View file

@ -62,7 +62,7 @@ namespace libhac
var title = new Title(); var title = new Title();
// Meta contents always have 1 Partition FS section with 1 file in it // Meta contents always have 1 Partition FS section with 1 file in it
Stream sect = nca.OpenSection(0); Stream sect = nca.OpenSection(0, false);
var pfs0 = new Pfs0(sect); var pfs0 = new Pfs0(sect);
var file = pfs0.GetFile(0); var file = pfs0.GetFile(0);