1

EDIT

As some people have pointed out, this exe file classifies as a 'virus' on some analysers. I can assure you this exe is from a very trusted source, and is actually part of a large puzzle (which contains more questions like this, related to RE, cryptography, etc)

You are more than welcome to check my stack exchange profile and see I don't really have the "this guy sends viruses to people and claims its a puzzle" typecast :-)


I've been debugging using OllyDbg this exe (original version, a simple puzzle) for a while now, yet I still can't find out what input it expects.

What it does

Once executed, it asks you to type in the decrypted token. The program will then tell you if the token is correct or not.

What I do know:

  1. The exe calculates the target hash and stores it in memory once you run it (this hash does not change from execution to execution)
  2. It calls IsDebuggerPresent once executed - and alters the above result if so. I patched this on my version of the exe.
  3. It waits for user input
  4. It calculates the hash of the users input (with a salt I think), and compares it to the hash it calculated on step 1

The target is to find out which input one should give to the exe on step 3

What I've tried

Basically, as the hash is a 32 byte string, I thought maybe this is plain MD4/MD5. What I did is to give the program a known plain text - "1111", and extract the hash it calculates. I then took the hash and tried to run a MD4/MD5 cracker on it, giving it a known candidate - "1111". I had no luck in doing that, which negated the MD4/MD5 thoery (probably?)

Next steps

As RE is new to me, the next logical step to do is to try and figure out which string the exe uses to calculate the target hash on step 1. I did find something in the memory while I debugged step 1 - a string with the value of

"Habh;)86!!'a/$r3})RX;8{>T"

(note the \t and \n), but when giving this input to the exe on step 3, the result is incorrect.

Thoughts would be appreciated!

  • DO NOT DOWNLOAD -- the linked EXE is detected as microsoft.com/security/portal/threat/Encyclopedia/… – Jason Geffner Feb 3 '16 at 14:02
  • Couldn't it be a false positive? I mean it would be common for a crackme. Shai, can you tell me the source of this binary? Where did you get it? Malwr was unable to analyze it. Virustotal and anubis results. – Dominik Antal Feb 3 '16 at 14:31
  • 2
    At the very least, @Shai's link should point to a password-protected ZIP file containing the EXE, with the disclaimer that the EXE may be malicious. Shai, if you want to make those changes then I'll remove my post below and un-flag your question. – Jason Geffner Feb 3 '16 at 15:03
  • @JasonGeffner The exe file is from a trusted source, really :-) this is part of a puzzle I received earlier. Nothing malicious. I see no point in zipping the whole thing? – Shai Feb 3 '16 at 16:45
  • 1
    After checking the binary, it seems to be really only a crackme, which performs suspicious behavior for AVs, such as creating executable code in the memory. – ebux Feb 4 '16 at 12:51
4

Since it is a crackme, I don't want to spoiler the solution, although I try to give some hint, which may help you.

The hash function may be MD5 using salt, modified IV or the hash function is used in a different way as you try. However, you neither have to reverse the hash function nor have to find out the exact algorithm. It is because, the crackme calculates the hashes of both the original and the user provided token. So, I think the easiest way if you try to catch the token at the start of the hash function, since at that point it should be in clear text.

The string you tried as a token is used somehow for the token decryption. This challenge is more trickier than you can find the solution token in the memory at any time during the execution.

  • Have you done that successfully? – Shai Feb 4 '16 at 14:20
  • Yes, I know the correct token. – ebux Feb 4 '16 at 14:28
  • Help me with this one, I have 7 more like these and I'm stuck on the first one :-) At least I'll know where to look next time! – Shai Feb 4 '16 at 14:31
  • 1
    I think the next one will be completely different, so you can learn more if you find the solution by yourself. – ebux Feb 4 '16 at 14:37
  • Even so, I don't even know if I'm breaking on the right function... – Shai Feb 4 '16 at 14:38
2

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****>

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.