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.
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Dmitry Isaenko 2023-01-05 18:22:29 +03:00
parent 83cf199beb
commit e406dd642c
21 changed files with 989 additions and 245 deletions

View file

@ -22,7 +22,7 @@ import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader;
import libKonogonka.Tools.PFS0.PFS0Provider; import libKonogonka.Tools.PFS0.PFS0Provider;
import libKonogonka.Tools.RomFs.RomFsProvider; import libKonogonka.Tools.RomFs.RomFsProvider;
import libKonogonka.ctraes.AesCtrBufferedInputStream; import libKonogonka.ctraes.AesCtrBufferedInputStream;
import libKonogonka.ctraes.AesCtrDecryptSimple; import libKonogonka.ctraes.AesCtrDecryptForMediaBlocks;
import libKonogonka.ctraes.InFileStreamProducer; import libKonogonka.ctraes.InFileStreamProducer;
import libKonogonka.exceptions.EmptySectionException; import libKonogonka.exceptions.EmptySectionException;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@ -119,7 +119,7 @@ public class NCAContent {
public RomFsProvider getRomfs() { return romfs; } public RomFsProvider getRomfs() { return romfs; }
private InFileStreamProducer makeEncryptedProducer() throws Exception{ private InFileStreamProducer makeEncryptedProducer() throws Exception{
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(decryptedKey, ncaFsHeader.getSectionCTR(), AesCtrDecryptForMediaBlocks decryptor = new AesCtrDecryptForMediaBlocks(decryptedKey, ncaFsHeader.getSectionCTR(),
ncaHeaderTableEntry.getMediaStartOffset() * 0x200); ncaHeaderTableEntry.getMediaStartOffset() * 0x200);
return new InFileStreamProducer(file, ncaOffsetPosition, 0, decryptor, return new InFileStreamProducer(file, ncaOffsetPosition, 0, decryptor,
ncaHeaderTableEntry.getMediaStartOffset(), ncaHeaderTableEntry.getMediaEndOffset()); ncaHeaderTableEntry.getMediaStartOffset(), ncaHeaderTableEntry.getMediaEndOffset());
@ -146,7 +146,7 @@ public class NCAContent {
stream = new BufferedInputStream(Files.newInputStream(file.toPath())); stream = new BufferedInputStream(Files.newInputStream(file.toPath()));
} }
else if(ncaFsHeader.getCryptoType()==0x03) { else if(ncaFsHeader.getCryptoType()==0x03) {
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(decryptedKey, AesCtrDecryptForMediaBlocks decryptor = new AesCtrDecryptForMediaBlocks(decryptedKey,
ncaFsHeader.getSectionCTR(), ncaFsHeader.getSectionCTR(),
mediaStartOffset * 0x200); mediaStartOffset * 0x200);

View file

@ -86,58 +86,50 @@ public class KernelAccessControlProvider {
KERNELRELEASEVERSION = 14, KERNELRELEASEVERSION = 14,
HANDLETABLESIZE = 15, HANDLETABLESIZE = 15,
DEBUGFLAGS = 16; DEBUGFLAGS = 16;
// RAW data // RAW data
private final LinkedList<Integer> rawData; private final LinkedList<Integer> rawData;
// Kernel flags // Kernel flags
private boolean kernelFlagsAvailable; private boolean kernelFlagsAvailable;
private int kernelFlagCpuIdHi, private int kernelFlagCpuIdHi;
kernelFlagCpuIdLo, private int kernelFlagCpuIdLo;
kernelFlagThreadPrioHi, private int kernelFlagThreadPrioHi;
kernelFlagThreadPrioLo; private int kernelFlagThreadPrioLo;
// Syscall Masks as index | mask - order AS IS. [0] = bit5; [1] = bit6 // Syscall Masks as index | mask - order AS IS. [0] = bit5; [1] = bit6
private final LinkedHashMap<Byte, byte[]> syscallMasks; // Index, Mask private final LinkedHashMap<Byte, byte[]> syscallMasks; // Index, Mask
// MapIoOrNormalRange // MapIoOrNormalRange
private final LinkedHashMap<byte[], Boolean> mapIoOrNormalRange; // alt page+num, RO flag private final LinkedHashMap<byte[], Boolean> mapIoOrNormalRange; // alt page+num, RO flag
// MapNormalPage (RW) // MapNormalPage (RW)
private byte[] mapNormalPage; // TODO: clarify is possible to have multiple private byte[] mapNormalPage; // TODO: clarify is possible to have multiple
// InterruptPair
private final LinkedHashMap<Integer, byte[][]> interruptPairs; // Number; irq0, irq2 private final LinkedHashMap<Integer, byte[][]> interruptPairs; // Number; irq0, irq2
// Application type
private int applicationType; private int applicationType;
// KernelReleaseVersion
private boolean isKernelRelVersionAvailable; private boolean isKernelRelVersionAvailable;
private int kernelRelVersionMajor, private int kernelRelVersionMajor;
kernelRelVersionMinor; private int kernelRelVersionMinor;
// Handle Table Size
private int handleTableSize; private int handleTableSize;
// Debug flags // Debug flags
private boolean debugFlagsAvailable, private boolean debugFlagsAvailable;
canBeDebugged, private boolean canBeDebugged;
canDebugOthers; private boolean canDebugOthers;
public KernelAccessControlProvider(byte[] bytes) throws Exception{ public KernelAccessControlProvider(byte[] bytes) throws Exception{
if (bytes.length < 4) if (bytes.length < 4)
throw new Exception("ACID-> KernelAccessControlProvider: too small size of the Kernel Access Control"); throw new Exception("ACID-> KernelAccessControlProvider: too small size of the Kernel Access Control");
rawData = new LinkedList<Integer>(); this.rawData = new LinkedList<>();
this.interruptPairs = new LinkedHashMap<>();
this.syscallMasks = new LinkedHashMap<>();
this.mapIoOrNormalRange = new LinkedHashMap<>();
interruptPairs = new LinkedHashMap<>();
syscallMasks = new LinkedHashMap<Byte, byte[]>();
mapIoOrNormalRange = new LinkedHashMap<byte[], Boolean>();
int position = 0;
// Collect all blocks // 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); int block = Converter.getLEint(bytes, position);
position += 4;
rawData.add(block); rawData.add(block);
int type = findBitsCount(block);
//RainbowHexDump.octDumpInt(block);
int type = getMinBitCnt(block);
switch (type){ switch (type){
case KERNELFLAGS: case KERNELFLAGS:
kernelFlagsAvailable = true; kernelFlagsAvailable = true;
@ -145,42 +137,39 @@ public class KernelAccessControlProvider {
kernelFlagCpuIdLo = block >> 16 & 0b11111111; kernelFlagCpuIdLo = block >> 16 & 0b11111111;
kernelFlagThreadPrioHi = block >> 10 & 0b111111; kernelFlagThreadPrioHi = block >> 10 & 0b111111;
kernelFlagThreadPrioLo = block >> 4 & 0b111111; kernelFlagThreadPrioLo = block >> 4 & 0b111111;
//log.debug("KERNELFLAGS "+kernelFlagCpuIdHi+" "+kernelFlagCpuIdLo+" "+kernelFlagThreadPrioHi+" "+kernelFlagThreadPrioLo); log.trace("KERNELFLAGS "+kernelFlagCpuIdHi+" "+kernelFlagCpuIdLo+" "+kernelFlagThreadPrioHi+" "+kernelFlagThreadPrioLo);
break; break;
case SYSCALLMASK: case SYSCALLMASK:
byte maskTableIndex = (byte) (block >> 29 & 0b111); // declared as byte; max value could be 7; min - 0; byte maskTableIndex = (byte) (block >> 29 & 0b111); // declared as byte; max value could be 7; min - 0;
byte[] mask = new byte[24]; // Consider as bit. 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--) { for (int k = 28; k >= 5; k--) {
mask[k-5] = (byte) (block >> k & 1); // Only 1 or 0 possible 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); syscallMasks.put(maskTableIndex, mask);
break; break;
case MAPIOORNORMALRANGE: case MAPIOORNORMALRANGE:
byte[] altStPgNPgNum = new byte[24]; 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--){ for (int k = 30; k >= 7; k--){
altStPgNPgNum[k-7] = (byte) (block >> k & 1); // Only 1 or 0 possible 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); mapIoOrNormalRange.put(altStPgNPgNum, (block >> 31 & 1) != 0);
//log.debug();
break; break;
case MAPNORMALPAGE_RW: case MAPNORMALPAGE_RW:
//log.debug("MAPNORMALPAGE_RW\t"); log.trace("MAPNORMALPAGE_RW\t");
mapNormalPage = new byte[24]; mapNormalPage = new byte[24];
for (int k = 31; k >= 8; k--){ for (int k = 31; k >= 8; k--){
mapNormalPage[k-8] = (byte) (block >> k & 1); mapNormalPage[k-8] = (byte) (block >> k & 1);
//log.debug(" " + mapNormalPage[k-8]); log.trace(" " + mapNormalPage[k-8]);
} }
//log.debug();
break; break;
case INTERRUPTPAIR: case INTERRUPTPAIR:
//log.debug("INTERRUPTPAIR"); log.trace("INTERRUPTPAIR");
//RainbowHexDump.octDumpInt(block); //RainbowHexDump.octDumpInt(block);
byte[][] pair = new byte[2][]; byte[][] pair = new byte[2][];
byte[] irq0 = new byte[10]; byte[] irq0 = new byte[10];
@ -196,33 +185,35 @@ public class KernelAccessControlProvider {
break; break;
case APPLICATIONTYPE: case APPLICATIONTYPE:
applicationType = block >> 14 & 0b111; applicationType = block >> 14 & 0b111;
//log.debug("APPLICATIONTYPE "+applicationType); log.trace("APPLICATIONTYPE "+applicationType);
break; break;
case KERNELRELEASEVERSION: 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; isKernelRelVersionAvailable = true;
kernelRelVersionMajor = (block >> 19 & 0b111111111111); kernelRelVersionMajor = (block >> 19 & 0b111111111111);
kernelRelVersionMinor = (block >> 15 & 0b1111); kernelRelVersionMinor = (block >> 15 & 0b1111);
break; break;
case HANDLETABLESIZE: case HANDLETABLESIZE:
handleTableSize = block >> 16 & 0b1111111111; handleTableSize = block >> 16 & 0b1111111111;
//log.debug("HANDLETABLESIZE "+handleTableSize); log.trace("HANDLETABLESIZE "+handleTableSize);
break; break;
case DEBUGFLAGS: case DEBUGFLAGS:
debugFlagsAvailable = true; debugFlagsAvailable = true;
canBeDebugged = (block >> 17 & 1) != 0; canBeDebugged = (block >> 17 & 1) != 0;
canDebugOthers = (block >> 18 & 1) != 0; canDebugOthers = (block >> 18 & 1) != 0;
//log.debug("DEBUGFLAGS "+canBeDebugged+" "+canDebugOthers); log.trace("DEBUGFLAGS "+canBeDebugged+" "+canDebugOthers);
break; break;
default: 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; int minBitCnt = 0;
while ((value & 1) != 0){ for (int i = 0; i < 32; i++){
if((value & 1) == 0)
break;
value >>= 1; value >>= 1;
minBitCnt++; minBitCnt++;
} }

View file

@ -29,7 +29,7 @@ public class SegmentHeader {
this(data, 0); this(data, 0);
} }
SegmentHeader(byte[] data, int fromOffset){ public SegmentHeader(byte[] data, int fromOffset){
this.segmentOffset = Converter.getLEint(data, fromOffset); this.segmentOffset = Converter.getLEint(data, fromOffset);
this.memoryOffset = Converter.getLEint(data, fromOffset+4); this.memoryOffset = Converter.getLEint(data, fromOffset+4);
this.sizeAsDecompressed = Converter.getLEint(data, fromOffset+8); this.sizeAsDecompressed = Converter.getLEint(data, fromOffset+8);
@ -42,7 +42,9 @@ public class SegmentHeader {
public int getMemoryOffset() { public int getMemoryOffset() {
return memoryOffset; return memoryOffset;
} }
/**
* @return Size of compressed if used in KIP1
* */
public int getSizeAsDecompressed() { public int getSizeAsDecompressed() {
return sizeAsDecompressed; return sizeAsDecompressed;
} }

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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));
}
}

View file

@ -20,7 +20,7 @@ package libKonogonka.Tools.other.System2;
import libKonogonka.Converter; import libKonogonka.Converter;
import libKonogonka.RainbowDump; import libKonogonka.RainbowDump;
import libKonogonka.ctraes.AesCtrClassic; import libKonogonka.ctraesclassic.AesCtrDecryptClassic;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -33,6 +33,7 @@ public class System2Header {
private final static Logger log = LogManager.getLogger(System2Header.class); private final static Logger log = LogManager.getLogger(System2Header.class);
private final byte[] headerCtr; private final byte[] headerCtr;
private final long packageSize;
private byte[] section0Ctr; private byte[] section0Ctr;
private byte[] section1Ctr; private byte[] section1Ctr;
private byte[] section2Ctr; private byte[] section2Ctr;
@ -63,6 +64,7 @@ public class System2Header {
public System2Header(byte[] headerBytes, HashMap<String, String> keys) throws Exception{ public System2Header(byte[] headerBytes, HashMap<String, String> keys) throws Exception{
this.headerCtr = Arrays.copyOfRange(headerBytes, 0, 0x10); this.headerCtr = Arrays.copyOfRange(headerBytes, 0, 0x10);
this.packageSize = Converter.getLEint(headerCtr, 0) ^ Converter.getLEint(headerCtr, 0x8) ^ Converter.getLEint(headerCtr, 0xC);
collectKeys(keys); collectKeys(keys);
decodeEncrypted(headerBytes); decodeEncrypted(headerBytes);
buildHeader(); buildHeader();
@ -76,9 +78,8 @@ public class System2Header {
} }
} }
private void decodeEncrypted(byte[] headerBytes) throws Exception{ private void decodeEncrypted(byte[] headerBytes) throws Exception{
int i=0;
for (Map.Entry<String, String> entry: package2Keys.entrySet()){ for (Map.Entry<String, String> entry: package2Keys.entrySet()){
AesCtrClassic ctrClassic = new AesCtrClassic(entry.getValue(), headerCtr); AesCtrDecryptClassic ctrClassic = new AesCtrDecryptClassic(entry.getValue(), headerCtr);
decodedHeaderBytes = ctrClassic.decryptNext(headerBytes); decodedHeaderBytes = ctrClassic.decryptNext(headerBytes);
byte[] magicBytes = Arrays.copyOfRange(decodedHeaderBytes, 0x50, 0x54); byte[] magicBytes = Arrays.copyOfRange(decodedHeaderBytes, 0x50, 0x54);
@ -117,6 +118,7 @@ public class System2Header {
} }
public byte[] getHeaderCtr() { return headerCtr; } public byte[] getHeaderCtr() { return headerCtr; }
public long getPackageSize() { return packageSize; }
public byte[] getSection0Ctr() { return section0Ctr; } public byte[] getSection0Ctr() { return section0Ctr; }
public byte[] getSection1Ctr() { return section1Ctr; } public byte[] getSection1Ctr() { return section1Ctr; }
public byte[] getSection2Ctr() { return section2Ctr; } public byte[] getSection2Ctr() { return section2Ctr; }
@ -144,6 +146,7 @@ public class System2Header {
public void printDebug(){ public void printDebug(){
log.debug("== System2 Header ==\n" + log.debug("== System2 Header ==\n" +
"Header CTR : " + Converter.byteArrToHexStringAsLE(headerCtr) + "\n" + "Header CTR : " + Converter.byteArrToHexStringAsLE(headerCtr) + "\n" +
" Package size : " + RainbowDump.formatDecHexString(packageSize) + "\n" +
"Section 0 CTR : " + Converter.byteArrToHexStringAsLE(section0Ctr) + "\n" + "Section 0 CTR : " + Converter.byteArrToHexStringAsLE(section0Ctr) + "\n" +
"Section 1 CTR : " + Converter.byteArrToHexStringAsLE(section1Ctr) + "\n" + "Section 1 CTR : " + Converter.byteArrToHexStringAsLE(section1Ctr) + "\n" +
"Section 2 CTR : " + Converter.byteArrToHexStringAsLE(section2Ctr) + "\n" + "Section 2 CTR : " + Converter.byteArrToHexStringAsLE(section2Ctr) + "\n" +

View file

@ -19,26 +19,25 @@
package libKonogonka.Tools.other.System2; package libKonogonka.Tools.other.System2;
import libKonogonka.KeyChainHolder; import libKonogonka.KeyChainHolder;
import libKonogonka.RainbowDump; import libKonogonka.Tools.other.System2.ini1.Ini1Provider;
import libKonogonka.ctraes.AesCtrClassic; import libKonogonka.ctraesclassic.AesCtrStream;
import libKonogonka.ctraes.AesCtrStream;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import javax.crypto.CipherInputStream; import javax.crypto.CipherInputStream;
import java.io.*; import java.io.*;
import java.math.BigInteger; import java.nio.ByteBuffer;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Arrays;
public class System2Provider { public class System2Provider {
private final static Logger log = LogManager.getLogger(System2Provider.class); private final static Logger log = LogManager.getLogger(System2Provider.class);
private byte[] Rsa2048signature; private byte[] rsa2048signature;
private System2Header header; private System2Header header;
// ... private KernelMap kernelMap;
private Ini1Provider ini1Provider;
private final String pathToFile; private final String pathToFile;
private final KeyChainHolder keyChainHolder; private final KeyChainHolder keyChainHolder;
@ -47,19 +46,54 @@ public class System2Provider {
this.pathToFile = pathToFile; this.pathToFile = pathToFile;
this.keyChainHolder = keyChainHolder; this.keyChainHolder = keyChainHolder;
readHeaderCtr(); try (BufferedInputStream stream = new BufferedInputStream(Files.newInputStream(Paths.get(pathToFile)))) {
readSignatures(stream);
readHeader(stream);
findIni1KernelMap();
}
} }
private void readHeaderCtr() throws Exception{ private void readSignatures(BufferedInputStream stream) throws Exception{
try (BufferedInputStream stream = new BufferedInputStream(Files.newInputStream(Paths.get(pathToFile)))){ rsa2048signature = new byte[0x100];
if (0x100 != stream.skip(0x100)) if (0x100 != stream.read(rsa2048signature))
throw new Exception("Can't skip RSA-2048 signature offset (0x100)"); throw new Exception("Unable to read System2 RSA-2048 signature bytes");
}
private void readHeader(BufferedInputStream stream) throws Exception{
byte[] headerBytes = new byte[0x100]; byte[] headerBytes = new byte[0x100];
if (0x100 != stream.read(headerBytes)) if (0x100 != stream.read(headerBytes))
throw new Exception("System2 header is too small"); throw new Exception("Unable to read System2 header bytes");
this.header = new System2Header(headerBytes, keyChainHolder.getRawKeySet()); 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{ public boolean exportKernel(String saveTo) throws Exception{
File location = new File(saveTo); File location = new File(saveTo);
location.mkdirs(); location.mkdirs();
@ -103,63 +137,13 @@ public class System2Provider {
} }
return true; 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); public byte[] getRsa2048signature() { return rsa2048signature; }
BufferedOutputStream extractedFileBOS = new BufferedOutputStream( public System2Header getHeader() { return header; }
Files.newOutputStream(Paths.get(saveTo+File.separator+"INI1.bin")))){ public KernelMap getKernelMap() { return kernelMap; }
public Ini1Provider getIni1Provider() throws Exception{
long iniSize = header.getSection0size()-header.getSection0offset(); if (ini1Provider == null)
ini1Provider = new Ini1Provider(header, pathToFile, kernelMap);
int blockSize = 0x200; return ini1Provider;
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;
} }
} }

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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));
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Kip1> 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<Kip1> 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;
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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
);
}
}

View file

@ -27,12 +27,12 @@ import java.io.*;
public class AesCtrBufferedInputStream extends BufferedInputStream { public class AesCtrBufferedInputStream extends BufferedInputStream {
private final static Logger log = LogManager.getLogger(AesCtrBufferedInputStream.class); private final static Logger log = LogManager.getLogger(AesCtrBufferedInputStream.class);
private final AesCtrDecryptSimple decryptor; private final AesCtrDecryptForMediaBlocks decryptor;
private final long mediaOffsetPositionStart; private final long mediaOffsetPositionStart;
private final long mediaOffsetPositionEnd; private final long mediaOffsetPositionEnd;
private final long fileSize; private final long fileSize;
public AesCtrBufferedInputStream(AesCtrDecryptSimple decryptor, public AesCtrBufferedInputStream(AesCtrDecryptForMediaBlocks decryptor,
long ncaOffsetPosition, long ncaOffsetPosition,
long mediaStartOffset, long mediaStartOffset,
long mediaEndOffset, long mediaEndOffset,

View file

@ -19,26 +19,40 @@
package libKonogonka.ctraes; package libKonogonka.ctraes;
import libKonogonka.Converter; 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 * 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 long realMediaOffset;
private byte[] IVarray; private byte[] ivArray;
private AesCtrForMediaBlocks aesCtr;
private final byte[] initialKey;
private final byte[] initialSectionCTR; private final byte[] initialSectionCTR;
private final long initialRealMediaOffset; private final long initialRealMediaOffset;
public AesCtrDecryptSimple(byte[] key, byte[] sectionCTR, long realMediaOffset) throws Exception{ public AesCtrDecryptForMediaBlocks(byte[] key, byte[] sectionCTR, long realMediaOffset) throws Exception{
this.initialKey = key; if ( ! BCinitialized)
initBCProvider();
this.key = new SecretKeySpec(key, "AES");
this.initialSectionCTR = sectionCTR; this.initialSectionCTR = sectionCTR;
this.initialRealMediaOffset = realMediaOffset; this.initialRealMediaOffset = realMediaOffset;
reset(); reset();
} }
private void initBCProvider(){
Security.addProvider(new BouncyCastleProvider());
BCinitialized = true;
}
public void skipNext(){ public void skipNext(){
realMediaOffset += 0x200; realMediaOffset += 0x200;
@ -50,7 +64,7 @@ public class AesCtrDecryptSimple {
public byte[] decryptNext(byte[] encryptedBlock) throws Exception{ public byte[] decryptNext(byte[] encryptedBlock) throws Exception{
updateIV(); updateIV();
byte[] decryptedBlock = aesCtr.decrypt(encryptedBlock, IVarray); byte[] decryptedBlock = decrypt(encryptedBlock);
realMediaOffset += 0x200; realMediaOffset += 0x200;
return decryptedBlock; return decryptedBlock;
} }
@ -58,17 +72,22 @@ public class AesCtrDecryptSimple {
private void updateIV(){ private void updateIV(){
long offset = realMediaOffset >> 4; long offset = realMediaOffset >> 4;
for (int i = 0; i < 0x8; i++){ 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; 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{ public void reset() throws Exception{
realMediaOffset = initialRealMediaOffset; realMediaOffset = initialRealMediaOffset;
aesCtr = new AesCtrForMediaBlocks(initialKey); cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");
// IV for CTR == 16 bytes // 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) // 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);
} }
} }

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

View file

@ -28,7 +28,7 @@ public class InFileStreamProducer {
private final File file; private final File file;
private final long initialOffset; private final long initialOffset;
private long subOffset; private long subOffset;
private AesCtrDecryptSimple decryptor; private AesCtrDecryptForMediaBlocks decryptor;
private long mediaStartOffset; private long mediaStartOffset;
private long mediaEndOffset; private long mediaEndOffset;
@ -46,7 +46,7 @@ public class InFileStreamProducer {
File file, File file,
long initialOffset, long initialOffset,
long subOffset, long subOffset,
AesCtrDecryptSimple decryptor, AesCtrDecryptForMediaBlocks decryptor,
long mediaStartOffset, long mediaStartOffset,
long mediaEndOffset){ long mediaEndOffset){
this.encrypted = (decryptor != null); this.encrypted = (decryptor != null);

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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");
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}
}

View file

@ -1,5 +1,5 @@
/* /*
Copyright 2019-2022 Dmitry Isaenko Copyright 2019-2023 Dmitry Isaenko
This file is part of libKonogonka. This file is part of libKonogonka.
@ -16,43 +16,38 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with libKonogonka. If not, see <https://www.gnu.org/licenses/>. along with libKonogonka. If not, see <https://www.gnu.org/licenses/>.
*/ */
package libKonogonka.ctraes; package libKonogonka.ctraesclassic;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream; import javax.crypto.CipherInputStream;
import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import java.io.InputStream;
import java.security.Security; import java.security.Security;
public class AesCtrClassic { public class AesCtrStream {
private static boolean BCinitialized = false; private static boolean BCinitialized = false;
private void initBCProvider(){ private static void initBCProvider(){
Security.addProvider(new BouncyCastleProvider()); Security.addProvider(new BouncyCastleProvider());
BCinitialized = true; BCinitialized = true;
} }
private AesCtrStream(){ }
private final Cipher cipher; public static CipherInputStream getStream(String keyString, byte[] IVarray, InputStream inputStream) throws Exception{
public AesCtrClassic(String keyString, byte[] IVarray) throws Exception{
if ( ! BCinitialized) if ( ! BCinitialized)
initBCProvider(); initBCProvider();
byte[] keyArray = hexStrToByteArray(keyString); byte[] keyArray = hexStrToByteArray(keyString);
SecretKeySpec key = new SecretKeySpec(keyArray, "AES"); 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()); IvParameterSpec iv = new IvParameterSpec(IVarray.clone());
cipher.init(Cipher.DECRYPT_MODE, key, iv); cipher.init(Cipher.DECRYPT_MODE, key, iv);
//TODO: CipherOutputStream return new CipherInputStream(inputStream, cipher);
} }
public byte[] decryptNext(byte[] encryptedData) { private static byte[] hexStrToByteArray(String s) {
return cipher.update(encryptedData);
}
private byte[] hexStrToByteArray(String s) {
int len = s.length(); int len = s.length();
byte[] data = new byte[len / 2]; byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) { for (int i = 0; i < len; i += 2) {

View file

@ -25,7 +25,7 @@ import libKonogonka.Tools.NCA.NCAProvider;
import libKonogonka.Tools.PFS0.PFS0Provider; import libKonogonka.Tools.PFS0.PFS0Provider;
import libKonogonka.Tools.PFS0.PFS0subFile; import libKonogonka.Tools.PFS0.PFS0subFile;
import libKonogonka.ctraes.AesCtrBufferedInputStream; import libKonogonka.ctraes.AesCtrBufferedInputStream;
import libKonogonka.ctraes.AesCtrDecryptSimple; import libKonogonka.ctraes.AesCtrDecryptForMediaBlocks;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -101,7 +101,7 @@ public class NSPpfs0EncryptedTest {
} }
} }
private AesCtrDecryptSimple decryptSimple; private AesCtrDecryptForMediaBlocks decryptSimple;
long ACBISoffsetPosition; long ACBISoffsetPosition;
long ACBISmediaStartOffset; long ACBISmediaStartOffset;
long ACBISmediaEndOffset; long ACBISmediaEndOffset;

View file

@ -19,19 +19,14 @@
package libKonogonka.RomFsDecrypted; package libKonogonka.RomFsDecrypted;
import libKonogonka.KeyChainHolder; import libKonogonka.KeyChainHolder;
import libKonogonka.RainbowDump;
import libKonogonka.TitleKeyChainHolder; import libKonogonka.TitleKeyChainHolder;
import libKonogonka.Tools.NCA.NCAProvider;
import libKonogonka.Tools.PFS0.PFS0Provider; import libKonogonka.Tools.PFS0.PFS0Provider;
import libKonogonka.Tools.PFS0.PFS0subFile; import libKonogonka.Tools.PFS0.PFS0subFile;
import libKonogonka.ctraes.AesCtrBufferedInputStream;
import libKonogonka.ctraes.AesCtrDecryptSimple;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.io.*; import java.io.*;
import java.nio.file.Files;
import java.util.HashMap; import java.util.HashMap;
public class PFS0Test { public class PFS0Test {

View file

@ -20,8 +20,11 @@ package libKonogonka.RomFsDecrypted;
import libKonogonka.KeyChainHolder; import libKonogonka.KeyChainHolder;
import libKonogonka.Tools.other.System2.System2Provider; 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.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; 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"; private static final String fileLocation = "/home/loper/Projects/libKonogonka/FilesForTests/6b7abe7efa17ad065b18e62d1c87a5cc.nca_extracted/ROOT/nx/package2";
@Disabled
@DisplayName("Package2 unpacked test") @DisplayName("Package2 unpacked test")
@Test @Test
void discover() throws Exception{ void discover() throws Exception{
@ -79,7 +83,7 @@ public class Package2UnpackedTest {
byte[] headerCTR = Arrays.copyOfRange(header, 0, 0x10); byte[] headerCTR = Arrays.copyOfRange(header, 0, 0x10);
for (Map.Entry<String, String> entry: package2_keys.entrySet()){ for (Map.Entry<String, String> entry: package2_keys.entrySet()){
AesCtrClassic aesCtrClassic = new AesCtrClassic(entry.getValue(), headerCTR); AesCtrDecryptClassic aesCtrClassic = new AesCtrDecryptClassic(entry.getValue(), headerCTR);
byte[] decrypted = aesCtrClassic.decryptNext(header); byte[] decrypted = aesCtrClassic.decryptNext(header);
//RainbowDump.hexDumpUTF8(decrypted); //RainbowDump.hexDumpUTF8(decrypted);
@ -89,6 +93,7 @@ public class Package2UnpackedTest {
System.out.println(entry.getKey()+" "+entry.getValue()+" "+magicString); System.out.println(entry.getKey()+" "+entry.getValue()+" "+magicString);
} }
} }
@Disabled
@DisplayName("Package2 written test") @DisplayName("Package2 written test")
@Test @Test
void implement() throws Exception{ void implement() throws Exception{
@ -96,11 +101,16 @@ public class Package2UnpackedTest {
keyChainHolder = new KeyChainHolder(keysFileLocation, null); keyChainHolder = new KeyChainHolder(keysFileLocation, null);
System2Provider provider = new System2Provider(fileLocation, keyChainHolder); System2Provider provider = new System2Provider(fileLocation, keyChainHolder);
provider.getHeader().printDebug(); 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/"); boolean exported = provider.exportKernel("/home/loper/Projects/libKonogonka/FilesForTests/own/");
System.out.println("Exported = "+exported); 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); System.out.println("Exported INI1 = "+exported);
} }

View file

@ -24,7 +24,7 @@ import libKonogonka.Tools.NCA.NCAProvider;
import libKonogonka.Tools.PFS0.PFS0Provider; import libKonogonka.Tools.PFS0.PFS0Provider;
import libKonogonka.Tools.PFS0.PFS0subFile; import libKonogonka.Tools.PFS0.PFS0subFile;
import libKonogonka.ctraes.AesCtrBufferedInputStream; import libKonogonka.ctraes.AesCtrBufferedInputStream;
import libKonogonka.ctraes.AesCtrDecryptSimple; import libKonogonka.ctraes.AesCtrDecryptForMediaBlocks;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import java.io.*; import java.io.*;
@ -72,7 +72,7 @@ public class Pfs0EncryptedTest {
} }
} }
private AesCtrDecryptSimple decryptSimple; private AesCtrDecryptForMediaBlocks decryptSimple;
long ACBISoffsetPosition; long ACBISoffsetPosition;
long ACBISmediaStartOffset; long ACBISmediaStartOffset;
long ACBISmediaEndOffset; long ACBISmediaEndOffset;
@ -104,7 +104,7 @@ public class Pfs0EncryptedTest {
ACBISmediaStartOffset = ncaProvider.getTableEntry0().getMediaStartOffset(); ACBISmediaStartOffset = ncaProvider.getTableEntry0().getMediaStartOffset();
ACBISmediaEndOffset = ncaProvider.getTableEntry0().getMediaEndOffset(); ACBISmediaEndOffset = ncaProvider.getTableEntry0().getMediaEndOffset();
decryptSimple = new AesCtrDecryptSimple( decryptSimple = new AesCtrDecryptForMediaBlocks(
ncaProvider.getDecryptedKey2(), ncaProvider.getDecryptedKey2(),
ncaProvider.getSectionBlock0().getSectionCTR(), ncaProvider.getSectionBlock0().getSectionCTR(),
ncaProvider.getTableEntry0().getMediaStartOffset()*0x200); ncaProvider.getTableEntry0().getMediaStartOffset()*0x200);

View file

@ -24,7 +24,7 @@ import libKonogonka.RainbowDump;
import libKonogonka.Tools.NCA.NCAProvider; import libKonogonka.Tools.NCA.NCAProvider;
import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader; import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader;
import libKonogonka.Tools.RomFs.FileSystemEntry; import libKonogonka.Tools.RomFs.FileSystemEntry;
import libKonogonka.ctraes.AesCtrDecryptSimple; import libKonogonka.ctraes.AesCtrDecryptForMediaBlocks;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import java.io.*; import java.io.*;
@ -90,7 +90,7 @@ public class RomFsEncryptedTest {
} }
} }
private AesCtrDecryptSimple decryptSimple; private AesCtrDecryptForMediaBlocks decryptSimple;
long ACBISoffsetPosition; long ACBISoffsetPosition;
long ACBISmediaStartOffset; long ACBISmediaStartOffset;
long ACBISmediaEndOffset; long ACBISmediaEndOffset;
@ -111,7 +111,7 @@ public class RomFsEncryptedTest {
ACBISmediaStartOffset = ncaProvider.getTableEntry1().getMediaStartOffset(); ACBISmediaStartOffset = ncaProvider.getTableEntry1().getMediaStartOffset();
ACBISmediaEndOffset = ncaProvider.getTableEntry1().getMediaEndOffset(); ACBISmediaEndOffset = ncaProvider.getTableEntry1().getMediaEndOffset();
decryptSimple = new AesCtrDecryptSimple( decryptSimple = new AesCtrDecryptForMediaBlocks(
ncaProvider.getDecryptedKey2(), ncaProvider.getDecryptedKey2(),
ncaProvider.getSectionBlock1().getSectionCTR(), ncaProvider.getSectionBlock1().getSectionCTR(),
ncaProvider.getTableEntry1().getMediaStartOffset()*0x200); ncaProvider.getTableEntry1().getMediaStartOffset()*0x200);
@ -212,11 +212,4 @@ public class RomFsEncryptedTest {
extractedFileBOS.close(); extractedFileBOS.close();
}*/ }*/
@Disabled
@Order(6)
@DisplayName("RomFsEncryptedProvider: PFS test")
@Test
void pfsValidation(){
//1 PFS and cryptotype != 0
}
} }