QCRACK SKU Encryption

not much better than ENCRYPT.EXE

Posted by eric on March 31, 2017

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.