Hacking Hacking with 3DS Save DeEncrypter

  • Thread starter Thread starter Immortal_no1
  • Start date Start date
  • Views Views 99,962
  • Replies Replies 243
  • Likes Likes 2
Status
Not open for further replies.
It's a complicated process, and one that i'll get around to looking at in a little while once i'm finished with the current checksum part of the app.
 
This is a little programm that work with the early 3ds games (on newer it will result in unsorted data).

It makes out of the raw decrypted save file the virtual save file

it may maps some sectors that are no more used to its old locations but well. All unused virtual locations are shown as 0xFF blocks.

use it this way 3dssaveresorter.exe

it will create a list than you have to press enter and the out file is created

http://www.mediafire.com/?02hhp317m2bipdh

hope this help
 
That is an interesting app, i will have to play about with it a little bit and see how useful it might be.

With the file correctly mapped it should be possible to extract the files from within, although any replacing of the files will have to reverse the process. Who wrote the application? is there source available?
 
Immortal_no1 said:
That is an interesting app, i will have to play about with it a little bit and see how useful it might be.

With the file correctly mapped it should be possible to extract the files from within, although any replacing of the files will have to reverse the process. Who wrote the application? is there source available?


I wrote it. The src sorry I have to clean it a bit befor I releas it. There are many commentars that are not good etc. and I have made it togetter with a app to get the offsets for the files and other blocks out of that file I clean it up a bit and releas it.

ADD:Sorry for may bad english

ADD2: the other app http://www.mediafire.com/?vufn0s5q2j9akfc src follow soon

@ADD2: virtual_3ds_save_file_analyser.exe in must be a virtual save

ADD3:But first I update the 3dssaveresorter to work with newer games

ADD4: it should result in something like this

Partition0:
hashtabeloffset 2000
SAVEoffset 3000
SAVEsize D000

Partition1:
hashtabeloffset 10000
SAVEoffset 11000
SAVEsize D000

SAVE0:
savestatoffs 3400
filetabeloffs 3600

rootname: files: 2
system.dat (0) start: 3800 size: 22
save00.bin (1) start: 3a00 size: 14dc

SAVE1:
savestatoffs 11400
filetabeloffs 11600

rootname: files: 2
system.dat (0) start: 11800 size: 22
save00.bin (1) start: 11a00 size: 14dc

so with a hex editor you can now easy extract the files (this is a Zelda save)

ADD5: src of the 3dssaveresorter

