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
Thanks for the writeup, very interesting :)
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.
Author
The best thing in that way – you don’t need a cpuid to solve it =)
the author solution link dosent work :(
http://www.mediafire.com/?9fnmgt12jted1f6
can u update it