hack.lu CTF 2011 FluxScience (450)

Category: reversing

Thanks to a former employee of FluxScience (one of our competitors), we managed to get hands on some important files which might help us revealing company secrets. Attached you will find the files. The employee who provided them got fired. You might be lucky and find his account still working: FLUX-38B273DD75860083-0B3DD6B02EC5B9B1-4AFFBAC2EB8B4D17

He might not have the necessary permission to decrypt the personal data data.flux. he stole them from his boss, _GLaDOS_.

Would you mind helping us by finding their companysecret? download

Summary: reversing, VM, crypto, obfuscation, brainstorming


After start program check the presence of debugger and continue execution correctly if debugger flag doesn’t set in PEB.

When careful analyses is finished we understand that this file not so easy.

Program looks like Virtual Machine where all call and jamps are replaced special gateway function:


But when we had spent a little time we understood how it works.

  • It saves registers

  • choose new point to execute
  • restore registry and make “ret” to new function

So we can just set breakpoint at 0x411346 and catch all calls and jumps.

Ok, go on =)

It calls cpuid 4 times with parameters 80000001h, 80000002h, 80000003h, 80000004h

and  generate cpuid key which consists of 4 dwords.

  • CPU_DWORD1 = 0x65746e49
  • CPU_DWORD2 = 0x2952286c
  • CPU_DWORD3 = UNKNOWN
  • CPU_DWORD4 = DWORD3 ^ 0x78756C66

I never know how many Intel cpuid codes could be, and firstly I got stuck with it, because I didn’t know CPU_DWORD3. But how you will know a little later, I found different way to solve the problem.

There are 4 decrypt operations which follow this, and after all, there are several tests of validity of decrypt data.
The decrypt function is TEA and it looks like:

void decrypt (uint32_t* v, uint32_t* k, int num_round)
{
    uint32_t v0 = v[0], v1 = v[1], sum=0x61C88646 * num_round, i;
    uint32_t delta=0x61C88646;
    for (i=0; i < num_round; i++)
    {
    v1 -= (((v0 << 4)^(v0 >> 5)) + v0) ^ (sum + k[(sum >> 11) & 3]);
    sum -= delta;
    v0 -= (((v1 << 4)^(v1 >> 5)) + v1) ^ (sum + k[sum & 3]);
    }
    v[0] = v0;
    v[1] = v1;
}

First round:
Input:
num_round = 32;
v[0] = 0x38B273DD; – unknown for _GLaDOS_;
v[1] = 0x 75860083; – unknown for _GLaDOS_;
k[0] = 0x4AFFBAC2; – unknown for _GLaDOS_;
k[1] = 0xEB8B4D17; – unknown for _GLaDOS_;
k[2] = CPU_DWORD3;  – unknown;
k[3] = CPU_DWORD4;  – unknown;

Output:
KEY = v[0]
KEY0 = v[1] – unknown for us.