CODE static void Main(string[] args)
{

FileStream instream = new FileStream(args[0],FileMode.Open);
FileStream outstream = new FileStream(args[1], FileMode.Create);
UInt32 chucksize = 0x1000;//Convert.ToUInt32(args[3], 16);
UInt32 joffset = (UInt32)((instream.Length / chucksize) * 0xA);//Convert.ToUInt32(args[3], 16);

//we ignore all header_entrys since this is only for beta stuff
Byte[,] sectors = new Byte[0x10000,3]; // virt_sec , phys_sec , virt_realloc_cnt

Byte[,] usedsectors = new Byte[(UInt32)(instream.Length / chucksize), 3];

instream.Seek(joffset,SeekOrigin.Begin);
int size = 0;
for(int i = 0; instream.Position < 0xFFF;i++)
{
sectors[i, 0] = (Byte)instream.ReadByte();
instream.ReadByte();
sectors[i, 1] = (Byte)instream.ReadByte();
instream.Seek(2, SeekOrigin.Current);
sectors[i, 2] = (Byte)instream.ReadByte();
instream.Seek(0x1A, SeekOrigin.Current);
if (sectors[i, 1] != 0xFF) Console.WriteLine(sectors[i, 1].ToString() + " -> " + sectors[i, 0].ToString() + "(" + sectors[i, 2].ToString() + ")");
size++;
}
Console.ReadLine();

for (int i = 0; i < (UInt32)(instream.Length / chucksize); i++)
{
for (int i2 = 0; i2 < size; i2++)
{
if (sectors[i2, 1] != 0xFF) // no 0xFF
{
if (usedsectors[i, 2]
 
Good job on the App, nicely written source, is that in .NET?

Seems like you have a pretty good understanding of the save structure...

I have a nice collection, i'll post a .rar with the saves at some point soon, i'm actually going to have to play the games that i have now rather than leaving them in there nice pristine condition
tongue.gif
 
Immortal_no1 said:
Good job on the App, nicely written source, is that in .NET?

Seems like you have a pretty good understanding of the save structure...

I have a nice collection, i'll post a .rar with the saves at some point soon, i'm actually going to have to play the games that i have now rather than leaving them in there nice pristine condition
tongue.gif

looks like C#/.NET to me. Its awesome that both of you are making such progress!
 
ichichfly, by using your application, can you explain to me where the locations for the parts that differ are. so we want to find :

02 16 1C 0A 03 03
12 15 08 0F 03 03
00 14 0B 06 03 03
00 13 19 0B 03 03
00 0F 15 19 03 03

As at these points they have different values. If we know where the location of these parts are we may be able to find out how the values are generated.

48
C0

47
3A 47

Save3-CCCCCCCC-Analysis.pdf
 
Immortal_no1 said:
ichichfly, by using your application, can you explain to me where the locations for the parts that differ are. so we want to find :

02 16 1C 0A 03 03
12 15 08 0F 03 03
00 14 0B 06 03 03
00 13 19 0B 03 03
00 0F 15 19 03 03

As at these points they have different values. If we know where the location of these parts are we may be able to find out how the values are generated.

48
C0

47
3A 47

Save3-CCCCCCCC-Analysis.pdf



02 16 1C 0A 03 03 virtual: 0x2000 phys: 0x1C000
12 15 08 0F 03 03 virtual: 0x12000 phys: 0x8000
00 14 0B 06 03 03 virtual: 0x0 phys: 0xB000
 
Right, thanks for that, that helps. The locations 1C000 8000 and 15000 helps out, as the data at the address 8000 is where the scores are saved, the data at 1C000 I thinkl was the location of the crc and the location of 15000 I think is the save bloc for the filesystem... might be wrong on that part, I'm doing this from memory as I'm writing on my mobile.

Do you mind if I rewrite your app for integration into the Save De/Encrypter? Hopefully we may be able to get the extraction and importing of the save data working.

I've written an application that will run through the save file and it's searching for the checksums that we don't yet have. The search shoiuld finish tomorrow night so I'll update this post then.

Preliminary searches has found the DIFI / DISA hashes.
 
Immortal_no1 said:
Right, thanks for that, that helps. The locations 1C000 8000 and 15000 helps out, as the data at the address 8000 is where the scores are saved, the data at 1C000 I thinkl was the location of the crc and the location of 15000 I think is the save bloc for the filesystem... might be wrong on that part, I'm doing this from memory as I'm writing on my mobile.

Do you mind if I rewrite your app for integration into the Save De/Encrypter? Hopefully we may be able to get the extraction and importing of the save data working.

I've written an application that will run through the save file and it's searching for the checksums that we don't yet have. The search shoiuld finish tomorrow night so I'll update this post then.

Preliminary searches has found the DIFI / DISA hashes.

@Do you mind if I rewrite your app for integration into the Save De/Encrypter? Hopefully we may be able to get the extraction and importing of the save data working.

you are allowed to add it (you can also integrat the virtual save game analyser)

ADD: the src of the virtual save game analyser that gets the positions it is still not good but I fixed some things up now more games should work

CODEclass Program
{
static void Main(string[] args)
{
uint[] partoffset = new uint[0x10];
uint[] partsize = new uint[0x10];
uint[] parthashsize = new uint[0x10];
UInt16[] savestatoffs = new UInt16[0x10];
uint[] filetabeltoffs = new uint[0x10];
int i = 0;
FileStream instream = new FileStream(args[0],FileMode.Open);
instream.Seek(0x200, SeekOrigin.Begin);
while (true) //file system search
{
if (instream.ReadByte() == 0x44 && instream.ReadByte() == 0x49 && instream.ReadByte() == 0x46 && instream.ReadByte() == 0x49) //is DIFI
{
if (i == 0)
{
partoffset[0] = 0x2000;
instream.Seek(0x9C - 4, SeekOrigin.Current);
Byte[] buffer = new Byte[4];
instream.Read(buffer, 0, 4);
parthashsize = BitConverter.ToUInt32(buffer, 0);

instream.Seek(4, SeekOrigin.Current);

instream.Read(buffer, 0, 4);

partsize = BitConverter.ToUInt32(buffer, 0);

Console.WriteLine("Partition" + i.ToString() + ":");
Console.WriteLine("hashtabeloffset " + Convert.ToString(partoffset, 16).ToUpper());
Console.WriteLine("SAVEoffset " + Convert.ToString(partoffset + parthashsize, 16).ToUpper());
Console.WriteLine("SAVEsize " + Convert.ToString(partsize, 16).ToUpper());
Console.WriteLine("");
instream.Seek(0x88, SeekOrigin.Current);
}
else
{
partoffset = partoffset[i - 1] + parthashsize[i - 1] + partsize[i - 1];
if (partoffset % 0x1000 != 0) partoffset = (partoffset / 0x1000) * 0x1000 + 0x1000;
instream.Seek(0x9C - 4, SeekOrigin.Current);
Byte[] buffer = new Byte[4];
instream.Read(buffer, 0, 4);
parthashsize = BitConverter.ToUInt32(buffer, 0);

instream.Seek(4, SeekOrigin.Current);

instream.Read(buffer, 0, 4);

partsize = BitConverter.ToUInt32(buffer, 0);

Console.WriteLine("Partition" + i.ToString() + ":");
Console.WriteLine("hashtabeloffset " + Convert.ToString(partoffset, 16).ToUpper());
Console.WriteLine("SAVEoffset " + Convert.ToString(partoffset + parthashsize, 16).ToUpper());
Console.WriteLine("SAVEsize " + Convert.ToString(partsize, 16).ToUpper());
Console.WriteLine("");
}
}
else
{
break;
}
i++;
}
for (int i2 = 0; i != i2; i2++)
{
instream.Seek(partoffset[i2] + parthashsize[i2], SeekOrigin.Begin);
if (instream.ReadByte() == 0x53 && instream.ReadByte() == 0x41 && instream.ReadByte() == 0x56 && instream.ReadByte() == 0x45) //is SAVE
{
Console.WriteLine("SAVE" + i2.ToString() + ":");
instream.Seek(0x58 - 4, SeekOrigin.Current);
Byte[] buffer = new Byte[4];
Byte[] buffer1 = new Byte[2];
instream.Read(buffer1, 0, 2);
savestatoffs[i2] = BitConverter.ToUInt16(buffer1, 0);

instream.Seek(0x12, SeekOrigin.Current);
instream.Read(buffer, 0, 4);
filetabeltoffs[i2] = BitConverter.ToUInt32(buffer, 0) * 0x200;
Console.WriteLine("savestatoffs " + Convert.ToString(savestatoffs[i2] + partoffset[i2] + parthashsize[i2], 16).ToUpper());
Console.WriteLine("filetabeloffs " + Convert.ToString(savestatoffs[i2] + filetabeltoffs[i2] + partoffset[i2] + parthashsize[i2], 16).ToUpper());
Console.WriteLine("");
if ((savestatoffs[i2] + filetabeltoffs[i2]) == 0)
{
Console.WriteLine("calculated filetabel offset wrong");
}
else
{
instream.Seek(savestatoffs[i2] + filetabeltoffs[i2] + partoffset[i2] + parthashsize[i2], SeekOrigin.Begin);
instream.Read(buffer, 0, 4);
uint files = BitConverter.ToUInt32(buffer, 0) - 1;
Byte[] name = new byte[0x10];
instream.Read(name, 0, 0x10);
System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
Console.WriteLine("rootname:" + enc.GetString(name) + " files: " + files.ToString());
instream.Seek(0x20 - 4, SeekOrigin.Current);
for (int currfile = 0; currfile < files; currfile++)
{
instream.Read(buffer, 0, 4);
uint currentfiles = BitConverter.ToUInt32(buffer, 0) - 1;
instream.Read(name, 0, 0x10);
instream.Read(buffer, 0, 4);
uint index = BitConverter.ToUInt32(buffer, 0);
instream.Seek(4, SeekOrigin.Current);
instream.Read(buffer, 0, 4);
uint offset = BitConverter.ToUInt32(buffer, 0);
instream.Read(buffer, 0, 4);
uint curfilesize = BitConverter.ToUInt32(buffer, 0);
if (currentfiles == 0) Console.WriteLine(enc.GetString(name) + "(" + index.ToString() + ")" + " start: " + Convert.ToString(offset * 0x200 + savestatoffs[i2] + partoffset[i2] + parthashsize[i2], 16) + " size: " + Convert.ToString(curfilesize, 16));
else Console.WriteLine(" " + enc.GetString(name) + " (next " + currentfiles.ToString() + " files)");
instream.Seek(0xC, SeekOrigin.Current);
}
}
Console.WriteLine("");
}
else
{
Console.WriteLine("SAVE" + i2.ToString() + " is not there");
}
}
Console.ReadLine();
}
public static UInt16 Swap(UInt16 input)
{
return ((UInt16)(
((0xFF00 & input) >> 8) |
((0x00FF & input) > 24) |
((0x00FF0000 & input) >> 8) |
((0x0000FF00 & input)
 
I also just got Rayman 3D and Asphault 3D too, I'll upload these later.

That's quite a change to the application
 
Anyone have any ideas on how the checksums for the header data is created?

looking at the numbers at the bottom of the page in the file here


My checksum search application is going to take a long time to go through all the possible permutations
as i think there's only a few thousand trillion. I'm going to leave it running and see what we find.
 
Immortal_no1 said:
Anyone have any ideas on how the checksums for the header data is created?

looking at the numbers at the bottom of the page in the file here


My checksum search application is going to take a long time to go through all the possible permutations
as i think there's only a few thousand trillion. I'm going to leave it running and see what we find.

the checksum may be calculated over the encrypted data.
 
So far no luck on finding how those are generated.... i think it may be time for some trial and error
 
I wonder if the hex values are added together and then a modulus is used. I'll write a test app to see if that works. Otherwise I'm starting to run out of ideas...
 
Immortal_no1 said:
I wonder if the hex values are added together and then a modulus is used. I'll write a test app to see if that works. Otherwise I'm starting to run out of ideas...

I'm not sure if this'll help or not but I don't think we need to modify any blocks (ie 0xB000 -> 0xBFFF, 0x4000 -> 0x4FFF, or w/e) found in the current 3DS Save DeEncryptor because I tested it by copying just the blocks and checksums found with the experimental menu from one save to a different one (same game) and it still ended up corrupted.

Basically whatever needs to be done now to finish modifying the saves is simply not in the blocks found by the experimental menu (ie 0xB000 -> 0xBFFF, 0x4000 -> 0x4FFF, or w/e), or at least, that's the case for Legend of Zelda: Ocarina Of Time 3D. I think it has something to do with the data inbetween the blocks found in the experimental menu.
 
CollosalPokemon said:
Immortal_no1 said:
I wonder if the hex values are added together and then a modulus is used. I'll write a test app to see if that works. Otherwise I'm starting to run out of ideas...

I'm not sure if this'll help or not but I don't think we need to modify any blocks (ie 0xB000 -> 0xBFFF, 0x4000 -> 0x4FFF, or w/e) found in the current 3DS Save DeEncryptor because I tested it by copying just the blocks and checksums found with the experimental menu from one save to a different one (same game) and it still ended up corrupted.

Basically whatever needs to be done now to finish modifying the saves is simply not in the blocks found by the experimental menu (ie 0xB000 -> 0xBFFF, 0x4000 -> 0x4FFF, or w/e), or at least, that's the case for Legend of Zelda: Ocarina Of Time 3D. I think it has something to do with the data inbetween the blocks found in the experimental menu.

Did you change the header data too? if you compare the super monkeyball saves there are a few differences between them, including the header data there are also still 3 checksums that we don't know how their generated and one of those isn't an sha-256.

Now whether all those checksums need to be changed isn't known. Chances are they do however they may just be there to checksum the backup data and they're only used if your main game save gets messed up. In which case as soon you save the game after playing it, it will replace those checksums. I think the most important area at the moment is the header. As that would be the first thing to be checked.
 
Status
Not open for further replies.

Site & Scene News

Popular threads in this forum