CSCAMP CTF Quals 2014: PE 1 write-up

Created:2014.11.24, last modified: 2014.11.24 by xorpse
Status: Complete

The challenge executable presents us with an input prompt:

Using IDA Pro, and by examining the main function, we can quickly identify the routines responsible for the above output, and then begin to trace the input value to find out how it’s validated:

.text:00401B46                 call    __Z5debugv      ; debug(void)
.text:00401B4B                 call    __Z5hellov      ; hello(void)
.text:00401B50                 call    __Z5debugv      ; debug(void)
.text:00401B55                 lea     eax, [ebp+var_30]
.text:00401B58                 mov     [esp], eax
.text:00401B5B                 call    __Z5inputv      ; input(void)

The debug() function checks for the presence of a debugger and exits if one is found. hello() outputs the greeting banner and input() prompts the user for a secret value. The remainder of the main function validates the input value:

.text:00401BA6                 lea     eax, [ebp+var_2C]
.text:00401BA9                 mov     [esp+4], eax    ; std::string *
.text:00401BAD                 lea     eax, [ebp+var_25+1]
.text:00401BB0                 mov     [esp], eax      ; this
.text:00401BB3                 mov     [ebp+fc.call_site], 4
.text:00401BBA                 call    __Z4fakeSsSs    ; fake(std::string,std::string)

fake(), as its name suggests is merely a decoy function to hinder analysis.

.text:00401BF7                 lea     eax, [ebp+var_34]
.text:00401BFA                 mov     [esp], eax
.text:00401BFD                 call    __Z3keyv        ; key(void)
.text:00401C02                 lea     eax, [ebp+var_20]
.text:00401C05                 lea     edx, [ebp+var_34]
.text:00401C08                 mov     [esp], edx      ; this
.text:00401C0B                 mov     [ebp+fc.call_site], 6
.text:00401C12                 mov     ecx, eax
.text:00401C14                 call    __ZNSsC1ERKSs   ; std::string::string(std::string const&)
.text:00401C19                 sub     esp, 4
.text:00401C1C                 lea     eax, [ebp+var_1C]
.text:00401C1F                 lea     edx, [ebp+var_30]
.text:00401C22                 mov     [esp], edx      ; this
.text:00401C25                 mov     [ebp+fc.call_site], 7
.text:00401C2C                 mov     ecx, eax
.text:00401C2E                 call    __ZNSsC1ERKSs   ; std::string::string(std::string const&)
.text:00401C33                 sub     esp, 4
.text:00401C36                 lea     eax, [ebp+var_20]
.text:00401C39                 mov     [esp+4], eax
.text:00401C3D                 lea     eax, [ebp+var_1C]
.text:00401C40                 mov     [esp], eax
.text:00401C43                 mov     [ebp+fc.call_site], 8
.text:00401C4A                 call    __Z8checkingSsSs ; checking(std::string,std::string)

Assuming key() is some derivation function, we first see if there’s an easier route to solving the challenge rather than by reverse engineering the derivation routine (there is). The checking() function takes the original input and the output of key() and performs a comparison:

.text:00401A39                 call    __ZSteqIcEN9__gnu_cxx11__enable_ifIXsrSt9__is_charIT_E7_...
.text:00401A3E                 test    al, al
.text:00401A40                 jz      short loc_401A98

If the jump at 0x401a40 is taken, then the output of flag() is presented to us. Note: this function takes no input whatsoever.

.text:00401A42                 lea     eax, [ebp+var_1C]
.text:00401A45                 mov     [esp], eax
.text:00401A48                 call    __Z4flagv       ; flag(void)
.text:00401A4D                 mov     dword ptr [esp+4], offset aFlagIs ; "Flag is: "
.text:00401A55                 mov     dword ptr [esp], offset __ZSt4cout ; std::ostream::sentry *
.text:00401A5C                 mov     [ebp+fc.call_site], 1
.text:00401A63                 call    __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc

The disassembly reveals the flag to be a concatination of two (hard-coded) strings:

.text:004018C4                 mov     dword ptr [esp], offset aAiiaj ; "aiiaj"
.text:004018CB                 mov     [ebp+fc.call_site], 1
.text:004018D2                 mov     ecx, [ebp+arg_0]
.text:004018D5                 call    __ZNSsC1EPKcRKSaIcE
.text:004018DA                 sub     esp, 8
.text:004018DD                 lea     eax, [ebp+var_1A]
.text:004018E0                 mov     ecx, eax
.text:004018E2                 call    __ZNSaIcED1Ev   ; std::allocator<char>::~allocator()
.text:004018E7                 lea     eax, [ebp+var_1A+1]
.text:004018EA                 mov     ecx, eax
.text:004018EC                 call    __ZNSaIcEC1Ev   ; std::allocator<char>::allocator(void)
.text:004018F1                 lea     eax, [ebp+var_20]
.text:004018F4                 lea     edx, [ebp+var_1A+1]
.text:004018F7                 mov     [esp+4], edx    ; std::string *
.text:004018FB                 mov     dword ptr [esp], offset aCEik ; "c(eik"
.text:00401902                 mov     [ebp+fc.call_site], 2
.text:00401909                 mov     ecx, eax
.text:0040190B                 call    __ZNSsC1EPKcRKSaIcE
.text:00401910                 sub     esp, 8
.text:00401913                 lea     eax, [ebp+var_1A+1]
.text:00401916                 mov     ecx, eax
.text:00401918                 call    __ZNSaIcED1Ev   ; std::allocator<char>::~allocator()
.text:0040191D                 lea     eax, [ebp+var_20]
.text:00401920                 mov     [esp], eax      ; this
.text:00401923                 mov     [ebp+fc.call_site], 4
.text:0040192A                 mov     ecx, [ebp+arg_0]
.text:0040192D                 call    __ZNSs6appendERKSs ; std::string::append(std::string const&)

Thus, the flag is: ‘aiiajc(eik’. Reverse engineering key() tells us the required secret is ‘cairosecuritycamp’.