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

View File

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

View File

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

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

View File

@ -19,26 +19,25 @@
package libKonogonka.Tools.other.System2;
import libKonogonka.KeyChainHolder;
import libKonogonka.RainbowDump;
import libKonogonka.ctraes.AesCtrClassic;
import libKonogonka.ctraes.AesCtrStream;
import libKonogonka.Tools.other.System2.ini1.Ini1Provider;
import libKonogonka.ctraesclassic.AesCtrStream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.crypto.CipherInputStream;
import java.io.*;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
public class System2Provider {
private final static Logger log = LogManager.getLogger(System2Provider.class);
private byte[] Rsa2048signature;
private byte[] rsa2048signature;
private System2Header header;
// ...
private KernelMap kernelMap;
private Ini1Provider ini1Provider;
private final String pathToFile;
private final KeyChainHolder keyChainHolder;
@ -47,19 +46,54 @@ public class System2Provider {
this.pathToFile = pathToFile;
this.keyChainHolder = keyChainHolder;
readHeaderCtr();
}
private void readHeaderCtr() throws Exception{
try (BufferedInputStream stream = new BufferedInputStream(Files.newInputStream(Paths.get(pathToFile)))){
if (0x100 != stream.skip(0x100))
throw new Exception("Can't skip RSA-2048 signature offset (0x100)");
byte[] headerBytes = new byte[0x100];
if (0x100 != stream.read(headerBytes))
throw new Exception("System2 header is too small");
this.header = new System2Header(headerBytes, keyChainHolder.getRawKeySet());
try (BufferedInputStream stream = new BufferedInputStream(Files.newInputStream(Paths.get(pathToFile)))) {
readSignatures(stream);
readHeader(stream);
findIni1KernelMap();
}
}
private void readSignatures(BufferedInputStream stream) throws Exception{
rsa2048signature = new byte[0x100];
if (0x100 != stream.read(rsa2048signature))
throw new Exception("Unable to read System2 RSA-2048 signature bytes");
}
private void readHeader(BufferedInputStream stream) throws Exception{
byte[] headerBytes = new byte[0x100];
if (0x100 != stream.read(headerBytes))
throw new Exception("Unable to read System2 header bytes");
this.header = new System2Header(headerBytes, keyChainHolder.getRawKeySet());
}
private void findIni1KernelMap() throws Exception{
try (InputStream fis = Files.newInputStream(Paths.get(pathToFile))){
// Encrypted section comes next
long toSkip = 0x200;
if (toSkip != fis.skip(toSkip))
throw new Exception("Unable to skip offset: " + toSkip);
ByteBuffer byteBuffer = ByteBuffer.allocate(0x1000);
try (CipherInputStream stream = AesCtrStream.getStream(header.getKey(), header.getSection0Ctr(), fis);) {
for (int j = 0; j < 8; j++) {
byte[] block = new byte[0x200];
int actuallyRead;
if ((actuallyRead = stream.read(block)) != 0x200)
throw new Exception("Read failure " + actuallyRead);
byteBuffer.put(block);
}
}
byte[] searchField = byteBuffer.array();
for (int i = 0; i < 1024; i += 4) {
kernelMap = new KernelMap(searchField, i);
if (kernelMap.isValid(header.getSection0size()))
return;
}
throw new Exception("Kernel map not found");
}
}
public boolean exportKernel(String saveTo) throws Exception{
File location = new File(saveTo);
location.mkdirs();
@ -103,63 +137,13 @@ public class System2Provider {
}
return true;
}
public boolean exportIni1(String saveTo) throws Exception{
File location = new File(saveTo);
location.mkdirs();
InputStream fis = Files.newInputStream(Paths.get(pathToFile));
// Encrypted section comes next
long toSkip = 0x200 + header.getSection0offset();
if (toSkip != fis.skip(toSkip))
throw new Exception("Unable to skip offset: "+toSkip);
try (CipherInputStream stream = AesCtrStream.getStream(header.getKey(), calcIni1Ctr(), fis);
BufferedOutputStream extractedFileBOS = new BufferedOutputStream(
Files.newOutputStream(Paths.get(saveTo+File.separator+"INI1.bin")))){
long iniSize = header.getSection0size()-header.getSection0offset();
int blockSize = 0x200;
if (iniSize < 0x200)
blockSize = (int) iniSize;
long i = 0;
byte[] block = new byte[blockSize];
boolean skipMode = true;
final byte[] zeroes = new byte[blockSize];
int actuallyRead;
while (true) {
if ((actuallyRead = stream.read(block)) != blockSize)
throw new Exception("Read failure. Block Size: "+blockSize+", actuallyRead: "+actuallyRead);
if (skipMode && Arrays.equals(block, zeroes))
;
else {
skipMode = false;
extractedFileBOS.write(block);
}
i += blockSize;
if ((i + blockSize) > iniSize) {
blockSize = (int) (iniSize - i);
if (blockSize == 0)
break;
block = new byte[blockSize];
}
}
}
catch (Exception e){
log.error("File export failure", e);
return false;
}
return true;
}
private byte[] calcIni1Ctr(){
BigInteger ctr = new BigInteger(header.getSection0Ctr());
BigInteger updateTo = BigInteger.valueOf(header.getSection0offset() / 0x10L);
return ctr.add(updateTo).toByteArray();
}
public System2Header getHeader() {
return header;
public byte[] getRsa2048signature() { return rsa2048signature; }
public System2Header getHeader() { return header; }
public KernelMap getKernelMap() { return kernelMap; }
public Ini1Provider getIni1Provider() throws Exception{
if (ini1Provider == null)
ini1Provider = new Ini1Provider(header, pathToFile, kernelMap);
return ini1Provider;
}
}

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 {
private final static Logger log = LogManager.getLogger(AesCtrBufferedInputStream.class);
private final AesCtrDecryptSimple decryptor;
private final AesCtrDecryptForMediaBlocks decryptor;
private final long mediaOffsetPositionStart;
private final long mediaOffsetPositionEnd;
private final long fileSize;
public AesCtrBufferedInputStream(AesCtrDecryptSimple decryptor,
public AesCtrBufferedInputStream(AesCtrDecryptForMediaBlocks decryptor,
long ncaOffsetPosition,
long mediaStartOffset,
long mediaEndOffset,
@ -54,7 +54,7 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
private int pointerInsideDecryptedSection;
@Override
public synchronized int read(byte[] b) throws IOException {
public synchronized int read(byte[] b) throws IOException{
int bytesToRead = b.length;
if (isPointerInsideEncryptedSection()){
int bytesFromFirstBlock = 0x200 - pointerInsideDecryptedSection;

View File

@ -19,26 +19,40 @@
package libKonogonka.ctraes;
import libKonogonka.Converter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.Security;
/**
* Simplify decryption of the CTR for NCA's AesCtr sections
*/
public class AesCtrDecryptSimple {
public class AesCtrDecryptForMediaBlocks {
private static boolean BCinitialized = false;
private Cipher cipher;
private final SecretKeySpec key;
private long realMediaOffset;
private byte[] IVarray;
private AesCtrForMediaBlocks aesCtr;
private byte[] ivArray;
private final byte[] initialKey;
private final byte[] initialSectionCTR;
private final long initialRealMediaOffset;
public AesCtrDecryptSimple(byte[] key, byte[] sectionCTR, long realMediaOffset) throws Exception{
this.initialKey = key;
public AesCtrDecryptForMediaBlocks(byte[] key, byte[] sectionCTR, long realMediaOffset) throws Exception{
if ( ! BCinitialized)
initBCProvider();
this.key = new SecretKeySpec(key, "AES");
this.initialSectionCTR = sectionCTR;
this.initialRealMediaOffset = realMediaOffset;
reset();
}
private void initBCProvider(){
Security.addProvider(new BouncyCastleProvider());
BCinitialized = true;
}
public void skipNext(){
realMediaOffset += 0x200;
@ -50,7 +64,7 @@ public class AesCtrDecryptSimple {
public byte[] decryptNext(byte[] encryptedBlock) throws Exception{
updateIV();
byte[] decryptedBlock = aesCtr.decrypt(encryptedBlock, IVarray);
byte[] decryptedBlock = decrypt(encryptedBlock);
realMediaOffset += 0x200;
return decryptedBlock;
}
@ -58,17 +72,22 @@ public class AesCtrDecryptSimple {
private void updateIV(){
long offset = realMediaOffset >> 4;
for (int i = 0; i < 0x8; i++){
IVarray[0x10-i-1] = (byte)(offset & 0xff); // Note: issues could be here
ivArray[0x10-i-1] = (byte)(offset & 0xff); // Note: issues could be here
offset >>= 8;
}
}
private byte[] decrypt(byte[] encryptedData) throws Exception{
IvParameterSpec iv = new IvParameterSpec(ivArray);
cipher.init(Cipher.DECRYPT_MODE, key, iv);
return cipher.doFinal(encryptedData);
}
public void reset() throws Exception{
realMediaOffset = initialRealMediaOffset;
aesCtr = new AesCtrForMediaBlocks(initialKey);
cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");
// IV for CTR == 16 bytes
IVarray = new byte[0x10];
ivArray = new byte[0x10];
// Populate first 4 bytes taken from Header's section Block CTR (aka SecureValue)
System.arraycopy(Converter.flip(initialSectionCTR), 0x0, IVarray, 0x0, 0x8);
System.arraycopy(Converter.flip(initialSectionCTR), 0x0, ivArray, 0x0, 0x8);
}
}

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 long initialOffset;
private long subOffset;
private AesCtrDecryptSimple decryptor;
private AesCtrDecryptForMediaBlocks decryptor;
private long mediaStartOffset;
private long mediaEndOffset;
@ -46,7 +46,7 @@ public class InFileStreamProducer {
File file,
long initialOffset,
long subOffset,
AesCtrDecryptSimple decryptor,
AesCtrDecryptForMediaBlocks decryptor,
long mediaStartOffset,
long mediaEndOffset){
this.encrypted = (decryptor != null);

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.
@ -16,43 +16,38 @@
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;
package libKonogonka.ctraesclassic;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.CipherInputStream;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.InputStream;
import java.security.Security;
public class AesCtrClassic {
public class AesCtrStream {
private static boolean BCinitialized = false;
private void initBCProvider(){
private static void initBCProvider(){
Security.addProvider(new BouncyCastleProvider());
BCinitialized = true;
}
private AesCtrStream(){ }
private final Cipher cipher;
public AesCtrClassic(String keyString, byte[] IVarray) throws Exception{
public static CipherInputStream getStream(String keyString, byte[] IVarray, InputStream inputStream) throws Exception{
if ( ! BCinitialized)
initBCProvider();
byte[] keyArray = hexStrToByteArray(keyString);
SecretKeySpec key = new SecretKeySpec(keyArray, "AES");
cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");
IvParameterSpec iv = new IvParameterSpec(IVarray.clone());
cipher.init(Cipher.DECRYPT_MODE, key, iv);
//TODO: CipherOutputStream
return new CipherInputStream(inputStream, cipher);
}
public byte[] decryptNext(byte[] encryptedData) {
return cipher.update(encryptedData);
}
private byte[] hexStrToByteArray(String s) {
private static byte[] hexStrToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {

View File

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

View File

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

View File

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

View File

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

View File

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