Wooph =( Unknown only. This decrypt round use credentials and cpukey.
We don’t known them both.

Second round:
Input:
num_round = 33;
v[0] = KEY;
v[1] = KEY0;
k[0] = CPU_DWORD1;
k[1] = CPU_DWORD2;
k[2] = CPU_DWORD3; – unknown;
k[3] = CPU_DWORD4; – unknown;

Output:
KEY1 = v[0]
KEY2 = v[1]

Third round:
Input:
num_round = 34;
v[0] = 0x0B3DD6B0; – unknown for _GLaDOS_;
v[1] = 0x2EC5B9B1; – unknown for _GLaDOS_;
k[0] = CPU_DWORD1;
k[1] = CPU_DWORD2;
k[2] = CPU_DWORD3; – unknown;
k[3] = CPU_DWORD4; – unknown;

Output:
KEY3 = v[0]
KEY4 = v[1]

Fourth round:
Input:
num_round = 35;
v[0] = 0x4affbac2; – unknown for _GLaDOS_;
v[1] = 0xeb8b4d17; – unknown for _GLaDOS_;
k[0] = CPU_DWORD1;
k[1] = CPU_DWORD2;
k[2] = CPU_DWORD3; – unknown;
k[3] = CPU_DWORD4; – unknown;

Output:
KEY5 = v[0]
KEY6 = v[1]
Ok, after all, there are 6 unknown dwords, not good.
But go on analyze conditions (C++ like pseudo code):

unsigned int KEY_ARR = { KEY1, KEY2, KEY3, KEY4, KEY5, KEY6 };
bool validName = true;
if (KEY3 ==  0x614C475F)
{
   if (KEY4 ==  0x5F534F44)
   {
      if (hash(KEY5,4) == KEY6)
      {
         if  (hash(&KEY_ARR[0], 0x10) == CPU_DWORD3 ^ KEY5)
         {
             char username[9];
             *(DWORD*)&username[0] = KEY1
             *(DWORD*)&username[4] = KEY2
             username[8] = 0;

             for(int i = 0; i < 8; i++)
             {
                if (!( (username [i] >= 'a' && username [i] <= 'z') ||
                   (username [i] >= 'A' && username [i] <= 'Z')
                   || username [i] == '_'))
                {
                   validName = false;
                   break;
                }
             }
             if (validName)
             {
                int  len;
                char name[9];
                if  (GetUserNameA(name, &len) &&
                   len == 9 && hash (name, 9) == hash(username, 9))
                {
                  //Decrypt file!//Decrypt file!
                } 
             }
          }
      }
   }
}
unsigned int hash(char* input, int n)
{
    char * a = input;
    int c = 1, b = 2;
    int i = 0;
    int sum;

    while (i < n)
   {
      c = (unsigned)(c + a[i]) % 0xFFF1;
      b = (unsigned)(b + c) % 0xFFF1;
      i++;
    }
    sum = (b << 16) | c;
    return sum;
}

Hm, now we know, that not only KEY3 and KEY4 should be
KEY3 == 0x614C475F
KEY4 == 0x5F534F44

But also
KEY1 == 0x614C475F
KEY2 == 0x5F534F44

Because of we known username from task! User = “_GLaDOS_”

But KEY5 and KEY6 still unknown.

Ok, go on and check how file will be decrypt:

unsigned int v[2];
v[0] = 0x8903da58; // first dword from encrypted file
v[1] = 0xd4a6e5c4; // second dword from encrypted file
arr_key[0] = UNKNOWN; // unknown for us! =(
arr_key[1] = uhash((char*)&arr_key[0]);
arr_key[2] = 0x3e1f081b; // KEY3 ^ KEY4
arr_key[3] = 0x3e1f081b; // KEY3 ^ KEY4
decrypt((unsigned int*)v, (unsigned int*)arr_key, 32);

So after program check:

if (v[1] == 0xDEADBEEF && v[0] ==0xe3b0299)
{
    // ALL GOOD! 
}

GREAT! We can just bruteforce one unknown dword arr_key[0]
It should be possible!

for (unsigned int k = 0; k < 0xffffffff; k++)
{
   unsigned int v[2];
   v[0] = 0x8903da58; // first dword from encrypted file
   v[1] = 0xd4a6e5c4; // second dword from encrypted file
   arr_key[0] = k; // unknown for us! =(
   arr_key[1] = uhash((char*)&arr_key[0]);
   arr_key[2] = 0x3e1f081b; // KEY3 ^ KEY4
   arr_key[3] = 0x3e1f081b; // KEY3 ^ KEY4
   decrypt((unsigned int*)v, (unsigned int*)arr_key, 32);
   if (v[1] == 0xDEADBEEF && v[0] ==0xe3b0299)
   {
    // ALL GOOD! 
   }
}

But if we try, we couldn’t find anything =( Why? My logic is ideal!
So I tried milder conditions

if (v[1] == 0xDEADBEEF || v[0] ==0xe3b0299)
{
    // So So xD
}

And find interesting pair:
v[0] = 0x0e3b0299
v[1] = 0xdea0dbe0

Hm, it looks very similar to needed.
Try to decrypt with it. Bingo! It is archive with file company-secret.txt
But unfortunately it is damarged. We should repair it (PK signature, compress and decompress size) and it will be passworded archive =( WTF?
Just bruteforce all string from moonstone.panel.exe
Bingo! Password: FluxScience

company-secret.txt:
The solution is the MD5 hash of GLaDOS’ login.

That’s it that’s all!

Now we know KEY_ARR[0], 0x10 and can bruteforce first KEY5 and KEY6 by using

  • if (hash(KEY5,4) == KEY6)
  • if  (hash(&KEY_ARR[0], 0x10) == CPU_DWORD3^ KEY5)

And brute cpuid by

  • if (KEY3 == 0x614C475F)
  • if (KEY4 == 0x5F534F44)
for (unsigned int k = 0; k < 0xffffffff; k++)
{
    v[0] = 0x0B3DD6B0;
    v[1] = 0x2EC5B9B1;
    arr_key[0] = 0x65746e49;
    arr_key[1] = 0x2952286c;
    arr_key[2] = k ^ 'xulf';
    arr_key[3] = k;
    decrypt((unsigned long*)v, (unsigned long*)arr_key, 34);

    if (v[0] == 0x614c475f && v[1] == 0x5f534f44)
        printf ("k=0x%x\n", k);
}

So cpukeys:
CPU_DWORD1 = 0x65746e49;
CPU_DWORD2 = 0x2952286c;
CPU_DWORD3 = 0x6b043204 ^ ‘xulf’;
CPU_DWORD4 = 0x6b043204;

We known TEA encrypt function:

void encrypt (uint32_t* v, uint32_t* k, int num_round)
{
    uint32_t v0=v[0], v1=v[1], sum=0, i;
    uint32_t delta=0x61C88646;
    for (i=0; i < num_round; i++)
    {
    v0 += (((v1 << 4)^(v1 >> 5)) + v1) ^ (sum + k[(sum >> 11) & 3]);
    sum += delta;
    v1 += (((v0 << 4)^(v0 >> 5)) + v0) ^ (sum + k[sum & 3]);
    }
    v[0] = v0;
    v[1] = v1;
}

And after processing all condition we get the answer.

_GLaDOS_ credentials:

FLUX-04644DBE65073E9E-0B3DD6B02EC5B9B1-F01864F663B0B371

MD5 = B6C91946501F2B0E00CC0A0189DC7C2C

Unfortunately, I could not do the task before the end of the competition, as entangled with brute force (0xdea0dbe0 instead 0xdeadbeef) and at first it seemed that alghorithm unbrutable.
But better late than never, besides my solution quite different than author’s solution!

And also want to say “Thank you!” for author, task was very interesting!

4 comments

Skip to comment form

    • Horgh on September 25, 2011 at 17:37
    • Reply

    Thanks for the writeup, very interesting :)

  1. Thanks for your writeup! :) I didn’t think people would go the reverse way, but of course it’s perfectly valid, too. Sad to hear you didn’t make it anymore till CTF end.

    1. The best thing in that way – you don’t need a cpuid to solve it =)

    • sp4nky on November 27, 2013 at 02:54
    • Reply

    the author solution link dosent work :(
    http://www.mediafire.com/?9fnmgt12jted1f6

    can u update it

Leave a Reply

Your email address will not be published.