Hack.lu's CTF
Bonjour les gens !
The last two days, we have seen Hack.lu's
CTF taking place online.
It was a lot of fun, their IRC channel was really fun, so was their challenges
:)
Last results? 106 over 413 applying teams. Well done HackGyver \o/
Now it's time for the write-up. More precisely, the one on RoboAuth, their 150 points reverse challenge.
RoboAuth, the challenge
The challenge is a binary which asks for two distinct passwords. I salute the
ASCII art :P
Since futex powned ndh2K13 prequals' challenge (crackme200) with a simple
strings command, I will start from the beginning :p
~/VirtualBox VMs/Windows 7 Work/hacklu » file RoboAuth.exe RoboAuth.exe: PE32 executable (console) Intel 80386 (stripped to external PDB), for MS Windows
As expected, the first step is running the file command on that bin.
It tells us that we are dealing with a PE file, for 32bits architectures and
that it is also stripped to only contain public symbols.
Second step? :)
~/VirtualBox VMs/Windows 7 Work/hacklu » strings RoboAuth.exe [^_] r0b0 RUlef D$$t L[^_] L[^_] [^_] :MZt UWVS [^_] [^_] @' t |$D=N 3|$(3|$,1 UWVS [. . .] l$,vA <[^_] D$$t. <[^_] UWVS [^_] [^_] r/9D$ _set_invalid_parameter_handler %20s . _|_ ROBOTIC AUTHENTICATION SYSTEM /\/\ (. .) / `||' |#| ||__.-"-"-.___ `---| . . |--.\ | : : | ,||, `..-..' \/\/ || || || || |__|__| You passed level1! Unknown error _matherr(): %s in %s(%g, %g) (retval=%g) Argument domain error (DOMAIN) Argument singularity (SIGN) Overflow range error (OVERFLOW) The result is too small to be represented (UNDERFLOW) Total loss of significance (TLOSS) Partial loss of significance (PLOSS) Mingw-w64 runtime failure: Address %p has no image-section VirtualQuery failed for %d bytes at address %p VirtualProtect failed with code 0x%x Unknown pseudo relocation protocol version %d. Unknown pseudo relocation bit size %d. (null) PRINTF_EXPONENT_DIGITS Infinity ?aCoc <2ZGU ?_set_output_format _get_output_format vH7B W4vC [%Co O8M2 ___lc_codepage_func __lc_codepage DeleteCriticalSection EnterCriticalSection ExitProcess GetCurrentProcess GetCurrentProcessId GetCurrentThreadId GetLastError GetModuleHandleA GetProcAddress GetStartupInfoA GetSystemTimeAsFileTime GetTickCount InitializeCriticalSection IsDBCSLeadByteEx LeaveCriticalSection LoadLibraryW MultiByteToWideChar QueryPerformanceCounter SetUnhandledExceptionFilter Sleep TerminateProcess TlsGetValue UnhandledExceptionFilter VirtualProtect VirtualQuery WideCharToMultiByte __dllonexit __getmainargs __initenv __lconv_init __mb_cur_max __set_app_type __setusermatherr _acmdln _amsg_exit _cexit _errno _fmode _initterm _iob _lock _onexit _unlock abort atoi calloc exit fputc free getenv localeconv malloc memcpy puts scanf setlocale signal strchr strcmp strerror strlen strncmp wcslen KERNEL32.dll msvcrt.dll
Second step, strings command! (futex that's for you!)
Some strings look interesting:
- r0b0 and RUlef which could be the first and second password (or not)
- You passed level1! which we can later find in the debugger
- strcmp and strncmp which on what we could break and see the parameters
Well, I don't know if I already told you but reverse is not one of my best
skills (sleep? yeah, sleep is definitly one of my best skills).
Therefore please excuse my strategy for the next steps :)
Into its binary heart
I start by looking for the strcmp's references in IDA. Only one occurence, good
for me :)
Let us break on the two mov before the call of strcmp. They will certainly
contain the two strings that will be compared.
Let's try r0b0RUlez! as the first password then!
Nice! Well I admit, the first password was quite easy to find but let's continue and see what happens :)
Password 2! Stop hiding like a robot ninja!
The red line is a highlight to what bothered me. Fortunately jvoisin explained it to me :)
When you are debugging a binary and that you set some breakpoints, your debugger will in fact replace the instruction by an INT3, which is a SIGTRAP. Then, when that signal will be raise, the debugger will handle it and stops. Hence the break :) But if the guy who coded the binary already wrote some int 3 inside of his code, the debugger will not discern its own and the guy's one. If you have some lame debugger, it will even handle the breakpoints that are not its own. The trick here is that the guy will define a custom handler which will do some stuff when it will be called by the binary itself.
I'm glad to learn how IDA is smart :)
Since a trap debugger is not called a butterfly, but a trap debugger, I
will let the application handle the signal!
If you remember the strings command's results, it contained some strings like:
- SetUnhandledExceptionFilter
- UnhandledExceptionFilter
Let's continue...
After a while, we stumble upon an interesting part, where:
- It asks the user an input
- Call a function (well, already labelized by myself, sorry for the spoil :/)
- Then a branch where
- on one hand, it puts something and exit
- on the other hand, directly exit :p
We can presume that this function is indeed responsible for the check of the
second password.
Let's inspect it!
For the following, I wanted to statistically understand it, like a challenge
in a challenge (Yo Dawg! :D).
Remembering the explanations from jvoisin about reverse stuff, I will go step
by step.
First of all, the ESP stack pointer will be placed into EBP and arg_0 and arg_4 will be some kind of indexes/pointers for each strings.
The first letter of the ciphered string will be placed into EAX register:
- If it is 0x2 (the NUL-byte-like terminating character), we exit with the code 0
- Otherwise we continue
The first character of each strings will be respectively placed into EDX and
EAX. EAX is XORed with the key 0x2.
Finally, EAX and EDX will be compared, which means the first characters of
each strings:
- They are different, we exit with the code 1
- Otherwise, the two counters are incremented and we continue
We clearly understand that our input must match the 0x2-XORed string in
order to pass.
Here 0 means SUCCESS!
Let us use some python in order to retreive the deciphered password:
~/ctf/hacklu/roboauth » python2 -c "print ''.join([chr(ord(c) ^ 0x2) for c in 'u1nnf2lg'])"
w3lld0ne
Let's now try it!
Oh, yeah! Those sweet sweet words!
Therefore the flag: r0b0RUlez_w3lld0ne \o/
Might be interested: jvoisin prefers using SIGSEV signals instead of SIGTRAP ones :p Bonus: Robot Pirates (music) :)