From e406dd642c500d6a89fd74d3894dee76b126ce60 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko Date: Thu, 5 Jan 2023 18:22:29 +0300 Subject: [PATCH] Rename AesCtr classes used for NCA's media blocks decryption, add KIP1, KernelMap, INI1 classes that helps to retrieve information from headers, export, decrypt, etc. See Package2UnpackedTest.java for details. --- .../libKonogonka/Tools/NCA/NCAContent.java | 6 +- .../NPDM/KernelAccessControlProvider.java | 99 ++++--- .../libKonogonka/Tools/NSO/SegmentHeader.java | 6 +- .../Tools/other/System2/KernelMap.java | 118 +++++++++ .../Tools/other/System2/System2Header.java | 9 +- .../Tools/other/System2/System2Provider.java | 134 +++++----- .../Tools/other/System2/ini1/Ini1Header.java | 55 ++++ .../other/System2/ini1/Ini1Provider.java | 135 ++++++++++ .../Tools/other/System2/ini1/Kip1.java | 167 ++++++++++++ .../ctraes/AesCtrBufferedInputStream.java | 6 +- ....java => AesCtrDecryptForMediaBlocks.java} | 41 ++- .../ctraes/AesCtrForMediaBlocks.java | 53 ---- .../ctraes/InFileStreamProducer.java | 4 +- .../AesCtrClassicBufferedInputStream.java | 249 ++++++++++++++++++ .../ctraesclassic/AesCtrDecryptClassic.java | 81 ++++++ .../AesCtrStream.java} | 27 +- .../RomFsDecrypted/NSPpfs0EncryptedTest.java | 4 +- .../libKonogonka/RomFsDecrypted/PFS0Test.java | 5 - .../RomFsDecrypted/Package2UnpackedTest.java | 16 +- .../RomFsDecrypted/Pfs0EncryptedTest.java | 6 +- .../RomFsDecrypted/RomFsEncryptedTest.java | 13 +- 21 files changed, 989 insertions(+), 245 deletions(-) create mode 100644 src/main/java/libKonogonka/Tools/other/System2/KernelMap.java create mode 100644 src/main/java/libKonogonka/Tools/other/System2/ini1/Ini1Header.java create mode 100644 src/main/java/libKonogonka/Tools/other/System2/ini1/Ini1Provider.java create mode 100644 src/main/java/libKonogonka/Tools/other/System2/ini1/Kip1.java rename src/main/java/libKonogonka/ctraes/{AesCtrDecryptSimple.java => AesCtrDecryptForMediaBlocks.java} (62%) delete mode 100644 src/main/java/libKonogonka/ctraes/AesCtrForMediaBlocks.java create mode 100644 src/main/java/libKonogonka/ctraesclassic/AesCtrClassicBufferedInputStream.java create mode 100644 src/main/java/libKonogonka/ctraesclassic/AesCtrDecryptClassic.java rename src/main/java/libKonogonka/{ctraes/AesCtrClassic.java => ctraesclassic/AesCtrStream.java} (74%) diff --git a/src/main/java/libKonogonka/Tools/NCA/NCAContent.java b/src/main/java/libKonogonka/Tools/NCA/NCAContent.java index 0b02496..4052b35 100644 --- a/src/main/java/libKonogonka/Tools/NCA/NCAContent.java +++ b/src/main/java/libKonogonka/Tools/NCA/NCAContent.java @@ -22,7 +22,7 @@ import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader; import libKonogonka.Tools.PFS0.PFS0Provider; import libKonogonka.Tools.RomFs.RomFsProvider; import libKonogonka.ctraes.AesCtrBufferedInputStream; -import libKonogonka.ctraes.AesCtrDecryptSimple; +import libKonogonka.ctraes.AesCtrDecryptForMediaBlocks; import libKonogonka.ctraes.InFileStreamProducer; import libKonogonka.exceptions.EmptySectionException; import org.apache.logging.log4j.LogManager; @@ -119,7 +119,7 @@ public class NCAContent { public RomFsProvider getRomfs() { return romfs; } private InFileStreamProducer makeEncryptedProducer() throws Exception{ - AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(decryptedKey, ncaFsHeader.getSectionCTR(), + AesCtrDecryptForMediaBlocks decryptor = new AesCtrDecryptForMediaBlocks(decryptedKey, ncaFsHeader.getSectionCTR(), ncaHeaderTableEntry.getMediaStartOffset() * 0x200); return new InFileStreamProducer(file, ncaOffsetPosition, 0, decryptor, ncaHeaderTableEntry.getMediaStartOffset(), ncaHeaderTableEntry.getMediaEndOffset()); @@ -146,7 +146,7 @@ public class NCAContent { stream = new BufferedInputStream(Files.newInputStream(file.toPath())); } else if(ncaFsHeader.getCryptoType()==0x03) { - AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(decryptedKey, + AesCtrDecryptForMediaBlocks decryptor = new AesCtrDecryptForMediaBlocks(decryptedKey, ncaFsHeader.getSectionCTR(), mediaStartOffset * 0x200); diff --git a/src/main/java/libKonogonka/Tools/NPDM/KernelAccessControlProvider.java b/src/main/java/libKonogonka/Tools/NPDM/KernelAccessControlProvider.java index 2f3cb97..ecd4688 100644 --- a/src/main/java/libKonogonka/Tools/NPDM/KernelAccessControlProvider.java +++ b/src/main/java/libKonogonka/Tools/NPDM/KernelAccessControlProvider.java @@ -77,67 +77,59 @@ for (byte i = 0; i < 16; i++){ public class KernelAccessControlProvider { private final static Logger log = LogManager.getLogger(KernelAccessControlProvider.class); - private static final int KERNELFLAGS = 3, - SYSCALLMASK = 4, - MAPIOORNORMALRANGE = 6, - MAPNORMALPAGE_RW = 7, - INTERRUPTPAIR = 11, - APPLICATIONTYPE = 13, - KERNELRELEASEVERSION = 14, - HANDLETABLESIZE = 15, - DEBUGFLAGS = 16; - + private static final int KERNELFLAGS = 3, + SYSCALLMASK = 4, + MAPIOORNORMALRANGE = 6, + MAPNORMALPAGE_RW = 7, + INTERRUPTPAIR = 11, + APPLICATIONTYPE = 13, + KERNELRELEASEVERSION = 14, + HANDLETABLESIZE = 15, + DEBUGFLAGS = 16; // RAW data private final LinkedList rawData; // Kernel flags private boolean kernelFlagsAvailable; - private int kernelFlagCpuIdHi, - kernelFlagCpuIdLo, - kernelFlagThreadPrioHi, - kernelFlagThreadPrioLo; + private int kernelFlagCpuIdHi; + private int kernelFlagCpuIdLo; + private int kernelFlagThreadPrioHi; + private int kernelFlagThreadPrioLo; // Syscall Masks as index | mask - order AS IS. [0] = bit5; [1] = bit6 private final LinkedHashMap syscallMasks; // Index, Mask // MapIoOrNormalRange private final LinkedHashMap mapIoOrNormalRange; // alt page+num, RO flag // MapNormalPage (RW) private byte[] mapNormalPage; // TODO: clarify is possible to have multiple - // InterruptPair + private final LinkedHashMap interruptPairs; // Number; irq0, irq2 - // Application type + private int applicationType; - // KernelReleaseVersion + private boolean isKernelRelVersionAvailable; - private int kernelRelVersionMajor, - kernelRelVersionMinor; - // Handle Table Size + private int kernelRelVersionMajor; + private int kernelRelVersionMinor; + private int handleTableSize; // Debug flags - private boolean debugFlagsAvailable, - canBeDebugged, - canDebugOthers; + private boolean debugFlagsAvailable; + private boolean canBeDebugged; + private boolean canDebugOthers; public KernelAccessControlProvider(byte[] bytes) throws Exception{ if (bytes.length < 4) throw new Exception("ACID-> KernelAccessControlProvider: too small size of the Kernel Access Control"); - rawData = new LinkedList(); + this.rawData = new LinkedList<>(); + this.interruptPairs = new LinkedHashMap<>(); + this.syscallMasks = new LinkedHashMap<>(); + this.mapIoOrNormalRange = new LinkedHashMap<>(); - interruptPairs = new LinkedHashMap<>(); - syscallMasks = new LinkedHashMap(); - mapIoOrNormalRange = new LinkedHashMap(); - - int position = 0; // Collect all blocks - for (int i = 0; i < bytes.length / 4; i++) { + for (int position = 0; position < bytes.length; position += 4) { int block = Converter.getLEint(bytes, position); - position += 4; rawData.add(block); - - //RainbowHexDump.octDumpInt(block); - - int type = getMinBitCnt(block); - + int type = findBitsCount(block); switch (type){ case KERNELFLAGS: kernelFlagsAvailable = true; @@ -145,42 +137,39 @@ public class KernelAccessControlProvider { kernelFlagCpuIdLo = block >> 16 & 0b11111111; kernelFlagThreadPrioHi = block >> 10 & 0b111111; kernelFlagThreadPrioLo = block >> 4 & 0b111111; - //log.debug("KERNELFLAGS "+kernelFlagCpuIdHi+" "+kernelFlagCpuIdLo+" "+kernelFlagThreadPrioHi+" "+kernelFlagThreadPrioLo); + log.trace("KERNELFLAGS "+kernelFlagCpuIdHi+" "+kernelFlagCpuIdLo+" "+kernelFlagThreadPrioHi+" "+kernelFlagThreadPrioLo); break; case SYSCALLMASK: byte maskTableIndex = (byte) (block >> 29 & 0b111); // declared as byte; max value could be 7; min - 0; byte[] mask = new byte[24]; // Consider as bit. - //log.debug("SYSCALLMASK ind: "+maskTableIndex); + log.trace("SYSCALLMASK ind: "+maskTableIndex); for (int k = 28; k >= 5; k--) { mask[k-5] = (byte) (block >> k & 1); // Only 1 or 0 possible - //log.debug(" " + mask[k-5]); + log.trace("["+(k-4)+"/24]\t" + mask[k-5]); } - //log.debug(); syscallMasks.put(maskTableIndex, mask); break; case MAPIOORNORMALRANGE: byte[] altStPgNPgNum = new byte[24]; - //log.debug("MAPIOORNORMALRANGE Flag: "+((block >> 31 & 1) != 0)); + log.trace("MAPIOORNORMALRANGE Flag: "+((block >> 31 & 1) != 0)); for (int k = 30; k >= 7; k--){ altStPgNPgNum[k-7] = (byte) (block >> k & 1); // Only 1 or 0 possible - //log.debug(" " + altStPgNPgNum[k-7]); + log.trace(" " + altStPgNPgNum[k-7]); } mapIoOrNormalRange.put(altStPgNPgNum, (block >> 31 & 1) != 0); - //log.debug(); break; case MAPNORMALPAGE_RW: - //log.debug("MAPNORMALPAGE_RW\t"); + log.trace("MAPNORMALPAGE_RW\t"); mapNormalPage = new byte[24]; for (int k = 31; k >= 8; k--){ mapNormalPage[k-8] = (byte) (block >> k & 1); - //log.debug(" " + mapNormalPage[k-8]); + log.trace(" " + mapNormalPage[k-8]); } - //log.debug(); break; case INTERRUPTPAIR: - //log.debug("INTERRUPTPAIR"); + log.trace("INTERRUPTPAIR"); //RainbowHexDump.octDumpInt(block); byte[][] pair = new byte[2][]; byte[] irq0 = new byte[10]; @@ -196,33 +185,35 @@ public class KernelAccessControlProvider { break; case APPLICATIONTYPE: applicationType = block >> 14 & 0b111; - //log.debug("APPLICATIONTYPE "+applicationType); + log.trace("APPLICATIONTYPE "+applicationType); break; case KERNELRELEASEVERSION: - //log.debug("KERNELRELEASEVERSION\t"+(block >> 19 & 0b111111111111)+"."+(block >> 15 & 0b1111)+".X"); + log.trace("KERNELRELEASEVERSION\t"+(block >> 19 & 0b111111111111)+"."+(block >> 15 & 0b1111)+".X"); isKernelRelVersionAvailable = true; kernelRelVersionMajor = (block >> 19 & 0b111111111111); kernelRelVersionMinor = (block >> 15 & 0b1111); break; case HANDLETABLESIZE: handleTableSize = block >> 16 & 0b1111111111; - //log.debug("HANDLETABLESIZE "+handleTableSize); + log.trace("HANDLETABLESIZE "+handleTableSize); break; case DEBUGFLAGS: debugFlagsAvailable = true; canBeDebugged = (block >> 17 & 1) != 0; canDebugOthers = (block >> 18 & 1) != 0; - //log.debug("DEBUGFLAGS "+canBeDebugged+" "+canDebugOthers); + log.trace("DEBUGFLAGS "+canBeDebugged+" "+canDebugOthers); break; default: - log.error("UNKNOWN\t\t"+block+" "+type); + log.warn("INVALID ind:0b"+Integer.toBinaryString(block)); } } } - private int getMinBitCnt(int value){ + private int findBitsCount(int value){ int minBitCnt = 0; - while ((value & 1) != 0){ + for (int i = 0; i < 32; i++){ + if((value & 1) == 0) + break; value >>= 1; minBitCnt++; } diff --git a/src/main/java/libKonogonka/Tools/NSO/SegmentHeader.java b/src/main/java/libKonogonka/Tools/NSO/SegmentHeader.java index 445cd68..24dd92a 100644 --- a/src/main/java/libKonogonka/Tools/NSO/SegmentHeader.java +++ b/src/main/java/libKonogonka/Tools/NSO/SegmentHeader.java @@ -29,7 +29,7 @@ public class SegmentHeader { this(data, 0); } - SegmentHeader(byte[] data, int fromOffset){ + public SegmentHeader(byte[] data, int fromOffset){ this.segmentOffset = Converter.getLEint(data, fromOffset); this.memoryOffset = Converter.getLEint(data, fromOffset+4); this.sizeAsDecompressed = Converter.getLEint(data, fromOffset+8); @@ -42,7 +42,9 @@ public class SegmentHeader { public int getMemoryOffset() { return memoryOffset; } - + /** + * @return Size of compressed if used in KIP1 + * */ public int getSizeAsDecompressed() { return sizeAsDecompressed; } diff --git a/src/main/java/libKonogonka/Tools/other/System2/KernelMap.java b/src/main/java/libKonogonka/Tools/other/System2/KernelMap.java new file mode 100644 index 0000000..cb3aa91 --- /dev/null +++ b/src/main/java/libKonogonka/Tools/other/System2/KernelMap.java @@ -0,0 +1,118 @@ +/* + Copyright 2019-2023 Dmitry Isaenko + + This file is part of libKonogonka. + + libKonogonka is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libKonogonka is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libKonogonka. If not, see . + */ +package libKonogonka.Tools.other.System2; + +import libKonogonka.Converter; +import libKonogonka.RainbowDump; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class KernelMap { + private final static Logger log = LogManager.getLogger(KernelMap.class); + + private final int textStartOffset; + private final int textEndOffset; + private final int rodataStartOffset; + private final int rodataEndOffset; + private final int dataStartOffset; + private final int dataEndOffset; + private final int bssStartOffset; + private final int bssEndOffset; + private final int ini1Offset; + private final int dynamicOffset; + private final int initArrayStartOffset; + private final int initArrayEndOffset; + + public KernelMap(byte[] mapBytes, int offset){ + textStartOffset = Converter.getLEint(mapBytes, offset); + textEndOffset = Converter.getLEint(mapBytes, offset + 0x4); + rodataStartOffset = Converter.getLEint(mapBytes, offset + 0x8); + rodataEndOffset = Converter.getLEint(mapBytes, offset + 0xC); + dataStartOffset = Converter.getLEint(mapBytes, offset + 0x10); + dataEndOffset = Converter.getLEint(mapBytes, offset + 0x14); + bssStartOffset = Converter.getLEint(mapBytes, offset + 0x18); + bssEndOffset = Converter.getLEint(mapBytes, offset + 0x1C); + ini1Offset = Converter.getLEint(mapBytes, offset + 0x20); + dynamicOffset = Converter.getLEint(mapBytes, offset + 0x24); + initArrayStartOffset = Converter.getLEint(mapBytes, offset + 0x28); + initArrayEndOffset = Converter.getLEint(mapBytes, offset + 0x2C); + } + + public int getTextStartOffset() { return textStartOffset; } + public int getTextEndOffset() { return textEndOffset; } + public int getRodataStartOffset() { return rodataStartOffset; } + public int getRodataEndOffset() { return rodataEndOffset; } + public int getDataStartOffset() { return dataStartOffset; } + public int getDataEndOffset() { return dataEndOffset; } + public int getBssStartOffset() { return bssStartOffset; } + public int getBssEndOffset() { return bssEndOffset; } + public int getIni1Offset() { return ini1Offset; } + public int getDynamicOffset() { return dynamicOffset; } + public int getInitArrayStartOffset() { return initArrayStartOffset; } + public int getInitArrayEndOffset() { return initArrayEndOffset; } + + //taken from hactool + public boolean isValid(long maxSize) { // section0 size + if (textStartOffset != 0) + return false; + if (textStartOffset >= textEndOffset) + return false; + if ((textEndOffset & 0xFFF) > 0) + return false; + if (textEndOffset > rodataStartOffset) + return false; + if ((rodataStartOffset & 0xFFF) > 0) + return false; + if (rodataStartOffset >= rodataEndOffset) + return false; + if ((rodataEndOffset & 0xFFF) > 0) + return false; + if (rodataEndOffset > dataStartOffset) + return false; + if ((dataStartOffset & 0xFFF) > 0) + return false; + if (dataStartOffset >= dataEndOffset) + return false; + if (dataEndOffset > bssStartOffset) + return false; + if (bssStartOffset > bssEndOffset) + return false; + if (bssEndOffset > ini1Offset) + return false; + if (ini1Offset > maxSize - 0x80) + return false; + + return true; + } + public void printDebug(){ + log.debug("_ Kernel map _\n" + + " .text Start Offset " + RainbowDump.formatDecHexString(textStartOffset) + "\n" + + " .text End Offset " + RainbowDump.formatDecHexString(textEndOffset) + "\n" + + " .rodata Start Offset " + RainbowDump.formatDecHexString(rodataStartOffset) + "\n" + + " .rodata End Offset " + RainbowDump.formatDecHexString(rodataEndOffset) + "\n" + + " .data Start Offset " + RainbowDump.formatDecHexString(dataStartOffset) + "\n" + + " .data End Offset " + RainbowDump.formatDecHexString(dataEndOffset) + "\n" + + " .bss Start Offset " + RainbowDump.formatDecHexString(bssStartOffset) + "\n" + + " .bss End Offset " + RainbowDump.formatDecHexString(bssEndOffset) + "\n" + + " INI1 Offset " + RainbowDump.formatDecHexString(ini1Offset) + "\n" + + " Dynamic Offset " + RainbowDump.formatDecHexString(dynamicOffset) + "\n" + + " Init array Start Offset " + RainbowDump.formatDecHexString(initArrayStartOffset) + "\n" + + " Init array End Offset " + RainbowDump.formatDecHexString(initArrayEndOffset)); + } +} diff --git a/src/main/java/libKonogonka/Tools/other/System2/System2Header.java b/src/main/java/libKonogonka/Tools/other/System2/System2Header.java index bedec72..e523ce6 100644 --- a/src/main/java/libKonogonka/Tools/other/System2/System2Header.java +++ b/src/main/java/libKonogonka/Tools/other/System2/System2Header.java @@ -20,7 +20,7 @@ package libKonogonka.Tools.other.System2; import libKonogonka.Converter; import libKonogonka.RainbowDump; -import libKonogonka.ctraes.AesCtrClassic; +import libKonogonka.ctraesclassic.AesCtrDecryptClassic; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -33,6 +33,7 @@ public class System2Header { private final static Logger log = LogManager.getLogger(System2Header.class); private final byte[] headerCtr; + private final long packageSize; private byte[] section0Ctr; private byte[] section1Ctr; private byte[] section2Ctr; @@ -63,6 +64,7 @@ public class System2Header { public System2Header(byte[] headerBytes, HashMap keys) throws Exception{ this.headerCtr = Arrays.copyOfRange(headerBytes, 0, 0x10); + this.packageSize = Converter.getLEint(headerCtr, 0) ^ Converter.getLEint(headerCtr, 0x8) ^ Converter.getLEint(headerCtr, 0xC); collectKeys(keys); decodeEncrypted(headerBytes); buildHeader(); @@ -76,9 +78,8 @@ public class System2Header { } } private void decodeEncrypted(byte[] headerBytes) throws Exception{ - int i=0; for (Map.Entry entry: package2Keys.entrySet()){ - AesCtrClassic ctrClassic = new AesCtrClassic(entry.getValue(), headerCtr); + AesCtrDecryptClassic ctrClassic = new AesCtrDecryptClassic(entry.getValue(), headerCtr); decodedHeaderBytes = ctrClassic.decryptNext(headerBytes); byte[] magicBytes = Arrays.copyOfRange(decodedHeaderBytes, 0x50, 0x54); @@ -117,6 +118,7 @@ public class System2Header { } public byte[] getHeaderCtr() { return headerCtr; } + public long getPackageSize() { return packageSize; } public byte[] getSection0Ctr() { return section0Ctr; } public byte[] getSection1Ctr() { return section1Ctr; } public byte[] getSection2Ctr() { return section2Ctr; } @@ -144,6 +146,7 @@ public class System2Header { public void printDebug(){ log.debug("== System2 Header ==\n" + "Header CTR : " + Converter.byteArrToHexStringAsLE(headerCtr) + "\n" + + " Package size : " + RainbowDump.formatDecHexString(packageSize) + "\n" + "Section 0 CTR : " + Converter.byteArrToHexStringAsLE(section0Ctr) + "\n" + "Section 1 CTR : " + Converter.byteArrToHexStringAsLE(section1Ctr) + "\n" + "Section 2 CTR : " + Converter.byteArrToHexStringAsLE(section2Ctr) + "\n" + diff --git a/src/main/java/libKonogonka/Tools/other/System2/System2Provider.java b/src/main/java/libKonogonka/Tools/other/System2/System2Provider.java index 5952de0..bbf0708 100644 --- a/src/main/java/libKonogonka/Tools/other/System2/System2Provider.java +++ b/src/main/java/libKonogonka/Tools/other/System2/System2Provider.java @@ -19,26 +19,25 @@ package libKonogonka.Tools.other.System2; import libKonogonka.KeyChainHolder; -import libKonogonka.RainbowDump; -import libKonogonka.ctraes.AesCtrClassic; -import libKonogonka.ctraes.AesCtrStream; +import libKonogonka.Tools.other.System2.ini1.Ini1Provider; +import libKonogonka.ctraesclassic.AesCtrStream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import javax.crypto.CipherInputStream; import java.io.*; -import java.math.BigInteger; +import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.Arrays; public class System2Provider { private final static Logger log = LogManager.getLogger(System2Provider.class); - private byte[] Rsa2048signature; + private byte[] rsa2048signature; private System2Header header; - // ... + private KernelMap kernelMap; + private Ini1Provider ini1Provider; private final String pathToFile; private final KeyChainHolder keyChainHolder; @@ -47,19 +46,54 @@ public class System2Provider { this.pathToFile = pathToFile; this.keyChainHolder = keyChainHolder; - readHeaderCtr(); - } - - private void readHeaderCtr() throws Exception{ - try (BufferedInputStream stream = new BufferedInputStream(Files.newInputStream(Paths.get(pathToFile)))){ - if (0x100 != stream.skip(0x100)) - throw new Exception("Can't skip RSA-2048 signature offset (0x100)"); - byte[] headerBytes = new byte[0x100]; - if (0x100 != stream.read(headerBytes)) - throw new Exception("System2 header is too small"); - this.header = new System2Header(headerBytes, keyChainHolder.getRawKeySet()); + try (BufferedInputStream stream = new BufferedInputStream(Files.newInputStream(Paths.get(pathToFile)))) { + readSignatures(stream); + readHeader(stream); + findIni1KernelMap(); } } + + private void readSignatures(BufferedInputStream stream) throws Exception{ + rsa2048signature = new byte[0x100]; + if (0x100 != stream.read(rsa2048signature)) + throw new Exception("Unable to read System2 RSA-2048 signature bytes"); + } + + private void readHeader(BufferedInputStream stream) throws Exception{ + byte[] headerBytes = new byte[0x100]; + if (0x100 != stream.read(headerBytes)) + throw new Exception("Unable to read System2 header bytes"); + this.header = new System2Header(headerBytes, keyChainHolder.getRawKeySet()); + } + + private void findIni1KernelMap() throws Exception{ + try (InputStream fis = Files.newInputStream(Paths.get(pathToFile))){ + // Encrypted section comes next + long toSkip = 0x200; + if (toSkip != fis.skip(toSkip)) + throw new Exception("Unable to skip offset: " + toSkip); + + ByteBuffer byteBuffer = ByteBuffer.allocate(0x1000); + try (CipherInputStream stream = AesCtrStream.getStream(header.getKey(), header.getSection0Ctr(), fis);) { + for (int j = 0; j < 8; j++) { + byte[] block = new byte[0x200]; + int actuallyRead; + if ((actuallyRead = stream.read(block)) != 0x200) + throw new Exception("Read failure " + actuallyRead); + byteBuffer.put(block); + } + } + + byte[] searchField = byteBuffer.array(); + for (int i = 0; i < 1024; i += 4) { + kernelMap = new KernelMap(searchField, i); + if (kernelMap.isValid(header.getSection0size())) + return; + } + throw new Exception("Kernel map not found"); + } + } + public boolean exportKernel(String saveTo) throws Exception{ File location = new File(saveTo); location.mkdirs(); @@ -103,63 +137,13 @@ public class System2Provider { } return true; } - public boolean exportIni1(String saveTo) throws Exception{ - File location = new File(saveTo); - location.mkdirs(); - InputStream fis = Files.newInputStream(Paths.get(pathToFile)); - // Encrypted section comes next - long toSkip = 0x200 + header.getSection0offset(); - if (toSkip != fis.skip(toSkip)) - throw new Exception("Unable to skip offset: "+toSkip); - try (CipherInputStream stream = AesCtrStream.getStream(header.getKey(), calcIni1Ctr(), fis); - BufferedOutputStream extractedFileBOS = new BufferedOutputStream( - Files.newOutputStream(Paths.get(saveTo+File.separator+"INI1.bin")))){ - - long iniSize = header.getSection0size()-header.getSection0offset(); - - int blockSize = 0x200; - if (iniSize < 0x200) - blockSize = (int) iniSize; - - long i = 0; - byte[] block = new byte[blockSize]; - - boolean skipMode = true; - final byte[] zeroes = new byte[blockSize]; - - int actuallyRead; - while (true) { - if ((actuallyRead = stream.read(block)) != blockSize) - throw new Exception("Read failure. Block Size: "+blockSize+", actuallyRead: "+actuallyRead); - if (skipMode && Arrays.equals(block, zeroes)) - ; - else { - skipMode = false; - extractedFileBOS.write(block); - } - i += blockSize; - if ((i + blockSize) > iniSize) { - blockSize = (int) (iniSize - i); - if (blockSize == 0) - break; - block = new byte[blockSize]; - } - } - } - catch (Exception e){ - log.error("File export failure", e); - return false; - } - return true; - } - private byte[] calcIni1Ctr(){ - BigInteger ctr = new BigInteger(header.getSection0Ctr()); - BigInteger updateTo = BigInteger.valueOf(header.getSection0offset() / 0x10L); - return ctr.add(updateTo).toByteArray(); - } - - public System2Header getHeader() { - return header; + public byte[] getRsa2048signature() { return rsa2048signature; } + public System2Header getHeader() { return header; } + public KernelMap getKernelMap() { return kernelMap; } + public Ini1Provider getIni1Provider() throws Exception{ + if (ini1Provider == null) + ini1Provider = new Ini1Provider(header, pathToFile, kernelMap); + return ini1Provider; } } diff --git a/src/main/java/libKonogonka/Tools/other/System2/ini1/Ini1Header.java b/src/main/java/libKonogonka/Tools/other/System2/ini1/Ini1Header.java new file mode 100644 index 0000000..737de8f --- /dev/null +++ b/src/main/java/libKonogonka/Tools/other/System2/ini1/Ini1Header.java @@ -0,0 +1,55 @@ +/* + Copyright 2019-2023 Dmitry Isaenko + + This file is part of libKonogonka. + + libKonogonka is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libKonogonka is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libKonogonka. If not, see . + */ +package libKonogonka.Tools.other.System2.ini1; + +import libKonogonka.Converter; +import libKonogonka.RainbowDump; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Arrays; + +public class Ini1Header { + private final static Logger log = LogManager.getLogger(Ini1Header.class); + + private final String magic; + private final int size; + private final int kipNumber; + private final byte[] reserved; + + public Ini1Header(byte[] headerBytes){ + this.magic = new String(headerBytes, 0, 4); + this.size = Converter.getLEint(headerBytes, 0x4); + this.kipNumber = Converter.getLEint(headerBytes, 0x8); + this.reserved = Arrays.copyOfRange(headerBytes, 0xC, 0x10); + } + + public String getMagic() { return magic; } + public int getSize() { return size; } + public int getKipNumber() { return kipNumber; } + public byte[] getReserved() { return reserved; } + + public void printDebug(){ + log.debug("..:: INI1 Header ::..\n" + + "Magic : " + magic + "\n" + + "Size : " + RainbowDump.formatDecHexString(size) + "\n" + + "KPIs number : " + RainbowDump.formatDecHexString(kipNumber) + "\n" + + "Reserved : " + Converter.byteArrToHexStringAsLE(reserved)); + } +} diff --git a/src/main/java/libKonogonka/Tools/other/System2/ini1/Ini1Provider.java b/src/main/java/libKonogonka/Tools/other/System2/ini1/Ini1Provider.java new file mode 100644 index 0000000..717c828 --- /dev/null +++ b/src/main/java/libKonogonka/Tools/other/System2/ini1/Ini1Provider.java @@ -0,0 +1,135 @@ +/* + Copyright 2019-2023 Dmitry Isaenko + + This file is part of libKonogonka. + + libKonogonka is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libKonogonka is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libKonogonka. If not, see . + */ +package libKonogonka.Tools.other.System2.ini1; + +import libKonogonka.Tools.other.System2.KernelMap; +import libKonogonka.Tools.other.System2.System2Header; +import libKonogonka.ctraesclassic.AesCtrClassicBufferedInputStream; +import libKonogonka.ctraesclassic.AesCtrDecryptClassic; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +public class Ini1Provider { + private final static Logger log = LogManager.getLogger(Ini1Provider.class); + + private final System2Header system2Header; + private final String pathToFile; + private final KernelMap kernelMap; + private Ini1Header ini1Header; + private List kip1List; + + private AesCtrClassicBufferedInputStream stream; + + public Ini1Provider(System2Header system2Header, String pathToFile, KernelMap kernelMap) throws Exception{ + this.system2Header = system2Header; + this.pathToFile = pathToFile; + this.kernelMap = kernelMap; + + makeStream(); + makeHeader(); + collectKips(); + } + + private void makeStream() throws Exception{ + Path filePath = Paths.get(pathToFile); + long toSkip = 0x200 + kernelMap.getIni1Offset(); + AesCtrDecryptClassic decryptor = new AesCtrDecryptClassic(system2Header.getKey(), system2Header.getSection0Ctr()); + stream = new AesCtrClassicBufferedInputStream(decryptor, + 0x200, + Files.size(filePath), + Files.newInputStream(filePath), + Files.size(filePath)); + + if (toSkip != stream.skip(toSkip)) + throw new Exception("Unable to skip offset: "+toSkip); + } + + private void makeHeader() throws Exception{ + byte[] headerBytes = new byte[0x10]; + if (0x10 != stream.read(headerBytes)) + throw new Exception("Unable to read header bytes"); + ini1Header = new Ini1Header(headerBytes); + } + + private void collectKips() throws Exception{ + kip1List = new ArrayList<>(); + long skipTillNextKip1 = 0; + for (int i = 0; i < ini1Header.getKipNumber(); i++){ + if (skipTillNextKip1 != stream.skip(skipTillNextKip1)) + throw new Exception("Unable to skip bytes till next KIP1 header"); + byte[] kip1bytes = new byte[0x100]; + if (0x100 != stream.read(kip1bytes)) + throw new Exception("Unable to read KIP1 data "); + Kip1 kip1 = new Kip1(kip1bytes); + kip1List.add(kip1); + skipTillNextKip1 = kip1.getTextSegmentHeader().getSizeAsDecompressed() + + kip1.getRoDataSegmentHeader().getSizeAsDecompressed() + + kip1.getDataSegmentHeader().getSizeAsDecompressed(); + } + } + + public Ini1Header getIni1Header() { return ini1Header; } + public List getKip1List() { return kip1List; } + + public boolean export(String saveTo) throws Exception{ + makeStream(); + File location = new File(saveTo); + location.mkdirs(); + + try (BufferedOutputStream extractedFileBOS = new BufferedOutputStream( + Files.newOutputStream(Paths.get(saveTo+File.separator+"INI1.bin")))){ + + long iniSize = ini1Header.getSize(); + + int blockSize = 0x200; + if (iniSize < 0x200) + blockSize = (int) iniSize; + + long i = 0; + byte[] block = new byte[blockSize]; + + int actuallyRead; + while (true) { + if ((actuallyRead = stream.read(block)) != blockSize) + throw new Exception("Read failure. Block Size: "+blockSize+", actuallyRead: "+actuallyRead); + extractedFileBOS.write(block); + i += blockSize; + if ((i + blockSize) > iniSize) { + blockSize = (int) (iniSize - i); + if (blockSize == 0) + break; + block = new byte[blockSize]; + } + } + } + catch (Exception e){ + log.error("File export failure", e); + return false; + } + return true; + } +} diff --git a/src/main/java/libKonogonka/Tools/other/System2/ini1/Kip1.java b/src/main/java/libKonogonka/Tools/other/System2/ini1/Kip1.java new file mode 100644 index 0000000..f431f9f --- /dev/null +++ b/src/main/java/libKonogonka/Tools/other/System2/ini1/Kip1.java @@ -0,0 +1,167 @@ +/* + Copyright 2019-2023 Dmitry Isaenko + + This file is part of libKonogonka. + + libKonogonka is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libKonogonka is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libKonogonka. If not, see . + */ +package libKonogonka.Tools.other.System2.ini1; + +import libKonogonka.Converter; +import libKonogonka.RainbowDump; +import libKonogonka.Tools.NPDM.KernelAccessControlProvider; +import libKonogonka.Tools.NSO.SegmentHeader; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Arrays; + +public class Kip1 { + private final static Logger log = LogManager.getLogger(Kip1.class); + + private final String magic; + private final String name; + private final byte[] programId; + private final int version; + private final byte mainThreadPriority; + private final byte mainThreadCoreNumber; + private final byte reserved1; + private final byte flags; // bit0=TextCompress, bit1=RoCompress, bit2=DataCompress, bit3=Is64BitInstruction, bit4=ProcessAddressSpace64Bit, bit5=[2.0.0+] UseSecureMemory + private final SegmentHeader textSegmentHeader; + private final int threadAffinityMask; + private final SegmentHeader roDataSegmentHeader; + private final int mainThreadStackSize ; + private final SegmentHeader dataSegmentHeader; + private final byte[] reserved2; + private final SegmentHeader bssSegmentHeader; + private final byte[] reserved3; + private final KernelAccessControlProvider kernelCapabilityData; + + public Kip1(byte[] kip1Bytes) throws Exception{ + this.magic = new String(kip1Bytes, 0, 0x4); + this.name = new String(kip1Bytes, 0x4, 0xC); + this.programId = Arrays.copyOfRange(kip1Bytes, 0x10, 0x18); + this.version = Converter.getLEint(kip1Bytes, 0x18); + this.mainThreadPriority = kip1Bytes[0x1c]; + this.mainThreadCoreNumber = kip1Bytes[0x1d]; + this.reserved1 = kip1Bytes[0x1e]; + this.flags = kip1Bytes[0x1f]; + this.textSegmentHeader = new SegmentHeader(kip1Bytes, 0x20); + this.threadAffinityMask = Converter.getLEint(kip1Bytes, 0x2c); + this.roDataSegmentHeader = new SegmentHeader(kip1Bytes, 0x30); + this.mainThreadStackSize = Converter.getLEint(kip1Bytes, 0x3c); + this.dataSegmentHeader = new SegmentHeader(kip1Bytes, 0x40); + this.reserved2 = Arrays.copyOfRange(kip1Bytes, 0x4c, 0x50); + this.bssSegmentHeader = new SegmentHeader(kip1Bytes, 0x50); + this.reserved3 = Arrays.copyOfRange(kip1Bytes, 0x5c, 0x80); + this.kernelCapabilityData = new KernelAccessControlProvider(Arrays.copyOfRange(kip1Bytes, 0x80, 0x100)); + } + + public String getMagic() { return magic; } + public String getName() { return name; } + public byte[] getProgramId() { return programId; } + public int getVersion() { return version; } + public byte getMainThreadPriority() { return mainThreadPriority; } + public byte getMainThreadCoreNumber() { return mainThreadCoreNumber; } + public byte getReserved1() { return reserved1; } + public byte getFlags() { return flags; } + public SegmentHeader getTextSegmentHeader() { return textSegmentHeader; } + public int getThreadAffinityMask() { return threadAffinityMask; } + public SegmentHeader getRoDataSegmentHeader() { return roDataSegmentHeader; } + public int getMainThreadStackSize() { return mainThreadStackSize; } + public SegmentHeader getDataSegmentHeader() { return dataSegmentHeader; } + public byte[] getReserved2() { return reserved2; } + public SegmentHeader getBssSegmentHeader() { return bssSegmentHeader; } + public byte[] getReserved3() { return reserved3; } + public KernelAccessControlProvider getKernelCapabilityData() { return kernelCapabilityData; } + + public void printDebug(){ + StringBuilder mapIoOrNormalRange = new StringBuilder(); + StringBuilder interruptPairs = new StringBuilder(); + StringBuilder syscallMasks = new StringBuilder(); + + kernelCapabilityData.getMapIoOrNormalRange().forEach((bytes, aBoolean) -> { + mapIoOrNormalRange.append(" "); + mapIoOrNormalRange.append(Converter.byteArrToHexStringAsLE(bytes)); + mapIoOrNormalRange.append(" : "); + mapIoOrNormalRange.append(aBoolean); + mapIoOrNormalRange.append("\n"); + }); + kernelCapabilityData.getInterruptPairs().forEach((aInteger, bytes) -> { + interruptPairs.append(" #"); + interruptPairs.append(aInteger); + for (byte[] innerArray : bytes) { + interruptPairs.append("\n |- "); + interruptPairs.append(Converter.byteArrToHexStringAsLE(innerArray)); + } + interruptPairs.append("\n"); + }); + kernelCapabilityData.getSyscallMasks().forEach((aByte, bytes) -> { + syscallMasks.append(" "); + syscallMasks.append(String.format("0x%x", aByte)); + syscallMasks.append(" : "); + syscallMasks.append(Converter.byteArrToHexStringAsLE(bytes)); + syscallMasks.append("\n"); + }); + + log.debug(" ..:: KIP1 ::..\n" + + "Magic : " + magic + "\n" + + "Name : " + name + "\n" + + "ProgramId : " + Converter.byteArrToHexStringAsLE(programId) + "\n" + + "Version : " + RainbowDump.formatDecHexString(version) + "\n" + + "Main thread priority : " + String.format("0x%x", mainThreadPriority) + "\n" + + "Main thread core number : " + String.format("0x%x", mainThreadCoreNumber) + "\n" + + "Reserved 1 : " + String.format("0x%x", reserved1) + "\n" + + "Flags : " + flags + "\n" + + ".text segment header\n" + + " Segment offset : " + RainbowDump.formatDecHexString(textSegmentHeader.getSegmentOffset()) + "\n" + + " Memory offset : " + RainbowDump.formatDecHexString(textSegmentHeader.getMemoryOffset()) + "\n" + + " Size : " + RainbowDump.formatDecHexString(textSegmentHeader.getSizeAsDecompressed()) + "\n" + + "Thread affinity mask : " + RainbowDump.formatDecHexString(threadAffinityMask) + "\n" + + ".ro segment header\n" + + " Segment offset : " + RainbowDump.formatDecHexString(roDataSegmentHeader.getSegmentOffset()) + "\n" + + " Memory offset : " + RainbowDump.formatDecHexString(roDataSegmentHeader.getMemoryOffset()) + "\n" + + " Size : " + RainbowDump.formatDecHexString(roDataSegmentHeader.getSizeAsDecompressed()) + "\n" + + "Main thread stack size : " + RainbowDump.formatDecHexString(mainThreadStackSize) + "\n" + + ".data segment header\n" + + " Segment offset : " + RainbowDump.formatDecHexString(dataSegmentHeader.getSegmentOffset()) + "\n" + + " Memory offset : " + RainbowDump.formatDecHexString(dataSegmentHeader.getMemoryOffset()) + "\n" + + " Size : " + RainbowDump.formatDecHexString(dataSegmentHeader.getSizeAsDecompressed()) + "\n" + + "Reserved 2 : " + Converter.byteArrToHexStringAsLE(reserved2) + "\n" + + ".bss segment header\n" + + " Segment offset : " + RainbowDump.formatDecHexString(bssSegmentHeader.getSegmentOffset()) + "\n" + + " Memory offset : " + RainbowDump.formatDecHexString(bssSegmentHeader.getMemoryOffset()) + "\n" + + " Size : " + RainbowDump.formatDecHexString(bssSegmentHeader.getSizeAsDecompressed()) + "\n" + + "Reserved 3 : " + Converter.byteArrToHexStringAsLE(reserved3) + "\n" + + "Kernel capability data\n" + + " Kernel flags available? : " + kernelCapabilityData.isKernelFlagsAvailable() + "\n" + + " |- CPU ID Hi : " + kernelCapabilityData.getKernelFlagCpuIdHi() + "\n" + + " |- CPU ID Low : " + kernelCapabilityData.getKernelFlagCpuIdLo() + "\n" + + " |- Thread priority Hi : " + kernelCapabilityData.getKernelFlagThreadPrioHi() + "\n" + + " |- Thread priority Low : " + kernelCapabilityData.getKernelFlagThreadPrioLo()+ "\n" + + "Map IO or Normal Range:\n" + mapIoOrNormalRange + + "Interrupt pairs\n" + interruptPairs + + "Map normal page : " + Converter.byteArrToHexStringAsLE(kernelCapabilityData.getMapNormalPage()) + "\n" + + "Application type : " + RainbowDump.formatDecHexString(kernelCapabilityData.getApplicationType()) + "\n" + + " Kernel rel. version available? : " + kernelCapabilityData.isKernelRelVersionAvailable() + "\n" + + " |- Version Major : " + kernelCapabilityData.getKernelRelVersionMajor() + "\n" + + " |- Version Minor : " + kernelCapabilityData.getKernelRelVersionMinor() + "\n" + + "Handle table size : " + RainbowDump.formatDecHexString(kernelCapabilityData.getHandleTableSize()) + "\n" + + " Debug flags available? : " + kernelCapabilityData.isDebugFlagsAvailable() + "\n" + + " |- Can be debugged : " + kernelCapabilityData.isCanBeDebugged() + "\n" + + " |- Can debug others : " + kernelCapabilityData.isCanDebugOthers() + "\n" + + "Syscall masks\n" + syscallMasks + ); + } +} diff --git a/src/main/java/libKonogonka/ctraes/AesCtrBufferedInputStream.java b/src/main/java/libKonogonka/ctraes/AesCtrBufferedInputStream.java index 2837dd5..e89f31f 100644 --- a/src/main/java/libKonogonka/ctraes/AesCtrBufferedInputStream.java +++ b/src/main/java/libKonogonka/ctraes/AesCtrBufferedInputStream.java @@ -27,12 +27,12 @@ import java.io.*; public class AesCtrBufferedInputStream extends BufferedInputStream { private final static Logger log = LogManager.getLogger(AesCtrBufferedInputStream.class); - private final AesCtrDecryptSimple decryptor; + private final AesCtrDecryptForMediaBlocks decryptor; private final long mediaOffsetPositionStart; private final long mediaOffsetPositionEnd; private final long fileSize; - public AesCtrBufferedInputStream(AesCtrDecryptSimple decryptor, + public AesCtrBufferedInputStream(AesCtrDecryptForMediaBlocks decryptor, long ncaOffsetPosition, long mediaStartOffset, long mediaEndOffset, @@ -54,7 +54,7 @@ public class AesCtrBufferedInputStream extends BufferedInputStream { private int pointerInsideDecryptedSection; @Override - public synchronized int read(byte[] b) throws IOException { + public synchronized int read(byte[] b) throws IOException{ int bytesToRead = b.length; if (isPointerInsideEncryptedSection()){ int bytesFromFirstBlock = 0x200 - pointerInsideDecryptedSection; diff --git a/src/main/java/libKonogonka/ctraes/AesCtrDecryptSimple.java b/src/main/java/libKonogonka/ctraes/AesCtrDecryptForMediaBlocks.java similarity index 62% rename from src/main/java/libKonogonka/ctraes/AesCtrDecryptSimple.java rename to src/main/java/libKonogonka/ctraes/AesCtrDecryptForMediaBlocks.java index db8f05d..46fe3ec 100644 --- a/src/main/java/libKonogonka/ctraes/AesCtrDecryptSimple.java +++ b/src/main/java/libKonogonka/ctraes/AesCtrDecryptForMediaBlocks.java @@ -19,26 +19,40 @@ package libKonogonka.ctraes; import libKonogonka.Converter; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.security.Security; /** * Simplify decryption of the CTR for NCA's AesCtr sections */ -public class AesCtrDecryptSimple { +public class AesCtrDecryptForMediaBlocks { + + private static boolean BCinitialized = false; + private Cipher cipher; + private final SecretKeySpec key; private long realMediaOffset; - private byte[] IVarray; - private AesCtrForMediaBlocks aesCtr; + private byte[] ivArray; - private final byte[] initialKey; private final byte[] initialSectionCTR; private final long initialRealMediaOffset; - public AesCtrDecryptSimple(byte[] key, byte[] sectionCTR, long realMediaOffset) throws Exception{ - this.initialKey = key; + public AesCtrDecryptForMediaBlocks(byte[] key, byte[] sectionCTR, long realMediaOffset) throws Exception{ + if ( ! BCinitialized) + initBCProvider(); + this.key = new SecretKeySpec(key, "AES"); this.initialSectionCTR = sectionCTR; this.initialRealMediaOffset = realMediaOffset; reset(); } + private void initBCProvider(){ + Security.addProvider(new BouncyCastleProvider()); + BCinitialized = true; + } public void skipNext(){ realMediaOffset += 0x200; @@ -50,7 +64,7 @@ public class AesCtrDecryptSimple { public byte[] decryptNext(byte[] encryptedBlock) throws Exception{ updateIV(); - byte[] decryptedBlock = aesCtr.decrypt(encryptedBlock, IVarray); + byte[] decryptedBlock = decrypt(encryptedBlock); realMediaOffset += 0x200; return decryptedBlock; } @@ -58,17 +72,22 @@ public class AesCtrDecryptSimple { private void updateIV(){ long offset = realMediaOffset >> 4; for (int i = 0; i < 0x8; i++){ - IVarray[0x10-i-1] = (byte)(offset & 0xff); // Note: issues could be here + ivArray[0x10-i-1] = (byte)(offset & 0xff); // Note: issues could be here offset >>= 8; } } + private byte[] decrypt(byte[] encryptedData) throws Exception{ + IvParameterSpec iv = new IvParameterSpec(ivArray); + cipher.init(Cipher.DECRYPT_MODE, key, iv); + return cipher.doFinal(encryptedData); + } public void reset() throws Exception{ realMediaOffset = initialRealMediaOffset; - aesCtr = new AesCtrForMediaBlocks(initialKey); + cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC"); // IV for CTR == 16 bytes - IVarray = new byte[0x10]; + ivArray = new byte[0x10]; // Populate first 4 bytes taken from Header's section Block CTR (aka SecureValue) - System.arraycopy(Converter.flip(initialSectionCTR), 0x0, IVarray, 0x0, 0x8); + System.arraycopy(Converter.flip(initialSectionCTR), 0x0, ivArray, 0x0, 0x8); } } diff --git a/src/main/java/libKonogonka/ctraes/AesCtrForMediaBlocks.java b/src/main/java/libKonogonka/ctraes/AesCtrForMediaBlocks.java deleted file mode 100644 index 28b8700..0000000 --- a/src/main/java/libKonogonka/ctraes/AesCtrForMediaBlocks.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - Copyright 2019-2022 Dmitry Isaenko - - This file is part of libKonogonka. - - libKonogonka is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - libKonogonka is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with libKonogonka. If not, see . -*/ -package libKonogonka.ctraes; - -import org.bouncycastle.jce.provider.BouncyCastleProvider; - -import javax.crypto.Cipher; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; -import java.security.Security; - -public class AesCtrForMediaBlocks { - - private static boolean BCinitialized = false; - - private void initBCProvider(){ - Security.addProvider(new BouncyCastleProvider()); - BCinitialized = true; - } - - private final Cipher cipher; - private final SecretKeySpec key; - - AesCtrForMediaBlocks(byte[] keyArray) throws Exception{ - if ( ! BCinitialized) - initBCProvider(); - - key = new SecretKeySpec(keyArray, "AES"); - cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC"); - } - - byte[] decrypt(byte[] encryptedData, byte[] IVarray) throws Exception{ - IvParameterSpec iv = new IvParameterSpec(IVarray); - cipher.init(Cipher.DECRYPT_MODE, key, iv); - return cipher.doFinal(encryptedData); - } -} diff --git a/src/main/java/libKonogonka/ctraes/InFileStreamProducer.java b/src/main/java/libKonogonka/ctraes/InFileStreamProducer.java index c91241b..b4767ea 100644 --- a/src/main/java/libKonogonka/ctraes/InFileStreamProducer.java +++ b/src/main/java/libKonogonka/ctraes/InFileStreamProducer.java @@ -28,7 +28,7 @@ public class InFileStreamProducer { private final File file; private final long initialOffset; private long subOffset; - private AesCtrDecryptSimple decryptor; + private AesCtrDecryptForMediaBlocks decryptor; private long mediaStartOffset; private long mediaEndOffset; @@ -46,7 +46,7 @@ public class InFileStreamProducer { File file, long initialOffset, long subOffset, - AesCtrDecryptSimple decryptor, + AesCtrDecryptForMediaBlocks decryptor, long mediaStartOffset, long mediaEndOffset){ this.encrypted = (decryptor != null); diff --git a/src/main/java/libKonogonka/ctraesclassic/AesCtrClassicBufferedInputStream.java b/src/main/java/libKonogonka/ctraesclassic/AesCtrClassicBufferedInputStream.java new file mode 100644 index 0000000..e9de976 --- /dev/null +++ b/src/main/java/libKonogonka/ctraesclassic/AesCtrClassicBufferedInputStream.java @@ -0,0 +1,249 @@ +/* + Copyright 2019-2023 Dmitry Isaenko + + This file is part of libKonogonka. + + libKonogonka is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libKonogonka is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libKonogonka. If not, see . + */ +package libKonogonka.ctraesclassic; + +import libKonogonka.RainbowDump; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.*; + +public class AesCtrClassicBufferedInputStream extends BufferedInputStream { + private final static Logger log = LogManager.getLogger(AesCtrClassicBufferedInputStream.class); + + private final AesCtrDecryptClassic decryptor; + private final long encryptedStartOffset; + private final long encryptedEndOffset; + private final long fileSize; + + private byte[] decryptedBytes; + private long pseudoPos; + private int pointerInsideDecryptedSection; + + public AesCtrClassicBufferedInputStream(AesCtrDecryptClassic decryptor, + long encryptedStartOffset, + long encryptedEndOffset, + InputStream inputStream, + long fileSize){ + super(inputStream); + this.decryptor = decryptor; + this.encryptedStartOffset = encryptedStartOffset; + this.encryptedEndOffset = encryptedEndOffset; + this.fileSize = fileSize; + + log.trace(" EncryptedStartOffset : "+RainbowDump.formatDecHexString(encryptedStartOffset)+ + "\n EncryptedEndOffset : "+RainbowDump.formatDecHexString(encryptedEndOffset)); + } + + @Override + public synchronized int read(byte[] b) throws IOException{ + int bytesToRead = b.length; + if (isPointerInsideEncryptedSection()){ + int bytesFromFirstBlock = 0x200 - pointerInsideDecryptedSection; + if (bytesFromFirstBlock > bytesToRead){ + log.trace("1.2. Pointer Inside + End Position Inside (Decrypted) Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")"); + System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, bytesToRead); + + pseudoPos += bytesToRead; + pointerInsideDecryptedSection += bytesToRead; + return b.length; + } + + if (isEndPositionInsideEncryptedSection(b.length)) { + log.trace("1.1. Pointer Inside + End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")"); + int middleBlocksCount = (bytesToRead - bytesFromFirstBlock) / 0x200; + int bytesFromLastBlock = (bytesToRead - bytesFromFirstBlock) % 0x200; + //1 + System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, bytesFromFirstBlock); + //2 + for (int i = 0; i < middleBlocksCount; i++) { + fillDecryptedCache(); + System.arraycopy(decryptedBytes, 0, b, bytesFromFirstBlock+i*0x200, 0x200); + } + //3 + if(fileSize > (pseudoPos+bytesToRead)) { + fillDecryptedCache(); + System.arraycopy(decryptedBytes, 0, b, bytesFromFirstBlock + middleBlocksCount * 0x200, bytesFromLastBlock); + } + pseudoPos += bytesToRead; + pointerInsideDecryptedSection = bytesFromLastBlock; + return b.length; + } + log.trace("1. Pointer Inside + End Position Outside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")"); + int middleBlocksCount = (int) ((encryptedEndOffset - (pseudoPos+bytesFromFirstBlock)) / 0x200); + int bytesFromEnd = bytesToRead - bytesFromFirstBlock - middleBlocksCount * 0x200; + //1 + System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, bytesFromFirstBlock); + //2 + //log.debug("\n"+bytesFromFirstBlock+"\n"+ middleBlocksCount+" = "+(middleBlocksCount*0x200)+" bytes\n"+ bytesFromEnd+"\n"); + for (int i = 0; i < middleBlocksCount; i++) { + fillDecryptedCache(); + System.arraycopy(decryptedBytes, 0, b, bytesFromFirstBlock+i*0x200, 0x200); + } + //3 // TODO: if it's zero? + System.arraycopy(readChunk(bytesFromEnd), 0, b, bytesFromFirstBlock+middleBlocksCount*0x200, bytesFromEnd); + pseudoPos += bytesToRead; + pointerInsideDecryptedSection = 0; + return b.length; + } + if (isEndPositionInsideEncryptedSection(bytesToRead)) { + log.trace("2. End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")"); + int bytesTillEncrypted = (int) (encryptedStartOffset - pseudoPos); + int fullEncryptedBlocks = (bytesToRead - bytesTillEncrypted) / 0x200; + int incompleteEncryptedBytes = (bytesToRead - bytesTillEncrypted) % 0x200; + System.arraycopy(readChunk(bytesTillEncrypted), 0, b, 0, bytesTillEncrypted); + //2 + for (int i = 0; i < fullEncryptedBlocks; i++) { + fillDecryptedCache(); + System.arraycopy(decryptedBytes, 0, b, fullEncryptedBlocks+i*0x200, 0x200); + } + //3 + fillDecryptedCache(); + System.arraycopy(decryptedBytes, 0, b, bytesTillEncrypted+fullEncryptedBlocks*0x200, incompleteEncryptedBytes); + pseudoPos += bytesToRead; + pointerInsideDecryptedSection = incompleteEncryptedBytes; + return b.length; + } + log.trace("3. Not encrypted ("+pseudoPos+"-"+(pseudoPos+b.length)+")"); + pseudoPos += bytesToRead; + pointerInsideDecryptedSection = 0; + return super.read(b); + } + private void fillDecryptedCache() throws IOException{ + try{ + decryptedBytes = decryptor.decryptNext(readChunk(0x200)); + } + catch (Exception e){ + throw new IOException(e); + } + } + + private byte[] readChunk(int bytes) throws IOException{ + byte[] chunkBytes = new byte[bytes]; + long actuallyRead = super.read(chunkBytes); + if (actuallyRead != bytes) + throw new IOException("Can't read. Need block of "+ bytes +" while only " + + actuallyRead + " bytes."); + return chunkBytes; + } + + private boolean isPointerInsideEncryptedSection(){ + return (pseudoPos-pointerInsideDecryptedSection >= encryptedStartOffset) && + (pseudoPos-pointerInsideDecryptedSection < encryptedEndOffset); + } + private boolean isEndPositionInsideEncryptedSection(long requestedBytesCount){ + return ((pseudoPos-pointerInsideDecryptedSection + requestedBytesCount) >= encryptedStartOffset) && + ((pseudoPos-pointerInsideDecryptedSection + requestedBytesCount) < encryptedEndOffset); + } + + @Override + public synchronized long skip(long n) throws IOException { + if (isPointerInsideEncryptedSection()){ + long realCountOfBytesToSkip = n - (0x200 - pointerInsideDecryptedSection); + if (realCountOfBytesToSkip <= 0){ + pseudoPos += n; + pointerInsideDecryptedSection += n; + return n; + } + + if (isEndPositionInsideEncryptedSection(n)){ // If we need to move somewhere out of the encrypted section + log.trace("4.1. Pointer Inside + End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+n)+")"); + long blocksToSkipCountingFromStart = (pseudoPos+n - encryptedStartOffset) / 0x200; // always positive + resetAndSkip(blocksToSkipCountingFromStart); + + long leftovers = realCountOfBytesToSkip % 0x200; // most likely will be 0; TODO: a lot of tests + long bytesToSkipTillRequiredBlock = realCountOfBytesToSkip - leftovers; + skipLoop(bytesToSkipTillRequiredBlock); + fillDecryptedCache(); + pseudoPos += n; + pointerInsideDecryptedSection = (int) leftovers; + return n; + } + log.trace("4. Pointer Inside + End Position Outside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+n)+")"); + skipLoop(realCountOfBytesToSkip); + pseudoPos += n; + pointerInsideDecryptedSection = 0; + return n; + // just fast-forward to position we need and flush caches + } + + if (isEndPositionInsideEncryptedSection(n)) { //pointer will be inside Encrypted Section, but now outside + log.trace("5. End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+n)+")"); + //skip to start if the block we need + long bytesToSkipTillEncryptedBlock = encryptedStartOffset - pseudoPos; + long blocksToSkipCountingFromStart = (n - bytesToSkipTillEncryptedBlock) / 0x200; // always positive + long bytesToSkipTillRequiredBlock = bytesToSkipTillEncryptedBlock + blocksToSkipCountingFromStart * 0x200; + long leftovers = n - bytesToSkipTillRequiredBlock; // most likely will be 0; + + long skipped = super.skip(bytesToSkipTillRequiredBlock); + if (bytesToSkipTillRequiredBlock != skipped) + throw new IOException("Can't skip bytes. To skip: " + + bytesToSkipTillEncryptedBlock + + ".\nActually skipped: " + skipped + + ".\nLeftovers inside encrypted section: " + leftovers); + log.trace("\tBlocks skipped "+blocksToSkipCountingFromStart); + resetAndSkip(blocksToSkipCountingFromStart); + fillDecryptedCache(); + pseudoPos += n; + pointerInsideDecryptedSection = (int) leftovers; + return n; + } + log.trace("6. Not encrypted ("+pseudoPos+"-"+(pseudoPos+n)+")"); + skipLoop(n); + pseudoPos += n; + pointerInsideDecryptedSection = 0; + return n; + } + private void skipLoop(long size) throws IOException{ + long mustSkip = size; + long skipped = 0; + while (mustSkip > 0){ + skipped += super.skip(mustSkip); + mustSkip = size - skipped; + log.trace("Skip loop: skipped: "+skipped+"\tmustSkip "+mustSkip); + } + } + private void resetAndSkip(long blockSum) throws IOException{ + try { + decryptor.resetAndSkip(blockSum); + } + catch (Exception e){ throw new IOException(e); } + } + + @Override + public synchronized int read() throws IOException { + byte[] b = new byte[1]; + if (read(b) != -1) + return b[0]; + return -1; + } + + @Override + public boolean markSupported() { + return false; + } + + @Override + public synchronized void mark(int readlimit) {} + + @Override + public synchronized void reset() throws IOException { + throw new IOException("Not supported"); + } +} \ No newline at end of file diff --git a/src/main/java/libKonogonka/ctraesclassic/AesCtrDecryptClassic.java b/src/main/java/libKonogonka/ctraesclassic/AesCtrDecryptClassic.java new file mode 100644 index 0000000..50d0730 --- /dev/null +++ b/src/main/java/libKonogonka/ctraesclassic/AesCtrDecryptClassic.java @@ -0,0 +1,81 @@ +/* + Copyright 2019-2023 Dmitry Isaenko + + This file is part of libKonogonka. + + libKonogonka is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libKonogonka is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libKonogonka. If not, see . +*/ +package libKonogonka.ctraesclassic; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.math.BigInteger; +import java.security.Security; + +public class AesCtrDecryptClassic { + + private static boolean BCinitialized = false; + + private void initBCProvider(){ + Security.addProvider(new BouncyCastleProvider()); + BCinitialized = true; + } + + private final SecretKeySpec key; + private final byte[] ivArray; + private Cipher cipher; + + public AesCtrDecryptClassic(String keyString, byte[] ivArray) throws Exception{ + if ( ! BCinitialized) + initBCProvider(); + byte[] keyArray = hexStrToByteArray(keyString); + this.ivArray = ivArray; + key = new SecretKeySpec(keyArray, "AES"); + cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC"); + IvParameterSpec iv = new IvParameterSpec(ivArray.clone()); + cipher.init(Cipher.DECRYPT_MODE, key, iv); + } + + public byte[] decryptNext(byte[] encryptedData) { + return cipher.update(encryptedData); + } + + /** + * Initializes cipher again using updated IV + * @param blocks - how many blocks from encrypted section start should be skipped. Block size = 0x200 + * */ + public void resetAndSkip(long blocks) throws Exception{ + cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC"); + IvParameterSpec iv = new IvParameterSpec(calculateCtr(blocks * 0x200)); + cipher.init(Cipher.DECRYPT_MODE, key, iv); + } + private byte[] calculateCtr(long offset){ + BigInteger ctr = new BigInteger(ivArray); + BigInteger updateTo = BigInteger.valueOf(offset / 0x10L); + return ctr.add(updateTo).toByteArray(); + } + + private byte[] hexStrToByteArray(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i+1), 16)); + } + return data; + } +} diff --git a/src/main/java/libKonogonka/ctraes/AesCtrClassic.java b/src/main/java/libKonogonka/ctraesclassic/AesCtrStream.java similarity index 74% rename from src/main/java/libKonogonka/ctraes/AesCtrClassic.java rename to src/main/java/libKonogonka/ctraesclassic/AesCtrStream.java index 039aaa2..48598df 100644 --- a/src/main/java/libKonogonka/ctraes/AesCtrClassic.java +++ b/src/main/java/libKonogonka/ctraesclassic/AesCtrStream.java @@ -1,5 +1,5 @@ /* - Copyright 2019-2022 Dmitry Isaenko + Copyright 2019-2023 Dmitry Isaenko This file is part of libKonogonka. @@ -16,43 +16,38 @@ You should have received a copy of the GNU General Public License along with libKonogonka. If not, see . */ -package libKonogonka.ctraes; +package libKonogonka.ctraesclassic; import org.bouncycastle.jce.provider.BouncyCastleProvider; import javax.crypto.Cipher; -import javax.crypto.CipherOutputStream; +import javax.crypto.CipherInputStream; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; +import java.io.InputStream; import java.security.Security; -public class AesCtrClassic { - +public class AesCtrStream { private static boolean BCinitialized = false; - private void initBCProvider(){ + private static void initBCProvider(){ Security.addProvider(new BouncyCastleProvider()); BCinitialized = true; } + private AesCtrStream(){ } - private final Cipher cipher; - - public AesCtrClassic(String keyString, byte[] IVarray) throws Exception{ + public static CipherInputStream getStream(String keyString, byte[] IVarray, InputStream inputStream) throws Exception{ if ( ! BCinitialized) initBCProvider(); byte[] keyArray = hexStrToByteArray(keyString); SecretKeySpec key = new SecretKeySpec(keyArray, "AES"); - cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC"); + Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC"); IvParameterSpec iv = new IvParameterSpec(IVarray.clone()); cipher.init(Cipher.DECRYPT_MODE, key, iv); - //TODO: CipherOutputStream + return new CipherInputStream(inputStream, cipher); } - public byte[] decryptNext(byte[] encryptedData) { - return cipher.update(encryptedData); - } - - private byte[] hexStrToByteArray(String s) { + private static byte[] hexStrToByteArray(String s) { int len = s.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { diff --git a/src/test/java/libKonogonka/RomFsDecrypted/NSPpfs0EncryptedTest.java b/src/test/java/libKonogonka/RomFsDecrypted/NSPpfs0EncryptedTest.java index 8d34dc5..b9f3e04 100644 --- a/src/test/java/libKonogonka/RomFsDecrypted/NSPpfs0EncryptedTest.java +++ b/src/test/java/libKonogonka/RomFsDecrypted/NSPpfs0EncryptedTest.java @@ -25,7 +25,7 @@ import libKonogonka.Tools.NCA.NCAProvider; import libKonogonka.Tools.PFS0.PFS0Provider; import libKonogonka.Tools.PFS0.PFS0subFile; import libKonogonka.ctraes.AesCtrBufferedInputStream; -import libKonogonka.ctraes.AesCtrDecryptSimple; +import libKonogonka.ctraes.AesCtrDecryptForMediaBlocks; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -101,7 +101,7 @@ public class NSPpfs0EncryptedTest { } } - private AesCtrDecryptSimple decryptSimple; + private AesCtrDecryptForMediaBlocks decryptSimple; long ACBISoffsetPosition; long ACBISmediaStartOffset; long ACBISmediaEndOffset; diff --git a/src/test/java/libKonogonka/RomFsDecrypted/PFS0Test.java b/src/test/java/libKonogonka/RomFsDecrypted/PFS0Test.java index 1f4d3a6..2e89c51 100644 --- a/src/test/java/libKonogonka/RomFsDecrypted/PFS0Test.java +++ b/src/test/java/libKonogonka/RomFsDecrypted/PFS0Test.java @@ -19,19 +19,14 @@ package libKonogonka.RomFsDecrypted; import libKonogonka.KeyChainHolder; -import libKonogonka.RainbowDump; import libKonogonka.TitleKeyChainHolder; -import libKonogonka.Tools.NCA.NCAProvider; import libKonogonka.Tools.PFS0.PFS0Provider; import libKonogonka.Tools.PFS0.PFS0subFile; -import libKonogonka.ctraes.AesCtrBufferedInputStream; -import libKonogonka.ctraes.AesCtrDecryptSimple; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.io.*; -import java.nio.file.Files; import java.util.HashMap; public class PFS0Test { diff --git a/src/test/java/libKonogonka/RomFsDecrypted/Package2UnpackedTest.java b/src/test/java/libKonogonka/RomFsDecrypted/Package2UnpackedTest.java index be02c54..ec6536a 100644 --- a/src/test/java/libKonogonka/RomFsDecrypted/Package2UnpackedTest.java +++ b/src/test/java/libKonogonka/RomFsDecrypted/Package2UnpackedTest.java @@ -20,8 +20,11 @@ package libKonogonka.RomFsDecrypted; import libKonogonka.KeyChainHolder; import libKonogonka.Tools.other.System2.System2Provider; -import libKonogonka.ctraes.AesCtrClassic; +import libKonogonka.Tools.other.System2.ini1.Ini1Provider; +import libKonogonka.Tools.other.System2.ini1.Kip1; +import libKonogonka.ctraesclassic.AesCtrDecryptClassic; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -42,6 +45,7 @@ public class Package2UnpackedTest { private static final String fileLocation = "/home/loper/Projects/libKonogonka/FilesForTests/6b7abe7efa17ad065b18e62d1c87a5cc.nca_extracted/ROOT/nx/package2"; + @Disabled @DisplayName("Package2 unpacked test") @Test void discover() throws Exception{ @@ -79,7 +83,7 @@ public class Package2UnpackedTest { byte[] headerCTR = Arrays.copyOfRange(header, 0, 0x10); for (Map.Entry entry: package2_keys.entrySet()){ - AesCtrClassic aesCtrClassic = new AesCtrClassic(entry.getValue(), headerCTR); + AesCtrDecryptClassic aesCtrClassic = new AesCtrDecryptClassic(entry.getValue(), headerCTR); byte[] decrypted = aesCtrClassic.decryptNext(header); //RainbowDump.hexDumpUTF8(decrypted); @@ -89,6 +93,7 @@ public class Package2UnpackedTest { System.out.println(entry.getKey()+" "+entry.getValue()+" "+magicString); } } + @Disabled @DisplayName("Package2 written test") @Test void implement() throws Exception{ @@ -96,11 +101,16 @@ public class Package2UnpackedTest { keyChainHolder = new KeyChainHolder(keysFileLocation, null); System2Provider provider = new System2Provider(fileLocation, keyChainHolder); provider.getHeader().printDebug(); + provider.getKernelMap().printDebug(); + Ini1Provider ini1Provider = provider.getIni1Provider(); + ini1Provider.getIni1Header().printDebug(); + for (Kip1 kip1 : ini1Provider.getKip1List()) + kip1.printDebug(); boolean exported = provider.exportKernel("/home/loper/Projects/libKonogonka/FilesForTests/own/"); System.out.println("Exported = "+exported); - exported = provider.exportIni1("/home/loper/Projects/libKonogonka/FilesForTests/own/"); + exported = ini1Provider.export("/home/loper/Projects/libKonogonka/FilesForTests/own/"); System.out.println("Exported INI1 = "+exported); } diff --git a/src/test/java/libKonogonka/RomFsDecrypted/Pfs0EncryptedTest.java b/src/test/java/libKonogonka/RomFsDecrypted/Pfs0EncryptedTest.java index 4931616..dbe30cb 100644 --- a/src/test/java/libKonogonka/RomFsDecrypted/Pfs0EncryptedTest.java +++ b/src/test/java/libKonogonka/RomFsDecrypted/Pfs0EncryptedTest.java @@ -24,7 +24,7 @@ import libKonogonka.Tools.NCA.NCAProvider; import libKonogonka.Tools.PFS0.PFS0Provider; import libKonogonka.Tools.PFS0.PFS0subFile; import libKonogonka.ctraes.AesCtrBufferedInputStream; -import libKonogonka.ctraes.AesCtrDecryptSimple; +import libKonogonka.ctraes.AesCtrDecryptForMediaBlocks; import org.junit.jupiter.api.*; import java.io.*; @@ -72,7 +72,7 @@ public class Pfs0EncryptedTest { } } - private AesCtrDecryptSimple decryptSimple; + private AesCtrDecryptForMediaBlocks decryptSimple; long ACBISoffsetPosition; long ACBISmediaStartOffset; long ACBISmediaEndOffset; @@ -104,7 +104,7 @@ public class Pfs0EncryptedTest { ACBISmediaStartOffset = ncaProvider.getTableEntry0().getMediaStartOffset(); ACBISmediaEndOffset = ncaProvider.getTableEntry0().getMediaEndOffset(); - decryptSimple = new AesCtrDecryptSimple( + decryptSimple = new AesCtrDecryptForMediaBlocks( ncaProvider.getDecryptedKey2(), ncaProvider.getSectionBlock0().getSectionCTR(), ncaProvider.getTableEntry0().getMediaStartOffset()*0x200); diff --git a/src/test/java/libKonogonka/RomFsDecrypted/RomFsEncryptedTest.java b/src/test/java/libKonogonka/RomFsDecrypted/RomFsEncryptedTest.java index ac7dd06..c427333 100644 --- a/src/test/java/libKonogonka/RomFsDecrypted/RomFsEncryptedTest.java +++ b/src/test/java/libKonogonka/RomFsDecrypted/RomFsEncryptedTest.java @@ -24,7 +24,7 @@ import libKonogonka.RainbowDump; import libKonogonka.Tools.NCA.NCAProvider; import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader; import libKonogonka.Tools.RomFs.FileSystemEntry; -import libKonogonka.ctraes.AesCtrDecryptSimple; +import libKonogonka.ctraes.AesCtrDecryptForMediaBlocks; import org.junit.jupiter.api.*; import java.io.*; @@ -90,7 +90,7 @@ public class RomFsEncryptedTest { } } - private AesCtrDecryptSimple decryptSimple; + private AesCtrDecryptForMediaBlocks decryptSimple; long ACBISoffsetPosition; long ACBISmediaStartOffset; long ACBISmediaEndOffset; @@ -111,7 +111,7 @@ public class RomFsEncryptedTest { ACBISmediaStartOffset = ncaProvider.getTableEntry1().getMediaStartOffset(); ACBISmediaEndOffset = ncaProvider.getTableEntry1().getMediaEndOffset(); - decryptSimple = new AesCtrDecryptSimple( + decryptSimple = new AesCtrDecryptForMediaBlocks( ncaProvider.getDecryptedKey2(), ncaProvider.getSectionBlock1().getSectionCTR(), ncaProvider.getTableEntry1().getMediaStartOffset()*0x200); @@ -212,11 +212,4 @@ public class RomFsEncryptedTest { extractedFileBOS.close(); }*/ - @Disabled - @Order(6) - @DisplayName("RomFsEncryptedProvider: PFS test") - @Test - void pfsValidation(){ - //1 PFS and cryptotype != 0 - } }