Refactoring

Fixing CTR IV calc logic
This commit is contained in:
Dmitry Isaenko 2019-05-21 03:36:01 +03:00
parent 18bdd0f3eb
commit 842be7048d
2 changed files with 167 additions and 140 deletions

View file

@ -17,11 +17,11 @@ public class NCAContentPFS0 {
public NCAContentPFS0(File file, long offsetPosition, NCASectionBlock ncaSectionBlock, NCAHeaderTableEntry ncaHeaderTableEntry, byte[] decryptedKey){ public NCAContentPFS0(File file, long offsetPosition, NCASectionBlock ncaSectionBlock, NCAHeaderTableEntry ncaHeaderTableEntry, byte[] decryptedKey){
SHA256hashes = new LinkedList<>(); SHA256hashes = new LinkedList<>();
try { try {
RandomAccessFile raf = new RandomAccessFile(file, "r");
// If it's PFS0Provider // If it's PFS0Provider
if (ncaSectionBlock.getSuperBlockPFS0() != null){ if (ncaSectionBlock.getSuperBlockPFS0() != null){
// IF NO ENCRYPTION // IF NO ENCRYPTION
if (ncaSectionBlock.getCryptoType() == 0x1) { if (ncaSectionBlock.getCryptoType() == 0x1) {
RandomAccessFile raf = new RandomAccessFile(file, "r");
long thisMediaLocation = offsetPosition + (ncaHeaderTableEntry.getMediaStartOffset() * 0x200); long thisMediaLocation = offsetPosition + (ncaHeaderTableEntry.getMediaStartOffset() * 0x200);
long hashTableLocation = thisMediaLocation + ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset(); long hashTableLocation = thisMediaLocation + ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset();
long pfs0Location = thisMediaLocation + ncaSectionBlock.getSuperBlockPFS0().getPfs0offset(); long pfs0Location = thisMediaLocation + ncaSectionBlock.getSuperBlockPFS0().getPfs0offset();
@ -42,77 +42,14 @@ public class NCAContentPFS0 {
// Get pfs0 // Get pfs0
pfs0 = new PFS0Provider(file, pfs0Location); pfs0 = new PFS0Provider(file, pfs0Location);
} }
// If encrypted (regular) todo: check keys provided // If encrypted (regular)
else if (ncaSectionBlock.getCryptoType() == 0x3){ // d0c1... else if (ncaSectionBlock.getCryptoType() == 0x3){ // d0c1...
if (decryptedKey == null) new CryptoSection03(file,
return; // TODO: FIX offsetPosition,
decryptedKey,
//-------------------------------------------------------------------------------------------------- ncaSectionBlock,
System.out.println("Media start location: " + ncaHeaderTableEntry.getMediaStartOffset()); ncaHeaderTableEntry.getMediaStartOffset(),
System.out.println("Media end location: " + ncaHeaderTableEntry.getMediaEndOffset()); ncaHeaderTableEntry.getMediaEndOffset());
System.out.println("Media size : " + (ncaHeaderTableEntry.getMediaEndOffset()-ncaHeaderTableEntry.getMediaStartOffset()));
System.out.println("Media act. location: " + (offsetPosition + (ncaHeaderTableEntry.getMediaStartOffset() * 0x200)));
System.out.println("SHA256 hash tbl size: " + ncaSectionBlock.getSuperBlockPFS0().getHashTableSize());
System.out.println("SHA256 hash tbl offs: " + ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset());
System.out.println("SHA256 records: " + (ncaSectionBlock.getSuperBlockPFS0().getHashTableSize() / 0x20));
System.out.println("KEY: " + LoperConverter.byteArrToHexString(decryptedKey));
System.out.println("CTR: " + LoperConverter.byteArrToHexString(ncaSectionBlock.getSectionCTR()));
System.out.println("PFS0 Offs: " + ncaSectionBlock.getSuperBlockPFS0().getPfs0offset());
System.out.println();
//--------------------------------------------------------------------------------------------------
long thisMediaLocation = offsetPosition + (ncaHeaderTableEntry.getMediaStartOffset() * 0x200); // According to real file
long hashTableLocation = thisMediaLocation + ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset(); // According to real file
raf.seek(thisMediaLocation);
try {
// IV for CTR == 32 bytes
byte[] IVarray = new byte[0x10];
// Populate first 8 bytes taken from Header's section Block CTR
System.arraycopy(LoperConverter.flip(ncaSectionBlock.getSectionCTR()), 0x0, IVarray,0x0, 0x8);
// Populate last 8 bytes calculated. Thanks hactool project!
// TODO: here is too much magic. It MUST be clarified and simplified
long mediaStrtOffReal = ncaHeaderTableEntry.getMediaStartOffset() * 0x200; // NOTE: long actually should be unsigned.. for calculation it's not critical, but for representation it is
mediaStrtOffReal >>= 4;
for (int i = 0; i < 0x8; i++){
IVarray[0x10-i-1] = (byte)(mediaStrtOffReal & 0xff); // Note: issues could be here
mediaStrtOffReal >>= 8;
}
AesCtr aesCtr = new AesCtr(decryptedKey, IVarray);
byte[] encryptedBlock;
byte[] dectyptedBlock;
long mediaBlockSize = ncaHeaderTableEntry.getMediaEndOffset() - ncaHeaderTableEntry.getMediaStartOffset();
// Prepare thread to parse encrypted data
PipedOutputStream streamOut = new PipedOutputStream();
PipedInputStream streamInp = new PipedInputStream(streamOut);
new Thread(new ParseEncrypted(
SHA256hashes,
streamInp,
ncaSectionBlock.getSuperBlockPFS0().getPfs0offset(),
ncaSectionBlock.getSuperBlockPFS0().getPfs0size(),
ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset(),
ncaSectionBlock.getSuperBlockPFS0().getHashTableSize()
)).start();
// Decrypt data
for (int i = 0; i < mediaBlockSize; i++){
encryptedBlock = new byte[0x200];
if (raf.read(encryptedBlock) != -1){
dectyptedBlock = aesCtr.decrypt(encryptedBlock);
// Writing decrypted data to pipe
streamOut.write(dectyptedBlock);
}
}
streamOut.flush();
}
catch (Exception e){
e.printStackTrace();
}
raf.close();
} }
} }
else if (ncaSectionBlock.getSuperBlockIVFC() != null){ else if (ncaSectionBlock.getSuperBlockIVFC() != null){
@ -130,82 +67,171 @@ public class NCAContentPFS0 {
public LinkedList<byte[]> getSHA256hashes() { return SHA256hashes; } public LinkedList<byte[]> getSHA256hashes() { return SHA256hashes; }
public PFS0Provider getPfs0() { return pfs0; } public PFS0Provider 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));
System.out.println("Media act. location: " + (offsetPosition + (mediaStartOffset * 0x200)));
System.out.println("SHA256 hash tbl size: " + ncaSectionBlock.getSuperBlockPFS0().getHashTableSize());
System.out.println("SHA256 hash tbl offs: " + ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset());
System.out.println("PFS0 Offs: " + ncaSectionBlock.getSuperBlockPFS0().getPfs0offset());
System.out.println("SHA256 records: " + (ncaSectionBlock.getSuperBlockPFS0().getHashTableSize() / 0x20));
System.out.println("KEY: " + LoperConverter.byteArrToHexString(decryptedKey));
System.out.println("CTR: " + LoperConverter.byteArrToHexString(ncaSectionBlock.getSectionCTR()));
System.out.println();
//--------------------------------------------------------------------------------------------------
private class ParseEncrypted implements Runnable{ if (decryptedKey == null)
throw new Exception("CryptoSection03: unable to proceed. No Key Area Encryption Keys provided.");
LinkedList<byte[]> SHA256hashes; RandomAccessFile raf = new RandomAccessFile(file, "r");
PipedInputStream pipedInputStream; raf.seek(offsetPosition + (mediaStartOffset * 0x200));
long hashTableOffset; AesCtrDecryptor decryptor = new AesCtrDecryptor(decryptedKey, ncaSectionBlock.getSectionCTR(), mediaStartOffset * 0x200);
long hashTableSize;
long hashTableRecordsCount;
long pfs0offset;
long pfs0size;
ParseEncrypted(LinkedList<byte[]> SHA256hashes, PipedInputStream pipedInputStream, long pfs0offset, long pfs0size, long hashTableOffset, long hashTableSize){ byte[] encryptedBlock;
this.SHA256hashes = SHA256hashes; byte[] dectyptedBlock;
this.pipedInputStream = pipedInputStream; long mediaBlockSize = mediaEndOffset - mediaStartOffset;
this.hashTableOffset = hashTableOffset; // Prepare thread to parse encrypted data
this.hashTableSize = hashTableSize; PipedOutputStream streamOut = new PipedOutputStream();
this.hashTableRecordsCount = hashTableSize / 0x20; PipedInputStream streamInp = new PipedInputStream(streamOut);
this.pfs0offset = pfs0offset;
this.pfs0size = pfs0size;
}
@Override new Thread(new ParseThread(
public void run() { streamInp,
long counter = 0; // How many bytes already read ncaSectionBlock.getSuperBlockPFS0().getPfs0offset(),
ncaSectionBlock.getSuperBlockPFS0().getPfs0size(),
try{ ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset(),
if (hashTableOffset > 0){ ncaSectionBlock.getSuperBlockPFS0().getHashTableSize()
while (counter < hashTableOffset) { )).start();
pipedInputStream.read(); // todo: .skip() // Decrypt data
counter++; for (int i = 0; i < mediaBlockSize; i++){
} encryptedBlock = new byte[0x200];
if (raf.read(encryptedBlock) != -1){
//dectyptedBlock = aesCtr.decrypt(encryptedBlock);
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
// Writing decrypted data to pipe
streamOut.write(dectyptedBlock);
} }
// Main loop }
while (true){ streamOut.flush();
// Loop for collecting all recrods from sha256 hash table streamOut.close();
while ((counter - hashTableOffset) < hashTableSize){
int hashCounter = 0; raf.close();
byte[] sectionHash = new byte[0x20]; }
// Loop for collecting bytes for every SINGLE records, where record size == 0x20 /*
while (hashCounter < 0x20){ * Simplify decryption of the CTR
* */
private class AesCtrDecryptor{
private long realMediaOffset;
byte[] IVarray;
private AesCtr aesCtr;
AesCtrDecryptor(byte[] decryptedKey, byte[] sectionCTR, long realMediaOffset) throws Exception{
this.realMediaOffset = realMediaOffset;
aesCtr = new AesCtr(decryptedKey);
// IV for CTR == 16 bytes
IVarray = new byte[0x10];
// Populate first 8 bytes taken from Header's section Block CTR
System.arraycopy(LoperConverter.flip(sectionCTR), 0x0, IVarray,0x0, 0x8);
}
public byte[] dectyptNext(byte[] enctyptedBlock) throws Exception{
updateIV(realMediaOffset);
byte[] decryptedBlock = aesCtr.decrypt(enctyptedBlock, IVarray);
realMediaOffset += 0x200;
return decryptedBlock;
}
// Populate last 8 bytes calculated. Thanks hactool project!
private void updateIV(long offset){
offset >>= 4;
for (int i = 0; i < 0x8; i++){
IVarray[0x10-i-1] = (byte)(offset & 0xff); // Note: issues could be here
offset >>= 8;
}
}
}
/*
* Since we representing decrypted data as stream (it's easier to look on it this way),
* this thread will be parsing it.
* */
private class ParseThread implements Runnable{
PipedInputStream pipedInputStream;
long hashTableOffset;
long hashTableSize;
long hashTableRecordsCount;
long pfs0offset;
long pfs0size;
ParseThread(PipedInputStream pipedInputStream, long pfs0offset, long pfs0size, long hashTableOffset, long hashTableSize){
this.pipedInputStream = pipedInputStream;
this.hashTableOffset = hashTableOffset;
this.hashTableSize = hashTableSize;
this.hashTableRecordsCount = hashTableSize / 0x20;
this.pfs0offset = pfs0offset;
this.pfs0size = pfs0size;
}
@Override
public void run() {
long counter = 0; // How many bytes already read
try{
if (hashTableOffset > 0){
if (hashTableOffset != pipedInputStream.skip(hashTableOffset))
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){
if ((pfs0offset-counter) != pipedInputStream.skip(pfs0offset-counter))
return; // TODO: fix?
counter += pfs0offset-counter;
}
//---------------------------------------------------------
byte[] magic = new byte[0x4];
for (int i = 0; i < 4; i++){
int currentByte = pipedInputStream.read(); int currentByte = pipedInputStream.read();
if (currentByte == -1) if (currentByte == -1)
break; break;
sectionHash[hashCounter] = (byte)currentByte; magic[i] = (byte)currentByte;
hashCounter++;
counter++;
} }
// Write after collecting RainbowHexDump.hexDumpUTF8(magic);
SHA256hashes.add(sectionHash); while (pipedInputStream.read() != -1)
;
break;
} }
// Skip padding and go to PFS0 location
if (counter < pfs0offset){
while (counter < pfs0offset){
pipedInputStream.read();
counter++;
}
}
//---------------------------------------------------------
byte[] magic = new byte[0x4];
for (int i = 0; i < 4; i++){
int currentByte = pipedInputStream.read();
if (currentByte == -1)
break;
magic[i] = (byte)currentByte;
}
RainbowHexDump.hexDumpUTF8(magic);
break;
} }
} catch (IOException ioe){
catch (IOException ioe){ System.out.println("'ParseThread' thread exception");
System.out.println("'ParseEncrypted' thread exception"); ioe.printStackTrace();
ioe.printStackTrace(); }
} finally {
finally { System.out.println("ParseThread thread died.");
System.out.println("Thread died."); }
} }
} }
} }

View file

@ -17,18 +17,19 @@ public class AesCtr {
} }
private Cipher cipher; private Cipher cipher;
private SecretKeySpec key;
public AesCtr(byte[] keyArray, byte[] IVarray) throws Exception{ public AesCtr(byte[] keyArray) throws Exception{
if ( ! BCinitialized) if ( ! BCinitialized)
initBCProvider(); initBCProvider();
IvParameterSpec iv = new IvParameterSpec(IVarray); key = new SecretKeySpec(keyArray, "AES");
SecretKeySpec key = new SecretKeySpec(keyArray, "AES");
cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC"); cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");
cipher.init(Cipher.DECRYPT_MODE, key, iv);
} }
public byte[] decrypt(byte[] encryptedData) throws Exception{ public byte[] decrypt(byte[] encryptedData, byte[] IVarray) throws Exception{
IvParameterSpec iv = new IvParameterSpec(IVarray);
cipher.init(Cipher.DECRYPT_MODE, key, iv);
return cipher.doFinal(encryptedData); return cipher.doFinal(encryptedData);
} }
} }