- Mon 21 May 2018
- challenges
- #challenge, #SANS
This is my write-up for the hard challenge at SANS Zurich 18. The challenges are no longer available at pastebin, but you can get them from my git repository:
First let's download the challenge and check what's all about:
zep@base:~$ get -q https://raw.githubusercontent.com/7a6570/challenges/master/sans_zurich_18/hard/hard_challenge.txt
zep@base:~$ file hard_challenge.txt
hard_challenge.txt: ASCII text, with CRLF line terminators
Like with the medium SANS Zurich challenge, we have again windows line endings and the content looks like base64. Let's fix the line endings and decode the file:
zep@base:~$ sed -i 's|\r||g' hard_challenge.txt
zep@base:~$ cat hard_challenge.txt | base64 -d > hard_challenge_decoded
Let's look what we've got:
zep@base:~$ file hard_challenge_decoded
hard_challenge_decoded: ELF 64-bit LSB executable, x86-64,
version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2,
for GNU/Linux 2.6.32, BuildID[sha1]=ac22edc7622454d7aeccf1836dbda66b72c14543, stripped
Nice, this time we have an amd64 linux binary. Let's copy the binary to a Virtual Machine for further analysis, make it executable and run it:
zep@rev_base:~$ chmod +x hard_challenge_decoded
zep@rev_base:~$ ./hard_challenge_decoded
.-'''/.\
(_.--' |
| == |
o-._ .--..--. _.-o
| || |
;--|`--:
|. | |
| ;_ .|
|_____ |
/| '|\
//`----'\\
////| | \\
/ | | \
/| |\
/ \ / \
/ \/ \
/ \
| |
|| /\ ||
|| , . ||
[[ NOPE! ]]
zep@rev_base:~$
Fancy :-) We have to dig deeper. Let's open the binary in IDA. Looking at the available functions, we see that all the important stuff happens in the main function at .text:400666. Let's get all xrefs from the main function:
We can see that from the main function strcmp, puts, ptrace and getenv are called. The main function also calls itself, so it seems that we have some sort of recursion (main calls itself which calls again main and so on..). As with any recursive function, there must be some sort of recursion termination. Let's put this thought aside for a moment and let's have a look at the called functions, e.g. getenv.
getenv is used to get the content of an environment variable and is called two times with the argument "PATH". That means the value of the environment variable PATH is used somewhere. If we have a look at the strcmp calls, we can see that the value of "PATH" is first compared to WOOT, then the value of "PATH" is retrieved again using getenv, main calls itself and after this recursion returns, the value of env_path_2nd is compared to WINNER.
WINNER sounds good, let's have a look where we get, if the last strcmp evaluates to zero and the right branch is taken from the jnz instruction:
We can spot a lot of local variables, which probably belong to an array of chars which compose the flag string. This means the flag string is built at runtime and is stored encrypted. We can easily spot the used decryption loop and the used counter for this loop. The counter is first set to zero and each loop iteration compared to 31. After 32 loop iterations, the final string is sent to stdout using the puts function.
Let's start the binary in a debugger and just jump to the first instruction after the jnz instruction, which leads to the flag decryption. But first let's set a breakpoint before the ptrace call, since a program cannot be debugged two times using ptrace. This is some sort of an easy anti-debug trick.
We set a breakpoint at .text:4006C8 and start the binary in the IDA debugger and continue until we hit our breakpoint. Now we scroll down until we find the instruction at text:4008CE, which is the first instruction after all checks passed (WOOT and WINNER string comparisons). Here we set this instruction as the next instruction to execute using the "SET IP" function of IDA (left click on the instruction and choose SET IP). This sets the value of the rip register to 0x4008CE. Now we continue the execution by pressing F9. The binary prints the flag on stdout and terminates. To see the flag, IDA must have been started in a terminal (or maybe there is some other way to capture stdout in IDA?). Anyway the flag is:
FLAG: ThisisDefinitelyNotaTROLL
We were actually able to get the flag without thinking too much about the checks needed to pass. We just looked where the "success" path goes in the binary and jumped to this path. We were lucky and this time the lazy approach worked ;-)
But still I think it might be possible to get the flag without using a debugger and changing $rip. Remember the prototype for the main function ?
int main(int argc, char* argv[], char* env[])
The third argument is a pointer to the environment. Since the main function of the binary calls itself, a pointer to an environment can be passed. Maybe it could be possible to set the environment this way and to set value of PATH to "WINNER". The things is, first PATH needs to be equal to "WOOT" and at the second check, PATH needs to be equal to "WINNER". But we can set the value of PATH only once, before we start to execute the binary.