QCRACK SKU Encryption
not much better than ENCRYPT.EXE
2017-03-31
related posts:
- the original QCRACK post where I try to learn about the original DOS executable
- QCracker - a JavaScript implementation of QCRACK on a webpage
- Continuing QCRACK
- flowlib in QCRACK
- The Super Advanced QCRACK ENCRYPT.EXE
- Finished with QCRACK - my last post on it
My next step in reversing QCRACK was decrypting the SKU file. I had previously got an unencrypted plain-text SKU file from flowlib.lib, so had that to compare against.
It searches for a file named “sku.[number]”. It takes the file with the largest number extension, presumably to account for later versions of the sku file. QCRACK stores the sku file name at 0xc69c.
It decrypts sku in 1024 byte chunks. It XORes the nth byte with the previous (n-1)th byte and the (lowest byte of the) nth Fibonnacci number. This starts with the second byte since the first doesn’t have a previous byte.
The first byte is encrypted with this:
0000:173c 55 push bp
0000:173d 89e5 mov bp, sp
0000:173f 53 push bx
0000:1740 8b5d08 mov bx, word [di + 8] ; [0x8:2]=0xffff ; 8
0000:1743 8b4d0c mov cx, word [di + 0xc] ; [0xc:2]=0xffff ; 12
0000:1746 31d2 xor dx, dx
0000:1748 85c9 test cx, cx
,=< 0000:174a 740f je 0x175b
| 0000:174c 01d2 add dx, dx
| 0000:174e 89d8 mov ax, bx
| 0000:1750 83e001 and eax, 1
! 0000:1753 09c2 or dx, ax
! 0000:1755 c1eb01 shr bx, 1
! 0000:1758 49 dec cx
`=< 0000:1759 75f1 jne 0x174c
0000:175b 89d0 mov ax, dx
0000:175d 8b5dfc mov bx, word [di - 4]
0000:1760 c9 leave
0000:1761 c3 ret
It is a bit reversal function. You pass in the value to reverse, and how many bits to reverse (we always do just 8 bits.) The basic idea behind the reversal is take the lowest bit, make it the lowest bit of the reversed value, shift the original value to the right to get the next bit while shifting the reversed value to the left
original bits
-> -> -> -> ->
|
\|/
<- <- <- <- <-
reversed bits
So the steps are zero out the reversed value you’ll return.
While your bit count hasn’t counted down to zero:
- shift the reversed value to the left to make room for the next bit (multiplying by 2 is the same as shift left by one, and adding value to itself same as mult by 2)
- get the lowest bit of your value to reverse by ANDing it with a mask of 0x1
- OR that lowest bit with the shifted reversed value to set it to the lowest reversed bit
- decrement the bit count, loop again if not counted down to zero
Here’s my reverse bits function
uint32_t reverse_bits(uint32_t val, uint32_t num_bits) {
uint32_t reversed = 0;
while(num_bits) {
reversed = (val & 1) | (reversed << 1);
val >>= 1;
--num_bits;
}
return reversed;
}
Here’s the Fibonacci and decrypt functions:
#define SKU_BUFF_SIZE 1024
//zero index base fibonacci
int fib(int n) {
int a=1, b=1, temp, i;
for(i = 2; i<=n; ++i) {
temp = b;
b = temp + a;
a = temp;
}
return b;
}
void decrypt_sku(void) {
FILE* sku17;
FILE* skutestout;
char sku_buffer[SKU_BUFF_SIZE];
int num_bytes_read;
if((sku17 = fopen("sku.17", "r")) == NULL) {
printf("couldn't open input file sku.17\n");
return;
}
if((skutestout = fopen("sku.testout", "w")) == NULL) {
printf("couldn't open output file sku.testout\n");
return;
}
while(num_bytes_read = fread(sku_buffer, 1, SKU_BUFF_SIZE, sku17)) {
int current_byte;
for(current_byte = 1; current_byte < num_bytes_read; ++current_byte) {
sku_buffer[current_byte] =
sku_buffer[current_byte-1] ^ sku_buffer[current_byte] ^ (fib(current_byte) & 0xFF);
}
sku_buffer[0] = reverse_bits(sku_buffer[0], 8);
fwrite(sku_buffer, num_bytes_read, 1, skutestout);
}
fclose(sku17);
fclose(skutestout);
return;
}
The ENCRYPT.EXE could be used to encrypt and decrypt since it only used XOR on individual bytes. However, the SKU encryption is dependent on the order of operations now. So we have the decryption. For the original encryption, it means just the reverse of decyption. So instead of XORing from 2nd to 1024th byte then reverse first byte, to encypt we first reverse the first byte, then XOR the 1024th byte down to 2nd.
void encrypt_sku(void) {
FILE* sku17;
FILE* skutestout;
char sku_buffer[SKU_BUFF_SIZE];
int num_bytes_read;
if((sku17 = fopen("sku.testout", "r")) == NULL) {
printf("couldn't open input file sku.17\n");
return;
}
if((skutestout = fopen("sku.test17out", "w")) == NULL) {
printf("couldn't open output file sku.testout\n");
return;
}
while(num_bytes_read = fread(sku_buffer, 1, SKU_BUFF_SIZE, sku17)) {
int current_byte;
sku_buffer[0] = reverse_bits(sku_buffer[0], 8);
for(current_byte = num_bytes_read - 1; current_byte > 0; --current_byte) {
sku_buffer[current_byte] =
sku_buffer[current_byte-1] ^ sku_buffer[current_byte] ^ (fib(current_byte) & 0xFF);
}
fwrite(sku_buffer, num_bytes_read, 1, skutestout);
}
fclose(sku17);
fclose(skutestout);
return;
}
I feel I am very close to reversing how QCRACK generates the serial number to unlock the games. Since it mentions the DOC file, I’m assuming each game’s DOC file is used in the serial number generation. My next steps are see how QCRACK uses the DOC file name, and see where it gets the string for displaying “Serial Number: %s” and work backwards from there.