First of all, for clarification, this thread is just for fun and sharing ideas, so don't except anything practical to came out of this, because nothing will probably do.
This is about the case when you don't have the key of the filesystem.
This thread about a theoretical cryptographic issue with the WFS that I thought about today. It is just theoretical, so I do many assumptions that are going to be an issue to do in real scenario. It has some potential but it is problematic because the little to no control that you have on the data that is stored in the file system. I will try to explain it as clear as I can.
TL;DR: If you can control content of files on the filesystem, you can create your own controlled file system without knowing the key.
As a note, there is NOTHING that Nintendo can do to fix it (not that they care anymore, but even if they wanted - they can't change the file system)
So first of all, I want to explain how the WFS is encrypted and verified.
There are two type of blocks, what I call a metadata block, and a data block. Data blocks are blocks that are dedicated to data of files. Metadata block store any other information that the file system uses.
There are multiple possible sizes of blocks, but usually a metadata block is 0x2000 bytes, and a data block is 0x2000 or 0x10000 bytes.
Every block is encrypted with AES-CBC, all the blocks with the same key, but a unique IV for each block. The IV is 16 bytes, 4 dwords:
IV[0] - used block size. In metadata block it is always 0x2000, in data block it can be less than the real block size if it isn't full. This size is rounded up to the sector size of the disk. (usually 0x200)
IV[1] - the most complex dword of the IV. I will return to this later.
IV[2] - number of sectors in the disk - it is known
IV[3] - sector size of the disk (usually 0x200)
As you can see, we can easily know IV[0], IV{2], and IV[3].
Just as a remainder, this is how CBC mode works:
Encryption:
decryption:
So lets say C[0...n] is 0x10 bytes cipher blocks, and P[0..n] is 0x10 bytes plaintext blocks.
encryption:
C[0] = AES_ENC(IV ^ P[0])
C[1] = AES_ENC(C[0] ^ P[1])
...
C[n] = AES_ENC(C[n-1] ^ P[n])
decryption:
P[0] = IV ^ AES_DEC(C[0])
P[1] = C[0] ^ AES_DEC(C[1])
..
P[n] = C[n-1] ^ AES_DEC(C[n])
Ok lets talk about another concept in WFS. There are things that are called Areas/Quotas. Quota is just a directory with size limit, and it is pre-allocated. There is the root/main quota, which is just the whole disk, and starts at the first block. Each block is part of a quota. In each quota header there is a random number, lets call it the quota id. There is also a random id in the wfs header for the whole wfs, lets call it wfs id.
So lets go back to IV[1].
IV[1] = (wfs_id ^ quota_id) + sector number of block (disk sector, relative to the start of the quota)
quite ugly, ah?
IV[1] of the first block is 0, because those ids are still not known.
Now lets talk about the verification. Each block has a hash, SHA1. The hash of metadata blocks are stored inside them, at offset 0x4-0x18. When the hash is calculated offset 0x4-0x18 are just replaced with 0xFF's.
The hash of the data blocks are in the metadata blocks that point to them, so it is pretty much impossible to temper with them, because if you change anything you need to change the metadata block also, which is also verified.
If you change any byte in the metadata block some bytes are going to change and the hash is going to fail.
Now, for the vulnerability, lets assume that all we can do is control content of files, and we know where the encrypted data is in the encrypted disk.
So if everything is hashed, encrypted and verified, where is the culprit?
The concept is simple, we are going to create a fake metadata block, and use file content to encrypt it. the hash is inside the metadata block
Lets have another assumption, just for a moment. Lets say we know the IV of blocks X, Y. X is a metadata block that we want to change, and Y is a data block with data with our control.
Lets call T[0..n] the target plaintext that we want the block X will have. We are going to build T as a valid metadata block, we can calculate hash of our T so T[4:0x18] will have the hash of T (it is just SHA1, not HMAC or anything)
And lets mark P_Y as the plaintext of our block Y, and C_Y as its cipher. The same thing about metadata block X: P_X, C_X.
I am going to do the equations step by step, without skipping anything, so it looks complex but it isn't.
so lets do:
P_Y[0] = T[0] ^ IV_X ^ IV_Y
P_Y[n>=1] = T[n].
Now lets check it, lets take a look on the cipher that we will get:
C_Y[0] = AES_ENC(IV_Y ^ P_Y[0]) = AES_ENC(IV_Y ^ T[0] ^ IV_X ^ IV_Y) = AES_ENC(T[0] ^ IV_X)
C_Y[n>=1] = AES_ENC(C_Y[n-1] ^ P_Y[n]) = AES_ENC(C_Y[n-1] ^ T[n])
Now, I claim that C_Y is a valid encrypted X metadata block, and we can replace the encrypted X metadata block with it, lets see what P_X will be:
C_X = C_Y
P_X[0] = IV_X ^ AES_DEC(C_X[0]) = IV_X ^ AES_DEC(C_Y[0]) = IV_X ^ T[0] ^ IV_X = T[0]
P_X[n>=1] = C_X[n-1] ^ AES_DEC(C_X[n]) = C_Y[n-1] ^ AES_DEC(C_Y[n]) = C_Y[n-1] ^ C_Y[n-1] ^ T[n] = T[n]
Success!
But wait, the IV contains a random number, how are we going to know it? So here is one idea:
Lets say that we totally want to control the file system. So lets choose block X as block 0, the first block. We know IV_0 ! (see the explanation above on how the IV is built)
And about block Y. We don't know IV_Y, but we do know C_Y[0], and C_Y[0] acts as the IV of C_Y[1], so we are going to do the same thing, just start from C_Y[1] instead C_Y[0], and use 0x10000 data block. So lets say that block Y is a 0x10000 bytes block, and we can change its content.
So lets first do P_Y[0...n] = 0, 0, 0, 0, 0, 0...
C_Y[0] = AES_ENC(0 ^ IV_Y)
Now we know C_Y[0] (because we look on the encrypted hard disk and we know where Y is, this was our assumption...)
Now lets change the content of the file, but this time lets do:
P_Y[0] = 0
P_Y[1] = T[0] ^ C_Y[0] ^ IV_0
P_Y[n>=2] = T[n-1]
Now lets check it:
C_Y[0] = AES_ENC(0 ^ IV_Y)
C_Y[1] = AES_ENC(C_Y[0] ^ P_Y[1]) = AES_ENC(C_Y[0] ^ T[0] ^ C_Y[0] ^ IV_0) = AES_ENC(T[0] ^ IV_0)
C_Y[n>=1] = AES_ENC(C_Y[n-1] ^ P_Y[n]) = AES_ENC(C_Y[n-1] ^ T[n-1])
Now lets do:
C_0[n] = C_Y[n + 1]
So lets check what P_0 is going to be
P_0[0] = IV_0 ^ AES_DEC(C_0[0]) = IV_0 ^ AES_DEC(C_Y[1]) = IV_0 ^ T[0] ^ IV_0 = T[0]
P_Y[n>=1] = C_0[n-1] ^ AES_DEC(C_0[n]) = C_Y[n] ^ AES_DEC(C_Y[n+1]) = C_Y[n] ^ C_Y[n] ^ T[n] = T[n]
Success again.
Now that we control block 0, we can repeat it to any other block (because now we will know its IV, because we control the wfs and quota id), and we can fake a whole file system that will be valid.
Ok, so now the issues. It is really hard to control the content of files. @wiiupoo suggested to do it with system transfer from hacked system (and because of him I thought about all of this). Another option is to find an app that download files to the disk from the internet over http or something like that.
Changing the content of the file (like the second method requires) is even harder (it isn't possible with system transfer for example), but maybe overwritng the file, deleting/recreating it somehow will do the trick. You just need to have a block of your data in the same place as part as the same quota.
Finding file in the encrypted disk is easier. The simplest thing to do is to compare the disk before and after creating the file. Another option is play with the IV. The only thing that change in an IV between two blocks in the same quota in the same size is the second dword. But between two adjusted block, there may be only one bit different in the IV (think about it). So you can do two blocks of plaintext with the same first block, except that bit, and you should get two identical first blocks encrypted, and search them later in the disk.
There are probably more possible variation of the same thing that you can do that may solve part of those issues.
What you can do with fake filesystem? Well I don't think that it is possible to do the regular contanthax, because even if you buy a game, you won't have the ticket of it, but lets just hint that there are some other good options
If I did any mistake/missed something please correct me.
This is about the case when you don't have the key of the filesystem.
This thread about a theoretical cryptographic issue with the WFS that I thought about today. It is just theoretical, so I do many assumptions that are going to be an issue to do in real scenario. It has some potential but it is problematic because the little to no control that you have on the data that is stored in the file system. I will try to explain it as clear as I can.
TL;DR: If you can control content of files on the filesystem, you can create your own controlled file system without knowing the key.
As a note, there is NOTHING that Nintendo can do to fix it (not that they care anymore, but even if they wanted - they can't change the file system)
So first of all, I want to explain how the WFS is encrypted and verified.
There are two type of blocks, what I call a metadata block, and a data block. Data blocks are blocks that are dedicated to data of files. Metadata block store any other information that the file system uses.
There are multiple possible sizes of blocks, but usually a metadata block is 0x2000 bytes, and a data block is 0x2000 or 0x10000 bytes.
Every block is encrypted with AES-CBC, all the blocks with the same key, but a unique IV for each block. The IV is 16 bytes, 4 dwords:
IV[0] - used block size. In metadata block it is always 0x2000, in data block it can be less than the real block size if it isn't full. This size is rounded up to the sector size of the disk. (usually 0x200)
IV[1] - the most complex dword of the IV. I will return to this later.
IV[2] - number of sectors in the disk - it is known
IV[3] - sector size of the disk (usually 0x200)
As you can see, we can easily know IV[0], IV{2], and IV[3].
Just as a remainder, this is how CBC mode works:
Encryption:
decryption:
So lets say C[0...n] is 0x10 bytes cipher blocks, and P[0..n] is 0x10 bytes plaintext blocks.
encryption:
C[0] = AES_ENC(IV ^ P[0])
C[1] = AES_ENC(C[0] ^ P[1])
...
C[n] = AES_ENC(C[n-1] ^ P[n])
decryption:
P[0] = IV ^ AES_DEC(C[0])
P[1] = C[0] ^ AES_DEC(C[1])
..
P[n] = C[n-1] ^ AES_DEC(C[n])
Ok lets talk about another concept in WFS. There are things that are called Areas/Quotas. Quota is just a directory with size limit, and it is pre-allocated. There is the root/main quota, which is just the whole disk, and starts at the first block. Each block is part of a quota. In each quota header there is a random number, lets call it the quota id. There is also a random id in the wfs header for the whole wfs, lets call it wfs id.
So lets go back to IV[1].
IV[1] = (wfs_id ^ quota_id) + sector number of block (disk sector, relative to the start of the quota)
quite ugly, ah?
IV[1] of the first block is 0, because those ids are still not known.
Now lets talk about the verification. Each block has a hash, SHA1. The hash of metadata blocks are stored inside them, at offset 0x4-0x18. When the hash is calculated offset 0x4-0x18 are just replaced with 0xFF's.
The hash of the data blocks are in the metadata blocks that point to them, so it is pretty much impossible to temper with them, because if you change anything you need to change the metadata block also, which is also verified.
If you change any byte in the metadata block some bytes are going to change and the hash is going to fail.
Now, for the vulnerability, lets assume that all we can do is control content of files, and we know where the encrypted data is in the encrypted disk.
So if everything is hashed, encrypted and verified, where is the culprit?
The concept is simple, we are going to create a fake metadata block, and use file content to encrypt it. the hash is inside the metadata block
Lets have another assumption, just for a moment. Lets say we know the IV of blocks X, Y. X is a metadata block that we want to change, and Y is a data block with data with our control.
Lets call T[0..n] the target plaintext that we want the block X will have. We are going to build T as a valid metadata block, we can calculate hash of our T so T[4:0x18] will have the hash of T (it is just SHA1, not HMAC or anything)
And lets mark P_Y as the plaintext of our block Y, and C_Y as its cipher. The same thing about metadata block X: P_X, C_X.
I am going to do the equations step by step, without skipping anything, so it looks complex but it isn't.
so lets do:
P_Y[0] = T[0] ^ IV_X ^ IV_Y
P_Y[n>=1] = T[n].
Now lets check it, lets take a look on the cipher that we will get:
C_Y[0] = AES_ENC(IV_Y ^ P_Y[0]) = AES_ENC(IV_Y ^ T[0] ^ IV_X ^ IV_Y) = AES_ENC(T[0] ^ IV_X)
C_Y[n>=1] = AES_ENC(C_Y[n-1] ^ P_Y[n]) = AES_ENC(C_Y[n-1] ^ T[n])
Now, I claim that C_Y is a valid encrypted X metadata block, and we can replace the encrypted X metadata block with it, lets see what P_X will be:
C_X = C_Y
P_X[0] = IV_X ^ AES_DEC(C_X[0]) = IV_X ^ AES_DEC(C_Y[0]) = IV_X ^ T[0] ^ IV_X = T[0]
P_X[n>=1] = C_X[n-1] ^ AES_DEC(C_X[n]) = C_Y[n-1] ^ AES_DEC(C_Y[n]) = C_Y[n-1] ^ C_Y[n-1] ^ T[n] = T[n]
Success!
But wait, the IV contains a random number, how are we going to know it? So here is one idea:
Lets say that we totally want to control the file system. So lets choose block X as block 0, the first block. We know IV_0 ! (see the explanation above on how the IV is built)
And about block Y. We don't know IV_Y, but we do know C_Y[0], and C_Y[0] acts as the IV of C_Y[1], so we are going to do the same thing, just start from C_Y[1] instead C_Y[0], and use 0x10000 data block. So lets say that block Y is a 0x10000 bytes block, and we can change its content.
So lets first do P_Y[0...n] = 0, 0, 0, 0, 0, 0...
C_Y[0] = AES_ENC(0 ^ IV_Y)
Now we know C_Y[0] (because we look on the encrypted hard disk and we know where Y is, this was our assumption...)
Now lets change the content of the file, but this time lets do:
P_Y[0] = 0
P_Y[1] = T[0] ^ C_Y[0] ^ IV_0
P_Y[n>=2] = T[n-1]
Now lets check it:
C_Y[0] = AES_ENC(0 ^ IV_Y)
C_Y[1] = AES_ENC(C_Y[0] ^ P_Y[1]) = AES_ENC(C_Y[0] ^ T[0] ^ C_Y[0] ^ IV_0) = AES_ENC(T[0] ^ IV_0)
C_Y[n>=1] = AES_ENC(C_Y[n-1] ^ P_Y[n]) = AES_ENC(C_Y[n-1] ^ T[n-1])
Now lets do:
C_0[n] = C_Y[n + 1]
So lets check what P_0 is going to be
P_0[0] = IV_0 ^ AES_DEC(C_0[0]) = IV_0 ^ AES_DEC(C_Y[1]) = IV_0 ^ T[0] ^ IV_0 = T[0]
P_Y[n>=1] = C_0[n-1] ^ AES_DEC(C_0[n]) = C_Y[n] ^ AES_DEC(C_Y[n+1]) = C_Y[n] ^ C_Y[n] ^ T[n] = T[n]
Success again.
Now that we control block 0, we can repeat it to any other block (because now we will know its IV, because we control the wfs and quota id), and we can fake a whole file system that will be valid.
Ok, so now the issues. It is really hard to control the content of files. @wiiupoo suggested to do it with system transfer from hacked system (and because of him I thought about all of this). Another option is to find an app that download files to the disk from the internet over http or something like that.
Changing the content of the file (like the second method requires) is even harder (it isn't possible with system transfer for example), but maybe overwritng the file, deleting/recreating it somehow will do the trick. You just need to have a block of your data in the same place as part as the same quota.
Finding file in the encrypted disk is easier. The simplest thing to do is to compare the disk before and after creating the file. Another option is play with the IV. The only thing that change in an IV between two blocks in the same quota in the same size is the second dword. But between two adjusted block, there may be only one bit different in the IV (think about it). So you can do two blocks of plaintext with the same first block, except that bit, and you should get two identical first blocks encrypted, and search them later in the disk.
There are probably more possible variation of the same thing that you can do that may solve part of those issues.
What you can do with fake filesystem? Well I don't think that it is possible to do the regular contanthax, because even if you buy a game, you won't have the ticket of it, but lets just hint that there are some other good options
If I did any mistake/missed something please correct me.
Last edited by EyeKey,