Refactoring
Fixing CTR IV calc logic
This commit is contained in:
parent
18bdd0f3eb
commit
842be7048d
2 changed files with 167 additions and 140 deletions
|
@ -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.");
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue