The token for this program is 57 characters long, and is encrypted using a 200KB lookup table. The cipher text is embedded in the application as 57 dword indices. Anti-debugging is implemented as a simple check against IsDebuggerAttached, which chooses between 57 good or 57 bad indices.
When the app starts the token is decrypted and then hashed one character at a time. Rather than reversing or inspecting the hashing, I was interested in the operation of the lookup table.
The code that indexes the table is run by the application in a separate thread. The thread proc begins at 0x4037A0, however it includes some invalid opcodes to confuse the disassembler. That invalid code is patched up by filling with NOP prior to executing it (see: 0x403998)
The table lookup function requires 3 index operations to resolve the encoded key character. Each dword index is modulo 0xc800 to ensure it falls within the 200KB table. The final index operation includes an offset based on the parity of the index.
The C program that corresponds to the table lookup is:
unsigned int lookup(unsigned char *cipher_table, unsigned int cipher_index)
{
unsigned int parity_check = 0, offset = 0;
cipher_index = *((unsigned int *)(cipher_table + (4 * (cipher_index % 0xc800))));
cipher_index = *((unsigned int *)(cipher_table + (4 * (cipher_index % 0xc800))));
if (cipher_index != 0) {
parity_check = cipher_index;
do {
if (!parity(parity_check & 0xFF)) offset = ~offset;
parity_check = parity_check >> 8;
} while (parity_check > 0);
}
cipher_index = *((unsigned int *)(cipher_table + (4 * ((cipher_index % 0xc800) + (offset & 1)))));
return ((~cipher_index & 0xFE000000) | cipher_index & 0x01FFFFFF) ^ 0xFE000000;
}
The following is a complete key decryption routine in C. It requires the "1.exe" binary, because it reads the 200KB lookup table from it.
#include <stdio.h>
#include <stdlib.h>
const unsigned char xor_data[57] = {
0xD1, 0x18, 0x52, 0xF3, 0x4B, 0x29, 0xB5, 0xE6,
0x2E, 0x60, 0x10, 0x1B, 0x59, 0x3E, 0x4D, 0xC1,
0x27, 0xAB, 0xA4, 0x82, 0x7F, 0x5B, 0x07, 0xA4,
0xE3, 0x1C, 0xE3, 0x93, 0x74, 0xBB, 0x3A, 0x62,
0x39, 0xE0, 0xDE, 0xE3, 0x85, 0x7A, 0x05, 0x29,
0x4F, 0xF1, 0x70, 0x1C, 0x70, 0x5E, 0xB3, 0xBF,
0x0A, 0x74, 0x97, 0x49, 0xFB, 0xE4, 0xC8, 0xF6,
0x5F
};
const unsigned int cipher_index[57] = {
0xd12479b8, 0x02802dd4, 0x9390b528, 0x6d13be10,
0x38637098, 0xadb0df76, 0x492afd8d, 0x6cda8589,
0x3f327fb3, 0xb559ed2c, 0x0d379569, 0xee50590a,
0xeffc3faf, 0xb183ff7c, 0x4942fe7a, 0x3f9f0383,
0xe5e8796e, 0x4acdb7e3, 0x6f7778ac, 0x9cfbd58d,
0x5d58a9d4, 0xb53ee0e8, 0x4f0bc8f7, 0x6ddcf35a,
0x0b32d6f3, 0xec181a05, 0xebf6aab4, 0x727beb1b,
0xd19b1f42, 0x0cd03ae5, 0xc901c555, 0x9e6123ed,
0x4805bbd3, 0x0b4e3b5f, 0xe74991a5, 0x16e56a67,
0xcdbf9035, 0x0c3fb795, 0x4e46ed21, 0x3098a99c,
0x0be2219a, 0x89008aba, 0xd1e8cb77, 0x8d0a0f39,
0xbf93eaae, 0xdd1f5179, 0x27aa29da, 0x0687579f,
0xe4961b86, 0x00047b96, 0xdf66fc9d, 0xe4ff21b6,
0x056c231b, 0xb94a2217, 0x2e385b22, 0xd9315588,
0x975aec60
};
unsigned int lookup(unsigned char *cipher_table, unsigned int cipher_index);
int main()
{
// read lookup table from 1.exe
unsigned char *cipher_table = (unsigned char*)malloc(0x32000);
if (cipher_table == NULL) {
printf("Failed to allocate 200K lookup table\r\n");
return -1;
}
FILE *file = NULL;
if (fopen_s(&file, "1.exe", "r") != 0) {
printf("Failed to open 1.exe\r\n");
return -1;
}
fseek(file, 0x6430, 0);
fread(cipher_table, 4, 0xc800, file);
fclose(file);
// Look up 57 key characters via indexes
for (int i = 0; i < 57; i++) {
char key = (char)((lookup(cipher_table, cipher_index[i]) >> ((i & 3) * 8)) ^ (xor_data[i]));
printf("%c", key);
}
printf("\r\n");
free(cipher_table);
return 0;
}
// true = even; false=odd
bool parity(unsigned char check)
{
check = (check & 0x55) + ((check >> 1) & 0x55);
check = (check & 0x33) + ((check >> 2) & 0x33);
return (((check & 0x0F) + ((check >> 4) & 0x0F)) % 2);
}
unsigned int lookup(unsigned char *cipher_table, unsigned int cipher_index)
{
unsigned int parity_check = 0, offset = 0;
cipher_index = *((unsigned int *)(cipher_table + (4 * (cipher_index % 0xc800))));
cipher_index = *((unsigned int *)(cipher_table + (4 * (cipher_index % 0xc800))));
if (cipher_index != 0) {
parity_check = cipher_index;
do {
if (!parity(parity_check & 0xFF)) offset = ~offset;
parity_check = parity_check >> 8;
} while (parity_check > 0);
}
cipher_index = *((unsigned int *)(cipher_table + (4 * ((cipher_index % 0xc800) + (offset & 1)))));
return ((~cipher_index & 0xFE000000) | cipher_index & 0x01FFFFFF) ^ 0xFE000000;
}
The token is (spoiler):
<****TKN!34C38983E67F17EE146C25B8838E428D8B0AE58!TKN****>