diff --git a/hactoolnet/Program.cs b/hactoolnet/Program.cs index 726ce303..f1b2805d 100644 --- a/hactoolnet/Program.cs +++ b/hactoolnet/Program.cs @@ -9,8 +9,8 @@ namespace hactoolnet { static void Main(string[] args) { - ReadNca(); - //ListSdfs(args); + //ReadNca(); + ListSdfs(args); } private static void ListSdfs(string[] args) @@ -24,16 +24,16 @@ namespace hactoolnet ListTitles(sdfs); //DecryptNax0(sdfs, "C0628FB07A89E9050BDA258F74868E8D"); - DecryptTitle(sdfs, 0x010025400AECE000); + //DecryptTitle(sdfs, 0x010023900AEE0000); } static void ReadNca() { 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 romfs = nca.OpenSection(0); + var romfs = nca.OpenSection(0, false); using (var output = new FileStream("romfs_net", FileMode.Create)) { @@ -77,7 +77,7 @@ namespace hactoolnet static SdFs LoadSdFs(string[] args) { - var keyset = ExternalKeys.ReadKeyFile(args[0]); + var keyset = ExternalKeys.ReadKeyFile(args[0], "titlekeys.txt"); keyset.SetSdSeed(args[1].ToBytes()); var sdfs = new SdFs(keyset, args[2]); return sdfs; @@ -103,6 +103,16 @@ namespace hactoolnet $" {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(""); } } diff --git a/libhac/AesCtrStream.cs b/libhac/AesCtrStream.cs index d4fb33ae..3938088b 100644 --- a/libhac/AesCtrStream.cs +++ b/libhac/AesCtrStream.cs @@ -24,6 +24,7 @@ // 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. +using System; using System.IO; using System.Security.Cryptography; using libhac.XTSSharp; @@ -40,6 +41,7 @@ namespace libhac /// public const int DefaultSectorSize = 16; + private readonly byte[] _initialCounter; private readonly long _counterOffset; private readonly byte[] _tempBuffer; private readonly Aes _aes; @@ -62,9 +64,16 @@ namespace libhac /// Offset to start at in the input stream /// The length of the created stream /// Offset to add to the counter - public AesCtrStream(Stream baseStream, byte[] key, long offset, long length, long counterOffset) + /// The value of the upper 64 bits of the counter + public AesCtrStream(Stream baseStream, byte[] key, long offset, long length, long counterOffset, byte[] ctrHi = null) : base(baseStream, 0x10, offset) { + _initialCounter = new byte[0x10]; + if (ctrHi != null) + { + Array.Copy(ctrHi, _initialCounter, 8); + } + _counterOffset = counterOffset; Length = length; _tempBuffer = new byte[0x10]; @@ -80,7 +89,7 @@ namespace libhac 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); return dec; } diff --git a/libhac/Nca.cs b/libhac/Nca.cs index 14a65c63..36bea252 100644 --- a/libhac/Nca.cs +++ b/libhac/Nca.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Security.Cryptography; using libhac.XTSSharp; namespace libhac @@ -34,15 +35,24 @@ namespace libhac { 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++) { 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)); var sect = Sections[index]; @@ -50,18 +60,22 @@ namespace libhac long offset = sect.Offset; long size = sect.Size; - switch (sect.Header.FsType) + if (!raw) { - case SectionFsType.Pfs0: - offset = sect.Offset + sect.Pfs0.Pfs0Offset; - size = sect.Pfs0.Pfs0Size; - break; - case SectionFsType.Romfs: - offset = sect.Offset + (long)sect.Header.Romfs.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1].LogicalOffset; - size = (long)sect.Header.Romfs.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1].HashDataSize; - break; - default: - throw new ArgumentOutOfRangeException(); + switch (sect.Header.FsType) + { + case SectionFsType.Pfs0: + offset = sect.Offset + sect.Pfs0.Pfs0Offset; + size = sect.Pfs0.Pfs0Size; + break; + case SectionFsType.Romfs: + offset = sect.Offset + (long)sect.Header.Romfs.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1] + .LogicalOffset; + size = (long)sect.Header.Romfs.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1].HashDataSize; + break; + default: + throw new ArgumentOutOfRangeException(); + } } Stream.Position = offset; @@ -73,7 +87,7 @@ namespace libhac case SectionCryptType.XTS: break; 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: break; default: @@ -128,6 +142,47 @@ namespace libhac 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() { if (!KeepOpen) @@ -145,6 +200,7 @@ namespace libhac public int SectionNum { get; set; } public long Offset { get; set; } public long Size { get; set; } + public Validity SuperblockHashValidity { get; set; } public Pfs0Superblock Pfs0 { get; set; } } diff --git a/libhac/NcaStructs.cs b/libhac/NcaStructs.cs index 74d1de23..eafa0666 100644 --- a/libhac/NcaStructs.cs +++ b/libhac/NcaStructs.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Linq; namespace libhac { @@ -96,7 +97,7 @@ namespace libhac public Pfs0Superblock Pfs0; public RomfsSuperblock Romfs; public BktrSuperblock Bktr; - public ulong Ctr; + public byte[] Ctr; public NcaFsHeader(BinaryReader reader) { @@ -126,7 +127,7 @@ namespace libhac } } - Ctr = reader.ReadUInt64(); + Ctr = reader.ReadBytes(8).Reverse().ToArray(); reader.BaseStream.Position += 184; } } @@ -280,4 +281,11 @@ namespace libhac Romfs, Bktr } + + public enum Validity + { + Unchecked, + Invalid, + Valid + } } diff --git a/libhac/SdFs.cs b/libhac/SdFs.cs index 966a189b..a6449ac5 100644 --- a/libhac/SdFs.cs +++ b/libhac/SdFs.cs @@ -62,7 +62,7 @@ namespace libhac var title = new Title(); // 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 file = pfs0.GetFile(0);