From 2fb05b577e09aa259cd1d02ff1ba1eb22064be40 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 28 Sep 2021 21:32:30 -0700 Subject: [PATCH] Fix reading CIA header info --- NDecrypt.CMD/Program.cs | 2 +- NDecrypt/N3DS/CIATool.cs | 2 +- NDecrypt/N3DS/Enums.cs | 17 ++++++------ NDecrypt/N3DS/Headers/CIAHeader.cs | 36 ++++++++++++++++++++------ NDecrypt/N3DS/Headers/Certificate.cs | 21 +++++++++++++++ NDecrypt/N3DS/Headers/NCCHHeader.cs | 12 ++++----- NDecrypt/N3DS/Headers/NCSDHeader.cs | 2 +- NDecrypt/N3DS/Headers/Ticket.cs | 34 ++++++++++++++---------- NDecrypt/N3DS/Headers/TitleMetadata.cs | 16 ++++++++++-- NDecrypt/N3DS/ThreeDSTool.cs | 2 +- 10 files changed, 102 insertions(+), 42 deletions(-) diff --git a/NDecrypt.CMD/Program.cs b/NDecrypt.CMD/Program.cs index f873acb..63ddfa1 100644 --- a/NDecrypt.CMD/Program.cs +++ b/NDecrypt.CMD/Program.cs @@ -192,7 +192,7 @@ More than one path can be specified at a time."); Console.WriteLine("File recognized as Nintendo 3DS"); return new ThreeDSTool(filename, decryptArgs); // case FileType.N3DSCIA: - // Console.WriteLine("File recognized as Nintendo 3DS"); + // Console.WriteLine("File recognized as Nintendo 3DS CIA"); // return new CIATool(filename, decryptArgs); case FileType.NULL: default: diff --git a/NDecrypt/N3DS/CIATool.cs b/NDecrypt/N3DS/CIATool.cs index 34752df..308293e 100644 --- a/NDecrypt/N3DS/CIATool.cs +++ b/NDecrypt/N3DS/CIATool.cs @@ -5,7 +5,7 @@ using NDecrypt.N3DS.Headers; namespace NDecrypt.N3DS { // https://www.3dbrew.org/wiki/CIA - internal class CIATool : ITool + public class CIATool : ITool { /// /// Name of the input CIA file diff --git a/NDecrypt/N3DS/Enums.cs b/NDecrypt/N3DS/Enums.cs index 70b869d..93e1db3 100644 --- a/NDecrypt/N3DS/Enums.cs +++ b/NDecrypt/N3DS/Enums.cs @@ -186,8 +186,8 @@ namespace NDecrypt.N3DS internal enum PublicKeyType : uint { RSA_4096 = 0x00000000, - RSA_2048 = 0x00000001, - ECDSA = 0x00000002, + RSA_2048 = 0x01000000, + ECDSA = 0x02000000, } internal enum ResourceLimitCategory @@ -198,14 +198,15 @@ namespace NDecrypt.N3DS OTHER = 3, } + // Note: These are reversed because of how C# reads values internal enum SignatureType : uint { - RSA_4096_SHA1 = 0x010000, - RSA_2048_SHA1 = 0x010001, - ECDSA_SHA1 = 0x010002, - RSA_4096_SHA256 = 0x010003, - RSA_2048_SHA256 = 0x010004, - ECDSA_SHA256 = 0x010005, + RSA_4096_SHA1 = 0x00000100, + RSA_2048_SHA1 = 0x01000100, + ECDSA_SHA1 = 0x02000100, + RSA_4096_SHA256 = 0x03000100, + RSA_2048_SHA256 = 0x04000100, + ECDSA_SHA256 = 0x05000100, } [Flags] diff --git a/NDecrypt/N3DS/Headers/CIAHeader.cs b/NDecrypt/N3DS/Headers/CIAHeader.cs index ab1351d..ff6e33b 100644 --- a/NDecrypt/N3DS/Headers/CIAHeader.cs +++ b/NDecrypt/N3DS/Headers/CIAHeader.cs @@ -1,3 +1,4 @@ +using System; using System.IO; namespace NDecrypt.N3DS.Headers @@ -8,7 +9,7 @@ namespace NDecrypt.N3DS.Headers /// /// Archive header size, usually 0x2020 bytes /// - public uint HeaderSize { get; private set; } + public int HeaderSize { get; private set; } /// /// Type @@ -45,6 +46,11 @@ namespace NDecrypt.N3DS.Headers /// public long ContentSize { get; private set; } + /// + /// Content Index + /// + public int ContentIndex { get; private set; } + #region Content Index /// @@ -68,10 +74,7 @@ namespace NDecrypt.N3DS.Headers /// /// Content file data /// - /// - /// Probably not going to be read in fully later - /// - public byte[] ContentFileData { get; set; } + public NCCHHeader ContentFileData { get; set; } /// /// Meta file data (Not a necessary component) @@ -91,7 +94,7 @@ namespace NDecrypt.N3DS.Headers try { - header.HeaderSize = reader.ReadUInt32(); + header.HeaderSize = reader.ReadInt32(); header.Type = reader.ReadUInt16(); header.Version = reader.ReadUInt16(); header.CertificateChainSize = reader.ReadInt32(); @@ -99,16 +102,33 @@ namespace NDecrypt.N3DS.Headers header.TMDFileSize = reader.ReadInt32(); header.MetaSize = reader.ReadInt32(); header.ContentSize = reader.ReadInt64(); + header.ContentIndex = reader.ReadInt32(); + reader.ReadBytes(0x2000); // TODO: Not sure what's in the Content Index area + if (reader.BaseStream.Position % 64 != 0) + reader.BaseStream.Seek(64 - (reader.BaseStream.Position % 64), SeekOrigin.Current); header.CertificateChain = new Certificate[3]; header.CertificateChain[0] = Certificate.Read(reader); // CA header.CertificateChain[1] = Certificate.Read(reader); // Ticket header.CertificateChain[2] = Certificate.Read(reader); // TMD + if (reader.BaseStream.Position % 64 != 0) + reader.BaseStream.Seek(64 - (reader.BaseStream.Position % 64), SeekOrigin.Current); header.Ticket = Ticket.Read(reader, header.TicketSize); + if (reader.BaseStream.Position % 64 != 0) + reader.BaseStream.Seek(64 - (reader.BaseStream.Position % 64), SeekOrigin.Current); + header.TMDFileData = TitleMetadata.Read(reader, header.TMDFileSize); - header.ContentFileData = reader.ReadBytes((int)header.ContentSize); - + if (reader.BaseStream.Position % 64 != 0) + reader.BaseStream.Seek(64 - (reader.BaseStream.Position % 64), SeekOrigin.Current); + + header.ContentFileData = NCCHHeader.Read(reader, readSignature: true); + if (header.ContentFileData == null) + { + Console.WriteLine($"CIA content file data error: Unable to read NCCH header"); + return null; + } + if (header.MetaSize > 0) header.MetaFileData = MetaFile.Read(reader); diff --git a/NDecrypt/N3DS/Headers/Certificate.cs b/NDecrypt/N3DS/Headers/Certificate.cs index b9accbb..6e67c84 100644 --- a/NDecrypt/N3DS/Headers/Certificate.cs +++ b/NDecrypt/N3DS/Headers/Certificate.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Text; namespace NDecrypt.N3DS.Headers { @@ -30,6 +31,13 @@ namespace NDecrypt.N3DS.Headers /// public byte[] Issuer { get; private set; } + /// + /// Issuer as a trimmed string + /// + public string IssuerString => Issuer != null && Issuer.Length > 0 + ? Encoding.ASCII.GetString(Issuer)?.TrimEnd('\0') + : null; + /// /// Key Type /// @@ -40,6 +48,13 @@ namespace NDecrypt.N3DS.Headers /// public byte[] Name { get; private set; } + /// + /// Name as a trimmed string + /// + public string NameString => Name != null && Name.Length > 0 + ? Encoding.ASCII.GetString(Name)?.TrimEnd('\0') + : null; + /// /// Expiration time as UNIX Timestamp, used at least for CTCert /// @@ -99,6 +114,8 @@ namespace NDecrypt.N3DS.Headers ct.SignatureSize = 0x03C; ct.PaddingSize = 0x40; break; + default: + return null; } ct.Signature = reader.ReadBytes(ct.SignatureSize); @@ -116,14 +133,18 @@ namespace NDecrypt.N3DS.Headers reader.ReadBytes(0x34); // Padding break; case PublicKeyType.RSA_2048: + case PublicKeyType.RSA_2048_REV: ct.Modulus = reader.ReadBytes(0x100); ct.PublicExponent = reader.ReadUInt32(); reader.ReadBytes(0x34); // Padding break; case PublicKeyType.ECDSA: + case PublicKeyType.ECDSA_REV: ct.PublicKey = reader.ReadBytes(0x3C); reader.ReadBytes(0x3C); // Padding break; + default: + return null; } return ct; diff --git a/NDecrypt/N3DS/Headers/NCCHHeader.cs b/NDecrypt/N3DS/Headers/NCCHHeader.cs index b33f629..6644f68 100644 --- a/NDecrypt/N3DS/Headers/NCCHHeader.cs +++ b/NDecrypt/N3DS/Headers/NCCHHeader.cs @@ -64,12 +64,12 @@ namespace NDecrypt.N3DS.Headers /// /// Maker code /// - public byte[] MakerCode { get; private set; } + public ushort MakerCode { get; private set; } /// /// Version /// - public byte[] Version { get; private set; } + public ushort Version { get; private set; } /// /// When ncchflag[7] = 0x20 starting with FIRM 9.6.0-X, this is compared with the first output u32 from a @@ -77,7 +77,7 @@ namespace NDecrypt.N3DS.Headers /// [programID from NCCH + 0x118]. This hash is only used for verification of the content lock seed, and /// is not the actual keyY. /// - public byte[] VerificationHash { get; private set; } + public uint VerificationHash { get; private set; } /// /// Program ID @@ -211,9 +211,9 @@ namespace NDecrypt.N3DS.Headers header.ContentSizeInMediaUnits = reader.ReadUInt32(); header.PartitionId = reader.ReadBytes(8).Reverse().ToArray(); - header.MakerCode = reader.ReadBytes(2); - header.Version = reader.ReadBytes(2); - header.VerificationHash = reader.ReadBytes(4); + header.MakerCode = reader.ReadUInt16(); + header.Version = reader.ReadUInt16(); + header.VerificationHash = reader.ReadUInt32(); header.ProgramId = reader.ReadBytes(8); header.Reserved1 = reader.ReadBytes(0x10); header.LogoRegionHash = reader.ReadBytes(0x20); diff --git a/NDecrypt/N3DS/Headers/NCSDHeader.cs b/NDecrypt/N3DS/Headers/NCSDHeader.cs index 6f262f2..7097eeb 100644 --- a/NDecrypt/N3DS/Headers/NCSDHeader.cs +++ b/NDecrypt/N3DS/Headers/NCSDHeader.cs @@ -299,7 +299,7 @@ namespace NDecrypt.N3DS.Headers header.CardSeedAESMAC = reader.ReadBytes(0x10); header.CardSeedNonce = reader.ReadBytes(0xC); header.Reserved5 = reader.ReadBytes(0xC4); - header.BackupHeader = NCCHHeader.Read(reader, false); + header.BackupHeader = NCCHHeader.Read(reader, readSignature: false); if (development) { diff --git a/NDecrypt/N3DS/Headers/Ticket.cs b/NDecrypt/N3DS/Headers/Ticket.cs index 2af9e6e..08fb501 100644 --- a/NDecrypt/N3DS/Headers/Ticket.cs +++ b/NDecrypt/N3DS/Headers/Ticket.cs @@ -1,4 +1,7 @@ +using System; using System.IO; +using System.Linq; +using System.Text; namespace NDecrypt.N3DS.Headers { @@ -30,6 +33,13 @@ namespace NDecrypt.N3DS.Headers /// public byte[] Issuer { get; private set; } + /// + /// Issuer as a trimmed string + /// + public string IssuerString => Issuer != null && Issuer.Length > 0 + ? Encoding.ASCII.GetString(Issuer)?.TrimEnd('\0') + : null; + /// /// ECC PublicKey /// @@ -166,16 +176,6 @@ namespace NDecrypt.N3DS.Headers /// public int ContentIndexSize { get; private set; } - /// - /// Content Index - /// - /// - /// The Content Index of a ticket has its own size defined within itself, - /// with seemingly a minimal of 20 bytes, the second u32 in big endian defines - /// the full value of X. - /// - public byte[] ContentIndex { get; private set; } - /// /// Certificate chain /// @@ -196,6 +196,8 @@ namespace NDecrypt.N3DS.Headers try { + long startingPosition = reader.BaseStream.Position; + tk.SignatureType = (SignatureType)reader.ReadUInt32(); switch (tk.SignatureType) { @@ -214,6 +216,8 @@ namespace NDecrypt.N3DS.Headers tk.SignatureSize = 0x03C; tk.PaddingSize = 0x40; break; + default: + return null; } tk.Signature = reader.ReadBytes(tk.SignatureSize); @@ -245,12 +249,14 @@ namespace NDecrypt.N3DS.Headers tk.Limits[i] = reader.ReadInt32(); } - reader.ReadBytes(4); - tk.ContentIndexSize = reader.ReadInt32(); + reader.ReadBytes(4); // Seek to size in Content Index + tk.ContentIndexSize = BitConverter.ToInt32(reader.ReadBytes(4).Reverse().ToArray(), 0); reader.BaseStream.Seek(-8, SeekOrigin.Current); - tk.ContentIndex = reader.ReadBytes(tk.ContentIndexSize); + reader.ReadBytes(tk.ContentIndexSize); // TODO: Not sure what's in the Content Index area + if (reader.BaseStream.Position % 64 != 0) + reader.BaseStream.Seek(64 - (reader.BaseStream.Position % 64), SeekOrigin.Current); - if (ticketSize - (0x164 + tk.ContentIndexSize) > 0) + if (ticketSize > (reader.BaseStream.Position - startingPosition) + (2 * 0x200)) { tk.CertificateChain = new Certificate[2]; tk.CertificateChain[0] = Certificate.Read(reader); // Ticket diff --git a/NDecrypt/N3DS/Headers/TitleMetadata.cs b/NDecrypt/N3DS/Headers/TitleMetadata.cs index f52983b..257e546 100644 --- a/NDecrypt/N3DS/Headers/TitleMetadata.cs +++ b/NDecrypt/N3DS/Headers/TitleMetadata.cs @@ -1,4 +1,7 @@ +using System; using System.IO; +using System.Linq; +using System.Text; namespace NDecrypt.N3DS.Headers { @@ -30,6 +33,13 @@ namespace NDecrypt.N3DS.Headers /// public byte[] SignatureIssuer { get; private set; } + /// + /// Signature Issuer as a trimmed string + /// + public string SignatureIssuerString => SignatureIssuer != null && SignatureIssuer.Length > 0 + ? Encoding.ASCII.GetString(SignatureIssuer)?.TrimEnd('\0') + : null; + /// /// Version /// @@ -156,6 +166,8 @@ namespace NDecrypt.N3DS.Headers try { + long startingPosition = reader.BaseStream.Position; + tm.SignatureType = (SignatureType)reader.ReadUInt32(); switch (tm.SignatureType) { @@ -194,7 +206,7 @@ namespace NDecrypt.N3DS.Headers tm.Reserved3 = reader.ReadBytes(0x31); tm.AccessRights = reader.ReadUInt32(); tm.TitleVersion = reader.ReadUInt16(); - tm.ContentCount = reader.ReadUInt16(); + tm.ContentCount = BitConverter.ToUInt16(reader.ReadBytes(2).Reverse().ToArray(), 0); tm.BootContent = reader.ReadUInt16(); tm.Padding = reader.ReadUInt16(); tm.SHA256HashContentInfoRecords = reader.ReadBytes(0x20); @@ -211,7 +223,7 @@ namespace NDecrypt.N3DS.Headers tm.ContentChunkRecords[i] = ContentChunkRecord.Read(reader); } - if (metadataSize - (0xC4 + (64 * 0x24) + (tm.ContentCount * 0x30)) > 0) + if (metadataSize > (reader.BaseStream.Position - startingPosition) + (2 * 0x200)) { tm.CertificateChain = new Certificate[2]; tm.CertificateChain[0] = Certificate.Read(reader); // TMD diff --git a/NDecrypt/N3DS/ThreeDSTool.cs b/NDecrypt/N3DS/ThreeDSTool.cs index 4cb27a2..d031d78 100644 --- a/NDecrypt/N3DS/ThreeDSTool.cs +++ b/NDecrypt/N3DS/ThreeDSTool.cs @@ -103,7 +103,7 @@ namespace NDecrypt.N3DS // Seek to the beginning of the NCCH partition reader.BaseStream.Seek((ncsdHeader.PartitionsTable[partitionNumber].Offset * ncsdHeader.MediaUnitSize), SeekOrigin.Begin); - NCCHHeader partitionHeader = NCCHHeader.Read(reader, true); + NCCHHeader partitionHeader = NCCHHeader.Read(reader, readSignature: true); if (partitionHeader == null) { Console.WriteLine($"Partition {partitionNumber} Unable to read NCCH header");