From dd6c81c7fde7840b770429feec2649550c53b2ec Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko Date: Thu, 23 May 2019 02:39:40 +0300 Subject: [PATCH] start working on encrypted sections extractor --- .../NCA/NCASectionContentController.java | 3 +- .../Controllers/NSP/NSPController.java | 3 +- .../NSP/Pfs0TableViewController.java | 3 +- .../konogonka/Tools/NCA/NCAContentPFS0.java | 157 +++++++++--------- .../java/konogonka/Tools/NCA/NCAProvider.java | 1 + .../konogonka/Tools/PFS0/IPFS0Provider.java | 11 ++ .../Tools/PFS0/PFS0EncryptedProvider.java | 114 +++++++++++++ .../konogonka/Tools/PFS0/PFS0Provider.java | 139 ++++++++-------- 8 files changed, 276 insertions(+), 155 deletions(-) create mode 100644 src/main/java/konogonka/Tools/PFS0/IPFS0Provider.java create mode 100644 src/main/java/konogonka/Tools/PFS0/PFS0EncryptedProvider.java diff --git a/src/main/java/konogonka/Controllers/NCA/NCASectionContentController.java b/src/main/java/konogonka/Controllers/NCA/NCASectionContentController.java index 4a47bba..226a8a5 100644 --- a/src/main/java/konogonka/Controllers/NCA/NCASectionContentController.java +++ b/src/main/java/konogonka/Controllers/NCA/NCASectionContentController.java @@ -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 sha256hashList) { + public void populateFields(IPFS0Provider pfs0, File file, LinkedList sha256hashList) { resetTab(); SectionPFS0Controller.setData(pfs0, file); if (sha256hashList != null){ diff --git a/src/main/java/konogonka/Controllers/NSP/NSPController.java b/src/main/java/konogonka/Controllers/NSP/NSPController.java index 7b56de7..2ebb890 100644 --- a/src/main/java/konogonka/Controllers/NSP/NSPController.java +++ b/src/main/java/konogonka/Controllers/NSP/NSPController.java @@ -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; diff --git a/src/main/java/konogonka/Controllers/NSP/Pfs0TableViewController.java b/src/main/java/konogonka/Controllers/NSP/Pfs0TableViewController.java index 422f98f..011b63f 100644 --- a/src/main/java/konogonka/Controllers/NSP/Pfs0TableViewController.java +++ b/src/main/java/konogonka/Controllers/NSP/Pfs0TableViewController.java @@ -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) { diff --git a/src/main/java/konogonka/Tools/NCA/NCAContentPFS0.java b/src/main/java/konogonka/Tools/NCA/NCAContentPFS0.java index 8e38bd6..29c56e1 100644 --- a/src/main/java/konogonka/Tools/NCA/NCAContentPFS0.java +++ b/src/main/java/konogonka/Tools/NCA/NCAContentPFS0.java @@ -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 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 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"); } } } diff --git a/src/main/java/konogonka/Tools/NCA/NCAProvider.java b/src/main/java/konogonka/Tools/NCA/NCAProvider.java index 7bdf92d..2d59779 100644 --- a/src/main/java/konogonka/Tools/NCA/NCAProvider.java +++ b/src/main/java/konogonka/Tools/NCA/NCAProvider.java @@ -267,6 +267,7 @@ public class NCAProvider { } catch (Exception e){ e.printStackTrace(); + System.out.println("No title.keys loaded?"); return null; } } diff --git a/src/main/java/konogonka/Tools/PFS0/IPFS0Provider.java b/src/main/java/konogonka/Tools/PFS0/IPFS0Provider.java new file mode 100644 index 0000000..a4dcd23 --- /dev/null +++ b/src/main/java/konogonka/Tools/PFS0/IPFS0Provider.java @@ -0,0 +1,11 @@ +package konogonka.Tools.PFS0; + +public interface IPFS0Provider { + String getMagic(); + int getFilesCount(); + int getStringTableSize(); + byte[] getPadding(); + + long getRawFileDataStart(); + PFS0subFile[] getPfs0subFiles(); +} diff --git a/src/main/java/konogonka/Tools/PFS0/PFS0EncryptedProvider.java b/src/main/java/konogonka/Tools/PFS0/PFS0EncryptedProvider.java new file mode 100644 index 0000000..46baf4b --- /dev/null +++ b/src/main/java/konogonka/Tools/PFS0/PFS0EncryptedProvider.java @@ -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; } +} diff --git a/src/main/java/konogonka/Tools/PFS0/PFS0Provider.java b/src/main/java/konogonka/Tools/PFS0/PFS0Provider.java index 9699794..1bcfea7 100644 --- a/src/main/java/konogonka/Tools/PFS0/PFS0Provider.java +++ b/src/main/java/konogonka/Tools/PFS0/PFS0Provider.java @@ -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