From 30273bf3ef1d3fe32ce1c6a71fa1f21426c04169 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sat, 15 Aug 2020 00:31:17 -0700 Subject: [PATCH] Avoid false positives in detection of unencrypted NCAs --- src/LibHac/FsSystem/NcaUtils/Nca.cs | 8 +--- src/LibHac/FsSystem/NcaUtils/NcaHeader.cs | 58 +++++++++++++++++------ 2 files changed, 46 insertions(+), 20 deletions(-) diff --git a/src/LibHac/FsSystem/NcaUtils/Nca.cs b/src/LibHac/FsSystem/NcaUtils/Nca.cs index 0d8c1388..f9ee9676 100644 --- a/src/LibHac/FsSystem/NcaUtils/Nca.cs +++ b/src/LibHac/FsSystem/NcaUtils/Nca.cs @@ -14,7 +14,7 @@ namespace LibHac.FsSystem.NcaUtils public class Nca { private Keyset Keyset { get; } - private bool IsEncrypted { get; set; } + private bool IsEncrypted => Header.IsEncrypted; private byte[] Nca0KeyArea { get; set; } private IStorage Nca0TransformedBody { get; set; } @@ -25,13 +25,9 @@ namespace LibHac.FsSystem.NcaUtils public Nca(Keyset keyset, IStorage storage) { - Span magic = stackalloc byte[3]; - storage.Read(0x200, magic); - IsEncrypted = magic[0] != 'N' && magic[1] != 'C' && magic[2] != 'A'; - Keyset = keyset; BaseStorage = storage; - Header = IsEncrypted ? new NcaHeader(keyset, storage) : new NcaHeader(storage); + Header = new NcaHeader(keyset, storage); } public byte[] GetDecryptedKey(int index) diff --git a/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs b/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs index 93cd7098..b0558233 100644 --- a/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs +++ b/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs @@ -4,6 +4,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Crypto; +using LibHac.Diag; using LibHac.Fs; namespace LibHac.FsSystem.NcaUtils @@ -17,21 +18,15 @@ namespace LibHac.FsSystem.NcaUtils private readonly Memory _header; - public NcaVersion FormatVersion { get; private set; } - - public NcaHeader(IStorage headerStorage) - { - _header = new byte[HeaderSize]; - Span headerSpan = _header.Span; - headerStorage.Read(0, headerSpan).ThrowIfFailure(); - - FormatVersion = DetectNcaVersion(headerSpan); - } + public NcaVersion FormatVersion { get; } + public bool IsEncrypted { get; } public NcaHeader(Keyset keyset, IStorage headerStorage) { - _header = DecryptHeader(keyset, headerStorage); + (byte[] header, bool isEncrypted) = DecryptHeader(keyset, headerStorage); + _header = header; + IsEncrypted = isEncrypted; FormatVersion = DetectNcaVersion(_header.Span); } @@ -200,10 +195,22 @@ namespace LibHac.FsSystem.NcaUtils return (long)blockIndex * BlockSize; } - public static byte[] DecryptHeader(Keyset keyset, IStorage storage) + private static (byte[] header, bool isEncrypted) DecryptHeader(Keyset keyset, IStorage storage) { var buf = new byte[HeaderSize]; - storage.Read(0, buf); + storage.Read(0, buf).ThrowIfFailure(); + + if (CheckIfDecrypted(buf)) + { + int decVersion = buf[0x203] - '0'; + + if (decVersion != 0 && decVersion != 2 && decVersion != 3) + { + throw new NotSupportedException($"NCA version {decVersion} is not supported."); + } + + return (buf, false); + } byte[] key1 = keyset.HeaderKey.AsSpan(0, 0x10).ToArray(); byte[] key2 = keyset.HeaderKey.AsSpan(0x10, 0x10).ToArray(); @@ -240,7 +247,30 @@ namespace LibHac.FsSystem.NcaUtils throw new NotSupportedException($"NCA version {version} is not supported."); } - return buf; + return (buf, true); + } + + private static bool CheckIfDecrypted(ReadOnlySpan header) + { + Assert.AssertTrue(header.Length >= 0x400); + + // Check the magic value + if (header[0x200] != 'N' || header[0x201] != 'C' || header[0x202] != 'A') + return false; + + // Check the version in the magic value + if (!StringUtils.IsDigit(header[0x203])) + return false; + + // Is the distribution type valid? + if (header[0x204] > (int)DistributionType.GameCard) + return false; + + // Is the content type valid? + if (header[0x205] > (int)NcaContentType.PublicData) + return false; + + return true; } private static NcaVersion DetectNcaVersion(ReadOnlySpan header)