PDA

View Full Version : VMU encryption cracked


Barubary
04-19-2001, 08:43 AM
I cracked the VMU file encryption system, at least for downloadable quests. The save file encryption system should be nearly identical to this code.

To run this, get the VMS file of a quest. Normally, quests have 2 files, script data and text/language-specific data. The Easter quest has a 3rd which merely contains that advertising logo bitmap. Since that's not encrypted, don't try to decrypt it. It's the 99 file so it's easily recognized.

Using a hex editor, extract from offset 0x280 in the VMS to the end into a file named vmu.bin in the directory of the compiled EXE.

Run the EXE, and you'll see a new file, outdata.bin. This is the decrypted version.

Below is the source code. It should work on any x86-based 32 bit environment. I certainly hope it's portable, but I kind of doubt it.

-- Barubary

#include <stdio.h>
#include <string.h>

typedef unsigned long DWORD;
typedef unsigned char BYTE;

inline DWORD read_DWORD(BYTE *input)
{
return ((DWORD) input[0]) | (((DWORD) input[1]) << 8) |
(((DWORD) input[2]) << 16) | (((DWORD) input[3]) << 24);
}

// This greatly simplifies the structure of
struct compress_input
{
const BYTE *ptr;
BYTE currbyte;
int currbits;

compress_input(const BYTE *input)
{
ptr = input;
currbits = 0;
}

inline BYTE getbit(void)
{
BYTE retval;

if (!currbits)
{
currbyte = *ptr++;
currbits = 8;
}
currbits--;
retval = currbyte & 1;
currbyte >>= 1;
return retval;
}

inline BYTE getbyte(void)
{
return *ptr++;
}
};

// Forceful forward copy; this is required because forward copying is
// expected when overlapping occurs.
void memfwdcpy(BYTE *dest, BYTE *src, DWORD count)
{
for (; count; count--)
*dest++ = *src++;
}

// The original function was a nightmare, so I simply figured out the
// algorithm and rewrote it.
DWORD pso_decompress(const BYTE *input, BYTE *output)
{
compress_input x = input;
BYTE *oldoutput = output;
int offset;
DWORD temp, count;

for (;;)
{
if (x.getbit())
{
*output++ = x.getbyte();
}
else
{
if (x.getbit())
{
temp = x.getbyte();
offset = x.getbyte() << 8;
offset |= temp;
if (!offset)
return output - oldoutput;

count = offset & 7;
if (count)
count += 2;
else
count = x.getbyte() + 1;

offset >>= 3;

memfwdcpy(output, &output[(int) offset - 8192], count);
output += count;
}
else
{
count = x.getbit() << 1;
count |= x.getbit();
count += 2;
offset = x.getbyte();
memfwdcpy(output, &output[(int) offset - 256], count);
output += count;
}
}
}
}


void build_table_sub(DWORD *stack)
{
DWORD *r5, *r6, *r7, r14;

r7 = &stack[1];
r5 = &stack[32];

for (r14 = 1; r14 < 25; r14++)
r7[r14] -= r5[r14];

// The -0x5C is a bit weird, but whatever...
r6 = &stack[1];
stack = &stack[-23];

// r14 is used as the counter, not r7
for (r14 = 25; r14 < 56; r14++)
r6[r14] -= stack[r14];
}


// Decrypt/encrypt quest data
void pso_decrypt(DWORD *stack, DWORD *input, DWORD size)
{
// r11 is stack; r12 is input; r13 is size
DWORD r14;

// There is a bug in the original that isn't fixed: it writes to the
// last DWORD whether it uses it entirely or not.
size = (size + 3) / sizeof(DWORD);

for (r14 = 0; r14 < size; r14++)
{
// Prefix ++ is the simplest way to represent this
if (++(stack[0]) > 55)
{
build_table_sub(stack);
stack[0] = 1;
}

input[r14] ^= stack[stack[0] + 1];
}
}


