Rename AesCtr classes used for NCA's media blocks decryption, add KIP1, KernelMap, INI1 classes that helps to retrieve information from headers, export, decrypt, etc. See Package2UnpackedTest.java for details.
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
83cf199beb
commit
e406dd642c
21 changed files with 989 additions and 245 deletions
|
@ -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);
|
||||
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
118
src/main/java/libKonogonka/Tools/other/System2/KernelMap.java
Normal file
118
src/main/java/libKonogonka/Tools/other/System2/KernelMap.java
Normal 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));
|
||||
}
|
||||
}
|
|
@ -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" +
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
167
src/main/java/libKonogonka/Tools/other/System2/ini1/Kip1.java
Normal file
167
src/main/java/libKonogonka/Tools/other/System2/ini1/Kip1.java
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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) {
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue