start working on encrypted sections extractor

This commit is contained in:
Dmitry Isaenko 2019-05-23 02:39:40 +03:00
parent e2f7f93988
commit dd6c81c7fd
8 changed files with 276 additions and 155 deletions

View file

@ -7,6 +7,7 @@ import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import konogonka.Controllers.NSP.NSPController;
import konogonka.LoperConverter;
import konogonka.Tools.PFS0.IPFS0Provider;
import konogonka.Tools.PFS0.PFS0Provider;
import java.io.File;
@ -23,7 +24,7 @@ public class NCASectionContentController{
sha256pane.getChildren().clear();
}
public void populateFields(PFS0Provider pfs0, File file, LinkedList<byte[]> sha256hashList) {
public void populateFields(IPFS0Provider pfs0, File file, LinkedList<byte[]> sha256hashList) {
resetTab();
SectionPFS0Controller.setData(pfs0, file);
if (sha256hashList != null){

View file

@ -6,6 +6,7 @@ import javafx.scene.control.Label;
import konogonka.Controllers.IRowModel;
import konogonka.Controllers.TabController;
import konogonka.MediatorControl;
import konogonka.Tools.PFS0.IPFS0Provider;
import konogonka.Tools.PFS0.PFS0Provider;
import konogonka.Workers.AnalyzerNSP;
import konogonka.Workers.NspXciExtractor;
@ -102,7 +103,7 @@ public class NSPController implements TabController {
/**
* Just populate fields by already analyzed PFS0
* */
public void setData(PFS0Provider pfs0, File fileWithNca){
public void setData(IPFS0Provider pfs0, File fileWithNca){
if (pfs0 != null){
if (fileWithNca != null)
this.selectedFile = fileWithNca;

View file

@ -17,6 +17,7 @@ import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.util.Callback;
import konogonka.Controllers.IRowModel;
import konogonka.Tools.PFS0.IPFS0Provider;
import konogonka.Tools.PFS0.PFS0Provider;
import java.net.URL;
@ -144,7 +145,7 @@ public class Pfs0TableViewController implements Initializable {
/**
* Add files when user selected them
* */
public void setNSPToTable(PFS0Provider pfs){
public void setNSPToTable(IPFS0Provider pfs){
rowsObsLst.clear();
Pfs0RowModel.resetNumCnt();
if (pfs == null) {

View file

@ -3,6 +3,8 @@ package konogonka.Tools.NCA;
import konogonka.LoperConverter;
import konogonka.RainbowHexDump;
import konogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock;
import konogonka.Tools.PFS0.IPFS0Provider;
import konogonka.Tools.PFS0.PFS0EncryptedProvider;
import konogonka.Tools.PFS0.PFS0Provider;
import konogonka.ctraes.AesCtr;
@ -11,66 +13,68 @@ import java.util.LinkedList;
public class NCAContentPFS0 {
private LinkedList<byte[]> SHA256hashes;
private PFS0Provider pfs0;
private IPFS0Provider pfs0;
// TODO: if decryptedKey is empty, thorow exception ??
public NCAContentPFS0(File file, long offsetPosition, NCASectionBlock ncaSectionBlock, NCAHeaderTableEntry ncaHeaderTableEntry, byte[] decryptedKey){
SHA256hashes = new LinkedList<>();
try {
// If it's PFS0Provider
if (ncaSectionBlock.getSuperBlockPFS0() != null){
// IF NO ENCRYPTION
if (ncaSectionBlock.getCryptoType() == 0x1) {
RandomAccessFile raf = new RandomAccessFile(file, "r");
long thisMediaLocation = offsetPosition + (ncaHeaderTableEntry.getMediaStartOffset() * 0x200);
long hashTableLocation = thisMediaLocation + ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset();
long pfs0Location = thisMediaLocation + ncaSectionBlock.getSuperBlockPFS0().getPfs0offset();
try {
// IF NO ENCRYPTION
if (ncaSectionBlock.getCryptoType() == 0x1) {
RandomAccessFile raf = new RandomAccessFile(file, "r");
long thisMediaLocation = offsetPosition + (ncaHeaderTableEntry.getMediaStartOffset() * 0x200);
long hashTableLocation = thisMediaLocation + ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset();
long pfs0Location = thisMediaLocation + ncaSectionBlock.getSuperBlockPFS0().getPfs0offset();
raf.seek(hashTableLocation);
raf.seek(hashTableLocation);
byte[] rawData;
long sha256recordsNumber = ncaSectionBlock.getSuperBlockPFS0().getHashTableSize() / 0x20;
// Collect hashes
for (int i = 0; i < sha256recordsNumber; i++){
rawData = new byte[0x20]; // 32 bytes - size of SHA256 hash
if (raf.read(rawData) != -1)
SHA256hashes.add(rawData);
else
return; // TODO: fix
byte[] rawData;
long sha256recordsNumber = ncaSectionBlock.getSuperBlockPFS0().getHashTableSize() / 0x20;
// Collect hashes
for (int i = 0; i < sha256recordsNumber; i++){
rawData = new byte[0x20]; // 32 bytes - size of SHA256 hash
if (raf.read(rawData) != -1)
SHA256hashes.add(rawData);
else {
raf.close();
return; // TODO: fix
}
}
raf.close();
// Get pfs0
pfs0 = new PFS0Provider(file, pfs0Location);
}
// If encrypted (regular)
else if (ncaSectionBlock.getCryptoType() == 0x3){
new CryptoSection03(file,
offsetPosition,
decryptedKey,
ncaSectionBlock,
ncaHeaderTableEntry.getMediaStartOffset(),
ncaHeaderTableEntry.getMediaEndOffset());
}
raf.close();
// Get pfs0
pfs0 = new PFS0Provider(file, pfs0Location);
}
// If encrypted (regular)
else if (ncaSectionBlock.getCryptoType() == 0x3){ // d0c1...
new CryptoSection03(file,
offsetPosition,
decryptedKey,
ncaSectionBlock,
ncaHeaderTableEntry.getMediaStartOffset(),
ncaHeaderTableEntry.getMediaEndOffset());
catch (Exception e){
e.printStackTrace();
}
}
else if (ncaSectionBlock.getSuperBlockIVFC() != null){
// TODO
}
else {
return; // TODO: FIX THIS STUFF
}
}
catch (Exception e){
e.printStackTrace();
}
}
public LinkedList<byte[]> getSHA256hashes() { return SHA256hashes; }
public PFS0Provider getPfs0() { return pfs0; }
public IPFS0Provider getPfs0() { return pfs0; }
private class CryptoSection03{
CryptoSection03(File file, long offsetPosition, byte[] decryptedKey, NCASectionBlock ncaSectionBlock, long mediaStartOffset, long mediaEndOffset) throws Exception{
//--------------------------------------------------------------------------------------------------
/*//--------------------------------------------------------------------------------------------------
System.out.println("Media start location: " + mediaStartOffset);
System.out.println("Media end location: " + mediaEndOffset);
System.out.println("Media size : " + (mediaEndOffset-mediaStartOffset));
@ -82,13 +86,13 @@ public class NCAContentPFS0 {
System.out.println("KEY: " + LoperConverter.byteArrToHexString(decryptedKey));
System.out.println("CTR: " + LoperConverter.byteArrToHexString(ncaSectionBlock.getSectionCTR()));
System.out.println();
//--------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------*/
if (decryptedKey == null)
throw new Exception("CryptoSection03: unable to proceed. No decrypted key provided.");
RandomAccessFile raf = new RandomAccessFile(file, "r");
raf.seek(offsetPosition + (mediaStartOffset * 0x200));
long abosluteOffsetPosition = offsetPosition + (mediaStartOffset * 0x200);
raf.seek(abosluteOffsetPosition);
AesCtrDecryptor decryptor = new AesCtrDecryptor(decryptedKey, ncaSectionBlock.getSectionCTR(), mediaStartOffset * 0x200);
@ -99,13 +103,14 @@ public class NCAContentPFS0 {
PipedOutputStream streamOut = new PipedOutputStream();
PipedInputStream streamInp = new PipedInputStream(streamOut);
new Thread(new ParseThread(
Thread pThread = new Thread(new ParseThread(
streamInp,
ncaSectionBlock.getSuperBlockPFS0().getPfs0offset(),
ncaSectionBlock.getSuperBlockPFS0().getPfs0size(),
ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset(),
ncaSectionBlock.getSuperBlockPFS0().getHashTableSize()
)).start();
));
pThread.start();
// Decrypt data
for (int i = 0; i < mediaBlockSize; i++){
encryptedBlock = new byte[0x200];
@ -113,12 +118,16 @@ public class NCAContentPFS0 {
//dectyptedBlock = aesCtr.decrypt(encryptedBlock);
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
// Writing decrypted data to pipe
streamOut.write(dectyptedBlock);
try {
streamOut.write(dectyptedBlock);
}
catch (IOException e){
break;
}
}
}
streamOut.flush();
pThread.join();
streamOut.close();
raf.close();
}
/*
@ -187,51 +196,39 @@ public class NCAContentPFS0 {
return; // TODO: fix?
counter = hashTableOffset;
}
// Main loop
while (true){
// Loop for collecting all recrods from sha256 hash table
while ((counter - hashTableOffset) < hashTableSize){
int hashCounter = 0;
byte[] sectionHash = new byte[0x20];
// Loop for collecting bytes for every SINGLE records, where record size == 0x20
while (hashCounter < 0x20){
int currentByte = pipedInputStream.read();
if (currentByte == -1)
break;
sectionHash[hashCounter] = (byte)currentByte;
hashCounter++;
counter++;
}
// Write after collecting
SHA256hashes.add(sectionHash); // From the NCAContentProvider obviously
}
// Skip padding and go to PFS0 location
if (counter < pfs0offset){
long toSkip = pfs0offset-counter;
if (toSkip != pipedInputStream.skip(toSkip))
return; // TODO: fix?
counter += toSkip;
}
//---------------------------------------------------------
byte[] magic = new byte[0x4];
for (int i = 0; i < 4; i++){
// Loop for collecting all recrods from sha256 hash table
while ((counter - hashTableOffset) < hashTableSize){
int hashCounter = 0;
byte[] sectionHash = new byte[0x20];
// Loop for collecting bytes for every SINGLE records, where record size == 0x20
while (hashCounter < 0x20){
int currentByte = pipedInputStream.read();
if (currentByte == -1)
break;
magic[i] = (byte)currentByte;
sectionHash[hashCounter] = (byte)currentByte;
hashCounter++;
counter++;
}
RainbowHexDump.hexDumpUTF8(magic);
while (pipedInputStream.read() != -1)
;
break;
// Write after collecting
SHA256hashes.add(sectionHash); // From the NCAContentProvider obviously
}
// Skip padding and go to PFS0 location
if (counter < pfs0offset){
long toSkip = pfs0offset-counter;
if (toSkip != pipedInputStream.skip(toSkip))
return; // TODO: fix?
counter += toSkip;
}
//---------------------------------------------------------
pfs0 = new PFS0EncryptedProvider(pipedInputStream);
pipedInputStream.close();
}
catch (IOException ioe){
catch (Exception e){
System.out.println("'ParseThread' thread exception");
ioe.printStackTrace();
e.printStackTrace();
}
finally {
System.out.println("ParseThread thread died.");
System.out.println("Thread dies");
}
}
}

View file

@ -267,6 +267,7 @@ public class NCAProvider {
}
catch (Exception e){
e.printStackTrace();
System.out.println("No title.keys loaded?");
return null;
}
}

View file

@ -0,0 +1,11 @@
package konogonka.Tools.PFS0;
public interface IPFS0Provider {
String getMagic();
int getFilesCount();
int getStringTableSize();
byte[] getPadding();
long getRawFileDataStart();
PFS0subFile[] getPfs0subFiles();
}

View file

@ -0,0 +1,114 @@
package konogonka.Tools.PFS0;
import java.io.PipedInputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import static konogonka.LoperConverter.getLEint;
import static konogonka.LoperConverter.getLElong;
public class PFS0EncryptedProvider implements IPFS0Provider{
private long rawFileDataStart; // If -1 then this PFS0 located @ encrypted region
private String magic;
private int filesCount;
private int stringTableSize;
private byte[] padding;
private PFS0subFile[] pfs0subFiles;
//---------------------------------------
/*
absOffsetPosOfMediaBlock
Counter - PFS0 Position
mediaBlockSize - PFS0 Subsustem Size
* */
//---------------------------------------
public PFS0EncryptedProvider(PipedInputStream pipedInputStream) throws Exception{
byte[] fileStartingBytes = new byte[0x10];
// Read PFS0Provider, files count, header, padding (4 zero bytes)
for (int i = 0; i < 0x10; i++){
int currentByte = pipedInputStream.read();
if (currentByte == -1) {
throw new Exception("PFS0: Reading stream suddenly ended while trying to read starting 0x10 bytes");
}
fileStartingBytes[i] = (byte)currentByte;
}
// Check PFS0Provider
magic = new String(fileStartingBytes, 0x0, 0x4, StandardCharsets.US_ASCII);
if (! magic.equals("PFS0")){
throw new Exception("PFS0Provider: Bad magic");
}
// Get files count
filesCount = getLEint(fileStartingBytes, 0x4);
if (filesCount <= 0 ) {
throw new Exception("PFS0Provider: Files count is too small");
}
// Get string table
stringTableSize = getLEint(fileStartingBytes, 0x8);
if (stringTableSize <= 0 ){
throw new Exception("PFS0Provider: String table is too small");
}
padding = Arrays.copyOfRange(fileStartingBytes, 0xc, 0x10);
//---------------------------------------------------------------------------------------------------------
pfs0subFiles = new PFS0subFile[filesCount];
long[] offsetsSubFiles = new long[filesCount];
long[] sizesSubFiles = new long[filesCount];
int[] strTableOffsets = new int[filesCount];
byte[][] zeroBytes = new byte[filesCount][];
byte[] fileEntryTable = new byte[0x18];
for (int i=0; i < filesCount; i++){
for (int j = 0; j < 0x18; j++){
int currentByte = pipedInputStream.read();
if (currentByte == -1) {
throw new Exception("PFS0: Reading stream suddenly ended while trying to read File Entry Table #"+i);
}
fileEntryTable[j] = (byte)currentByte;
}
offsetsSubFiles[i] = getLElong(fileEntryTable, 0);
sizesSubFiles[i] = getLElong(fileEntryTable, 0x8);
strTableOffsets[i] = getLEint(fileEntryTable, 0x10);
zeroBytes[i] = Arrays.copyOfRange(fileEntryTable, 0x14, 0x18);
}
//**********************************************************************************************************
// In here pointer in front of String table
String[] subFileNames = new String[filesCount];
byte[] stringTbl = new byte[stringTableSize];
for (int i = 0; i < stringTableSize; i++){
int currentByte = pipedInputStream.read();
if (currentByte == -1) {
throw new Exception("PFS0: Reading stream suddenly ended while trying to read string table");
}
stringTbl[i] = (byte)currentByte;
}
for (int i=0; i < filesCount; i++){
int j = 0;
while (stringTbl[strTableOffsets[i]+j] != (byte)0x00)
j++;
subFileNames[i] = new String(stringTbl, strTableOffsets[i], j, StandardCharsets.UTF_8);
}
for (int i = 0; i < filesCount; i++){
pfs0subFiles[i] = new PFS0subFile(
subFileNames[i],
offsetsSubFiles[i],
sizesSubFiles[i],
zeroBytes[i]
);
}
rawFileDataStart = -1;
}
public String getMagic() { return magic; }
public int getFilesCount() { return filesCount; }
public int getStringTableSize() { return stringTableSize; }
public byte[] getPadding() { return padding; }
public long getRawFileDataStart() { return rawFileDataStart; }
public PFS0subFile[] getPfs0subFiles() { return pfs0subFiles; }
}

View file

@ -3,15 +3,14 @@ package konogonka.Tools.PFS0;
import konogonka.RainbowHexDump;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import static konogonka.LoperConverter.*;
public class PFS0Provider {
private long rawFileDataStart;
public class PFS0Provider implements IPFS0Provider{
private long rawFileDataStart; // If -1 then this PFS0 located @ encrypted region
private String magic;
private int filesCount;
@ -22,80 +21,76 @@ public class PFS0Provider {
public PFS0Provider(File fileWithPfs0) throws Exception{ this(fileWithPfs0, 0); }
public PFS0Provider(File fileWithPfs0, long pfs0offsetPosition) throws Exception{
try {
RandomAccessFile raf = new RandomAccessFile(fileWithPfs0, "r");
raf.seek(pfs0offsetPosition);
byte[] fileStartingBytes = new byte[0x10];
// Read PFS0Provider, files count, header, padding (4 zero bytes)
if (raf.read(fileStartingBytes) != 0x10){
raf.close();
throw new Exception("PFS0Provider: Unable to read starting bytes");
}
// Check PFS0Provider
magic = new String(fileStartingBytes, 0x0, 0x4, StandardCharsets.US_ASCII);
if (! magic.equals("PFS0")){
raf.close();
throw new Exception("PFS0Provider: Bad magic");
}
// Get files count
filesCount = getLEint(fileStartingBytes, 0x4);
if (filesCount <= 0 ) {
raf.close();
throw new Exception("PFS0Provider: Files count is too small");
}
// Get string table
stringTableSize = getLEint(fileStartingBytes, 0x8);
if (stringTableSize <= 0 ){
raf.close();
throw new Exception("PFS0Provider: String table is too small");
}
padding = Arrays.copyOfRange(fileStartingBytes, 0xc, 0x10);
//---------------------------------------------------------------------------------------------------------
pfs0subFiles = new PFS0subFile[filesCount];
RandomAccessFile raf = new RandomAccessFile(fileWithPfs0, "r"); // TODO: replace to bufferedInputStream
long[] offsetsSubFiles = new long[filesCount];
long[] sizesSubFiles = new long[filesCount];
int[] strTableOffsets = new int[filesCount];
byte[][] zeroBytes = new byte[filesCount][];
byte[] fileEntryTable = new byte[0x18];
for (int i=0; i<filesCount; i++){
if (raf.read(fileEntryTable) != 0x18)
throw new Exception("PFS0Provider: String table is too small");
offsetsSubFiles[i] = getLElong(fileEntryTable, 0);
sizesSubFiles[i] = getLElong(fileEntryTable, 0x8);
strTableOffsets[i] = getLEint(fileEntryTable, 0x10);
zeroBytes[i] = Arrays.copyOfRange(fileEntryTable, 0x14, 0x18);
}
//**********************************************************************************************************
// In here pointer in front of String table
String[] subFileNames = new String[filesCount];
byte[] stringTbl = new byte[stringTableSize];
if (raf.read(stringTbl) != stringTableSize){
throw new Exception("Read PFS0Provider String table failure. Can't read requested string table size ("+stringTableSize+")");
}
for (int i=0; i < filesCount; i++){
int j = 0;
while (stringTbl[strTableOffsets[i]+j] != (byte)0x00)
j++;
subFileNames[i] = new String(stringTbl, strTableOffsets[i], j, StandardCharsets.UTF_8);
}
for (int i = 0; i < filesCount; i++){
pfs0subFiles[i] = new PFS0subFile(
subFileNames[i],
offsetsSubFiles[i],
sizesSubFiles[i],
zeroBytes[i]
);
}
rawFileDataStart = raf.getFilePointer();
raf.seek(pfs0offsetPosition);
byte[] fileStartingBytes = new byte[0x10];
// Read PFS0Provider, files count, header, padding (4 zero bytes)
if (raf.read(fileStartingBytes) != 0x10){
raf.close();
throw new Exception("PFS0Provider: Unable to read starting bytes");
}
catch (IOException ioe){
throw new IOException("PFS0Provider: "+ioe.getMessage());
// Check PFS0Provider
magic = new String(fileStartingBytes, 0x0, 0x4, StandardCharsets.US_ASCII);
if (! magic.equals("PFS0")){
raf.close();
throw new Exception("PFS0Provider: Bad magic");
}
// Get files count
filesCount = getLEint(fileStartingBytes, 0x4);
if (filesCount <= 0 ) {
raf.close();
throw new Exception("PFS0Provider: Files count is too small");
}
// Get string table
stringTableSize = getLEint(fileStartingBytes, 0x8);
if (stringTableSize <= 0 ){
raf.close();
throw new Exception("PFS0Provider: String table is too small");
}
padding = Arrays.copyOfRange(fileStartingBytes, 0xc, 0x10);
//---------------------------------------------------------------------------------------------------------
pfs0subFiles = new PFS0subFile[filesCount];
long[] offsetsSubFiles = new long[filesCount];
long[] sizesSubFiles = new long[filesCount];
int[] strTableOffsets = new int[filesCount];
byte[][] zeroBytes = new byte[filesCount][];
byte[] fileEntryTable = new byte[0x18];
for (int i=0; i<filesCount; i++){
if (raf.read(fileEntryTable) != 0x18)
throw new Exception("PFS0Provider: String table is too small");
offsetsSubFiles[i] = getLElong(fileEntryTable, 0);
sizesSubFiles[i] = getLElong(fileEntryTable, 0x8);
strTableOffsets[i] = getLEint(fileEntryTable, 0x10);
zeroBytes[i] = Arrays.copyOfRange(fileEntryTable, 0x14, 0x18);
}
//**********************************************************************************************************
// In here pointer in front of String table
String[] subFileNames = new String[filesCount];
byte[] stringTbl = new byte[stringTableSize];
if (raf.read(stringTbl) != stringTableSize){
throw new Exception("Read PFS0Provider String table failure. Can't read requested string table size ("+stringTableSize+")");
}
for (int i=0; i < filesCount; i++){
int j = 0;
while (stringTbl[strTableOffsets[i]+j] != (byte)0x00)
j++;
subFileNames[i] = new String(stringTbl, strTableOffsets[i], j, StandardCharsets.UTF_8);
}
for (int i = 0; i < filesCount; i++){
pfs0subFiles[i] = new PFS0subFile(
subFileNames[i],
offsetsSubFiles[i],
sizesSubFiles[i],
zeroBytes[i]
);
}
rawFileDataStart = raf.getFilePointer();
raf.close();
}
public String getMagic() { return magic; }