void build_decrypt_table(DWORD *stack, DWORD unknown)
{
// r11 is stack and r12 is unknown
DWORD r0, r13, r14;

r13 = r14 = 1;
stack[57] = unknown;
stack[56] = unknown;

// for() loop
for (; r13 < 55; r13++)
{
r0 = (r13 * 21) % 55;
stack[r0 + 1] = r14;
unknown -= r14;
r14 = unknown;
unknown = stack[r0 + 1];
}

// Repeated 4 times
build_table_sub(stack);
build_table_sub(stack);
build_table_sub(stack);
build_table_sub(stack);

stack[0] = 55;
}


// Source disassembly not included because it's so obvious
DWORD *init_decrypt_table(DWORD *stack)
{
build_decrypt_table(stack, 0);
return stack;
}


// Decompresses/decrypts a VMU quest
DWORD decrypt_vmu_quest(BYTE *input, DWORD insize, BYTE *output, DWORD
outsize)
{
// Stack storage (not including saved registers)
DWORD stack[58];

// Registers
// r11 is input, r12 is size, r13 is output
DWORD r10, r14;

// Why is this not ever used by the decryptor???
stack[1] = 0;

// Checks if enough space was allocated (?)
r14 = read_DWORD(input);
if (r14 > outsize)
return 0;

// This is in a delay slot, but that doesn't affect it, since the branch
// not taken case doesn't need it.
r10 = read_DWORD(&input[4]);

// Set the table to its defaults
init_decrypt_table(stack);

// Build the rest of the decryption table
build_decrypt_table(stack, r10);

// Decrypt the file
pso_decrypt(stack, (DWORD *) &input[8], insize - 8);

// Decompress the file
pso_decompress(&input[8], output);

return r14;
}


int main(void)
{
FILE *input, *output;
BYTE *indata = new BYTE[0x20000];
BYTE *outdata = new BYTE[0x20000];
long length;

input = fopen("vmu.bin", "rb");
output = fopen("outdata.bin", "wb");

fseek(input, 0, SEEK_END);
length = ftell(input);
fseek(input, 0, SEEK_SET);

fread(indata, length, 1, input);

length = decrypt_vmu_quest(indata, length, outdata, 0x20000);

fwrite(outdata, length, 1, output);

return 0;
}

NewTsumetai
04-19-2001, 10:41 AM
Do you have a life?

Hermione
04-28-2001, 04:18 AM
Tsu: Does anyone that plays PSO have a life (any more)?

MegumiHwang
04-28-2001, 05:25 AM
hee hee hee hee hee hee hee, he's a crazy loon. why bother!? it's just a game... *sigh*, oh what people would do just to get a stoopid vmu quest (besides, none of them have been good at all, except for the easter quests, and the japanese contests)... oh well, what do i have to say?

PARTY TOMORROW ALL DAY! sorry, no pso for me for the rest of the week.

~megumi hwang

p.s. magusonline@yahoo.com all you hardcore pso fans can send your hate mail to that link.

FredC
04-28-2001, 09:11 AM
You guys are certainly not programmers, right ? That's certainly why you don't understand the challenge it can be to crack something...

Maybe you can't see the interest in studying the PSO encryption, but that's generally the kind of research that may lead to interesting features... especially against cheaters. Because if a honest guy can decrypt a VMU save, a hacker with bad intentions will be able to do so too, and maybe activate some dangerous cheats. If we know how the process works, we may be able to counter attack !

Dont forget : Knowledge is power !

So congrats to Barubary !!

Kakyoin
04-28-2001, 06:42 PM
Has any 1 tried to compile the code?
What compiler are you using?

Evic
04-30-2001, 01:10 PM
Actually I've been intersted in how to get files off of a VMU (liek http://phs.donut.dhs.org/ does with PSO pics), and I was considering making a Player backup website, and maybe even a fair trading website. Where it would be like Diablo 2 you would never get jipped cause you would see exactly what the other person selected and then when you hit trade it pulls it from yoru game and adds to his, and vice-versa for his stuff.

This could be done in PHP if I could figure out the Character files....