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.
continuous-integration/drone/push Build is passing Details

master
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

@ -77,67 +77,59 @@ for (byte i = 0; i < 16; i++){
public class KernelAccessControlProvider { public class KernelAccessControlProvider {
private final static Logger log = LogManager.getLogger(KernelAccessControlProvider.class); private final static Logger log = LogManager.getLogger(KernelAccessControlProvider.class);
private static final int KERNELFLAGS = 3, private static final int KERNELFLAGS = 3,
SYSCALLMASK = 4, SYSCALLMASK = 4,
MAPIOORNORMALRANGE = 6, MAPIOORNORMALRANGE = 6,
MAPNORMALPAGE_RW = 7, MAPNORMALPAGE_RW = 7,
INTERRUPTPAIR = 11, INTERRUPTPAIR = 11,
APPLICATIONTYPE = 13, APPLICATIONTYPE = 13,
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);
private void readHeaderCtr() throws Exception{ findIni1KernelMap();
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());
} }
} }
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{ 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,
@ -54,7 +54,7 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
private int pointerInsideDecryptedSection; private int pointerInsideDecryptedSection;
@Override @Override
public synchronized int read(byte[] b) throws IOException { public synchronized int read(byte[] b) throws IOException{
int bytesToRead = b.length; int bytesToRead = b.length;
if (isPointerInsideEncryptedSection()){ if (isPointerInsideEncryptedSection()){
int bytesFromFirstBlock = 0x200 - pointerInsideDecryptedSection; int bytesFromFirstBlock = 0x200 - pointerInsideDecryptedSection;

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
}
} }