wopr decompiled

This post is a follow up to my Persistence post, and only of interest to people that really want to compare my decompiled C with the disassembled wopr binary.

Here follows the disassembled wopr executable, with my decompiling comments interspersed.

I glossed over error handling; wopr’s handling of failures (such as failure to open sockets) is completely ignored; only a best case-scenario is examined.

Persistence or: How I Learned to Stop Worrying and Love WOPR

I will remember September 2014 as the month I devoted to the Persistence competition over at VulnHub. As a relative newcomer to offensive security, I was not confident that I would ever reach the final conclusion of beating the system. Sure, I had completed Offensive Security’s excellent OSCP certification earlier in the year, but that was only a taster of the techniques that would be required to fully exploit sagi- & superkojiman’s devious challenge.

This very enjoyable (and frustrating) machine has taken me far beyond my basic understanding of buffer overflows and thrown me into the topics of binary decompiling, Return-Oriented Programming, Canaries, Linux shared-library addressing, and debugging. It’s been a great learning experience, and I highly recommend it for anyone interested in learning more about defeating buffer overflow protections, or even just x86 architecture.

So, for those still interested, please read on and I will try and recall the journey I took to break Persistence. Apologies if anyone finds my post too wordy or unpolished; this is my first and I am running out of time approaching the deadline.

Notes: My box is 192.168.13.127. Persistence is 192.168.13.181. Also I’m running Kali x64 so I needed to add the package libc6-i386 to allow x86 binaries to be analysed.

First steps

Persistence: "the fact of continuing in an opinion or course of action in spite of difficulty or opposition." by sagi- & superkojiman

With the machine booted up,  I found the new IP address on my network and set about scanning it.

Just a web server. I queried it in a browser and all I got was a static webpage with the famous Dali painting…

Dali's The Persistence of Memory

I did some initial service identification to see if I could get a quick start in, but I found that it was running a secure and properly configured version of nginx. Then hoping to find some hidden content I ran dirb, but its default wordlist didn’t turn up anything:

There had to be something in there, so I carried on with the more configurable OWASP DirBuster. Using the included word list directory-list-1.0.txt, and the file extension list “html,htm,php,pl,cgi”, I started a scan and waited. And then result! A single PHP page: /debug.php.

DirBuster scan settings DirBuster results Complete with ping tool (and command injection?)

By entering my IP address into this form and submitting, Persistence will send four pings towards my machine. I know this, because Tyler Wireshark knows this. The PHP form never produces any output, but I suspected that it was probably calling exec() with unsanitised user input and that it might be vulnerable to a command injection. That was quickly established to be true when I added additional commands to the input… “8.8.8.8 && ping -c 1 192.168.13.127” would ping a Google DNS server, and then ping my box once.

By using && logic I was able to tie together some commands and try and get some information back from the system. For example, “8.8.8.8 && php –help && ping -c 1 192.168.13.127” produced no pings back to my box, but “8.8.8.8 && python –help && ping -c 1 192.168.13.127” did. So I have access to python to execute complex attacks.

Python shell?

I tried some off the shelf reverse shell attacks, but no luck. So I also tried some basic TCP operations, but it appeared that no communication between Persistence and my box was possible. It looked like I was firewalled out. (This belief proved to be correct.)

The only communication I had was ICMP pings.

I took longer than I care to remember trying to figure out another way through. Eventually, while on the way to the pub one Thursday evening, I remembered being taught about steganography – the concept of hiding information in otherwise innocent data or communications noise.

It seemed mad to me that this was the intended challenge, but with some goading from friends and some encouragement from #vulnhub, I took my crazy idea forward.

ICMP Exfiltration

It turns out that this technique is all too common and has a name, but it was new to me at the time.

I started by constructing a script that would return the results of “ls -l”, and I would use ping‘s -p parameter which allows me to specify up to 16 bytes of data in the packet. The final input text field data looked like this:

Using wonderful BurpSuite Repeater, I made got my request URL-encoded and sent it out, and then to my amazement I was not only getting a large number of pings back, but was also getting interesting information back! I was on the right track. I read information coming through in byte range 3a-40 of each package captured in Wireshark:

Data stolen over ICMP is revealed

And was able to piece together the following directory listing:

Jackpot! Next, I would attempt to run sysadmin-tool to see what information I can gather from it. Again, I would use BurpSuite and URL-encoding to send the above request, but this time I would run “./sysadmin-tool” with parameter “–help”. Here’s the response extracted from Wireshark:

Finally, I modified the parameter once again to “–activate-service”:

Fingers crossed, I ran another nmap portscan on the box to see if there were any new services open. I was not disappointed; SSH was now open:

The Limited Shell

I quickly connected over SSH with the provided credentials, and found myself in a limited shell, where I couldn’t change directory, and could only execute commands contained in /home/avida/usr/bin.

After jumping into autopilot and trying some standard attempts to escape limited shells, I realised that I was in rbash and that it’s a pretty secure jail-cell. I needed to find something in usr/bin that I could exploit:

For the first time on this challenge, the answer jumped out immediately. I could use nice to run another shell and break out of the jail. I would also update the PATH environment variable so that I had intuitive access to all available system commands

Meet WOPR

Thus began my proper reconnaissance of the target. Looking back, this was a playful break before the major challenge. I examined the running processes, open ports, any logs that I could read, and traversed the file system for as many clues as I could find.

The system was pretty locked down, felt well configured, and no amount of searching the CVE database revealed public exploits to be available for the operating system (CentOS 6.5 in this case).

The only thing that seemed genuinely targetable was a service running on TCP port 3333:

Only accessible from my SSH shell, I tried connecting…

Nice. If I’m not mistaken that’s a reference to the film War Games. Having too much fun, I did some searches on the film in case a relevant response was required. I tried all sorts of things, but the response is always the same:

But the response is always the same…

Except for that last one! Notice that “[+] bye!” is missing in the response. No, that behaviour is not because I correctly guessed the correct phrase, but because the length is overflowing a buffer and changing the program’s behaviour. This tell will be crucial later on!

I surmise that the service running on TCP port 3333 is the one called wopr (also the name of the mainframe in the film), and confirm that each time I connect to port 3333, a new wopr process is forked and then terminated:

I won’t be able to debug the application on Persistence, so I take a copy of it to my Kali Linux box for analysis. Since I was prevented from creating any TCP (or even UDP) connections in or out of the box I resorted to base64 encoding the wopr binary in the terminal and then decoding it on my own machine. (No more unwieldy ICMP exfiltration required.)

An exercise in decompiling

Now that I had a binary, I disassembled it with objdump. It was not very large. As I need to learn to become a bit more fluent with assembly language (and also since I don’t have a fancy expensive decompiler like IDA Pro to do it for me) I decided to go about decompiling it manually.

This bit took me a few hours, but that’s mainly because I had to comb through the instructions and reference a good x86 instruction set reference.

For those really interested, my poorly decompiled C can be read interspersed in the disassembled program. But for most readers, it’s enough to know that the program looks something like this:

There you have it; there’s no real game logic to the application. But there is a buffer overflow that can be exploited…

When function main reads from the TCP connection, it will consume 512 bytes (or less if less sent at that point) into an array. That is done safely. However, it then calls get_reply, passing a pointer to that array. get_reply copies the entire length of the array into a local array that is only 30 or so bytes long. Buffer overflow!

Canary cracking

However, the function is protected by a canary. This is the reason that (earlier) the program didn’t output “[+] bye!” when long input was sent its way; the canary gets overwritten, and so instead of returning to main cleanly, the __stack_chk_fail function is called which terminates the program for its own protection.

So I cracked open The GNU Debugger and began to step through the program to see if the canary was static or not. I would want to be debugging the forked child processes, not the parent handling connections. To do so, I started building up my attack script in Perl. Key feature is the option to sleep between connecting and sending data; this allows me enough time to attach to the child process and debug.

For the avoidance of doubt, one can attach to the child thus:

After stepping through the program a few times, I found that the canary would remain the same for each new connection, but that the starting value is seemingly random. A restart of the parent process will change the canary and therefore cannot be predicted (by me anyway).

At this point I was seriously wondering whether I was seriously meant to try and brute-force the entire 2^32 combinations. That would take a long time. However, it was seriously considering this idea that a better one was presented (regretfully I did not reach the conclusion on my own). If I can overwrite the canary one byte at a time, then I at most need to try 256*4 combinations; very feasible!

Here is what my canary smasher looks like. It differentiates between a trashed and pristine canary by the absence or presence of the “bye” in the response.

This runs quickly…

(It’s worth mentioning for other buffer overflow beginners, that there is no need to find bad input characters in this challenge, as strcpy is not being used. I can see from the assembly that only memcpy and read functions are used, which will copy data verbatim.)

Segmentation fault

It was at this point that I realised I had control over the stack of the program and I got a bit excited. Instead of evaluating my new surroundings, I ploughed straight in and tried to create a shellcode. Since I couldn’t make network connections, reverse shells were out. So I used msfpayload to make a payload that would create a new user.

Nope. Segmentation fault. It’s a non-executable stack. If I had taken a few minutes to do some more footprinting of the application I wouldn’t have wasted my time. Arming GDB with the PEDA extensions allows this to be known at the start:

I realised at this point that I was going to need to level up if I was going to have any chance beating Persistence. It was naive of me to think that I would be able to beat a machine so-called that easily.

It was time to finally learn what all of the fancy terms like ROP, retn2libc, PLT, and ASLR were all about.

First things first… Is ASLR actually enabled on Persistence?

No. OK, that’s one less thing to worry about. I set my Kali box to be the same and started work.

Back to school

I spent a couple of days of intensive reading. While I was very keen to just smash the machine and get on with my life, I wanted to understand what I was doing and why it worked, not just beat it.

I want to use an analogy I once heard P. T. Anderson use about the writing process. Well, learning a computing specialism, for me, is a lot like ironing. Sometimes I just need to get the shirt ironed, so I sweep the iron over the fabric as quickly as possible. That’s me copying an exploit from a guide or using a cheat sheet. But there are all of these creases; these are the gaps in my knowledge and hacks I didn’t understand. So I try to go back over the creases as much as I can and try and get them flat. The only way I know to do that it to read the theory, and to get my hands dirty with the disassembler going through the attack time after time until it makes sense.

This takes many, many hours, but it is worth it.

In that spirit, I read a great many sources. Too many to remember them all but ones I found most valuable were:

Return-oriented programming

The first challenge that needs to be solved is the non-executable stack. For this, I learned about ROP; specifically return-to-libc attacks, and knowing about it is fundamental to the rest of this walkthrough.

This involves contriving an incorrect stack history in memory. A normal stack should look something like this:

  1. We’re in function_c. At end of function jump to just after function_c was called in function_b.
  2. We’re in function_b. At end of function jump to just after function_b was called in function_a.
  3. We’re in function_a. No more stack; just jump to exit system call.

If you can overwrite the stack you can contrive a completely incorrect (yet deterministic  and executable) stack history, which allows you to run any functions in sequence, provided you know their addresses:

  1. Currently in function_c. At end of function jump to start of strcpy.
  2. In strcpy. At end of function jump to start of write.
  3. In write. At end of function jump to start of puts.
  4. In puts. No more stack; just jump to exit system call.

This looks completely weird for most people that are used to high-level programming, but it works very effectively. Here’s a quick illustration of how it looks, but bear in mind that the guides I’ve cited are better explained.

What this is doing is performing one strcpy operation (from Memory address ARG2 to ARG1), then calling write to write ARG3 bytes from memory location ARG2, to filehandle ARG1. Finally, puts will print the string at memory location ARG1 to standard output.

  1. Call strcpy with return address write, and two arguments for strcpy: destination ARG1, and source ARG2.
  2. When strcpy returns, the instructions POP, POP, RET are called, thereby removing the two arguments from the stack, and returning to… start of write
  3. Call write with return address puts, and three arguments for write: filehandle ARG1, source ARG2, and length ARG3.
  4. When write returns, the instructions POP, POP, POP, RET are called, thereby removing the three arguments from the stack, and returning to… start of puts.
  5. Call puts with return address exit, and one arguments for puts: string ARG1.
  6. When puts returns, we will just call exit. No POPping required.

This is best learned by practice, and I shan’t go into the detail of the many failures and successes I had in learning about this.

Planning an attack

The attack I want to do is essentially to call system with a suitable command payload. To do so, I will need to know the address of system, and I will need to find (or inject) the commands I want to execute into the program memory.

It’s also worth recognising that I only have 474 bytes that I can insert into the stack (512 bytes, minus the 30-byte variable, the 4-byte canary, and the SFP).  In ROP, each call requires 16-bytes on average, so there is a maximum of around 29 functions that can be called.

A number of pieces of information need to be gathered for a successful attack.

Suitable POP addresses

First of all, you will need some POP [..] RET addresses.

msfelfscan will find these, but it appears to only find examples with two POPs.

We will be needing three POPs in some cases, so some manual searching is required. First, identify the memory range that wopr occupies using gdb-peda. Here, the start:end range is identified as 0x8048000:0x804b000…

Next, find all POP EBX instructions (0x5b) and then find one that is part of a chain of three.

DISCO! 0x8048bb6 contains a required POPx3, RET sequence.

Get constant function addresses

We can query function addresses in gdb. Anything in the range 0x8048000:0x804b000 can be reliably called across systems. These addresses are mostly short functions in the Procedure Linkage Table that redirect to the real shared function-locations in memory.

 Understanding PLT/GOT redirection

Take for example function write (0x0804858c).

This is just a PLT skeleton that jumps to an address contained in 0x804a008…

i.e. the constant addresses of functions in the PLT table are used to redirect to the dynamic addresses of the libc functions in shared memory. 0xf7f269e0 is the address I get for the write function, but not for Persistance, or any other machine.

Shared memory base and offset locations

Final trick…

If you can get a program to leak the dynamic memory address of a libc function that we do have the PLT/GOT addresses of (e.g. write), then we can also get the dynamic memory address of any other function in that library.

By way of example, let’s pretend that in the last section, the dynamic address of write@got.plt (0xf7f269e0) has been leaked and not stolen through debugging. Now we want to discover the address of another function, say, system.

To do so, simply dump the address offsets from the library and calculate.

The dynamic address of write, minus the offset address of write (0xf7f269e0 – 0x000c99e0) is 0xf7e5d000, which is the libc base address in memory.

The dynamic address of system, therefore, is 0xf7e5d000 + 0x0003be40 = 0xf7e98e40.

This can be confirmed in testing with gdb:

Leaking memory addresses

Since I want to run system and possibly strcpy , I need to find their address, but they are not in the PLT of wopr.

I start by trying to find the address of another function that is, e.g. write. I really need to be able to leak that information out of the process. To do so, I would utilise a call to write, and copy the address contained in write‘s PLT pointer over the TCP connection.

I already have the addresses of some POP instructions, and also write, and exit functions:

  • POP, POP, RET: 0x08048743
  • POP,POP,POP,RET: 0x08048bb6
  • write@plt: 0x0804858c
  • write@got.plt: 0x0804a008

I next find the offsets of functions in libc on Persistence:

I added the following block to my canary cracking script from earlier:

This outputs the addresses of these functions as they appear in Persistence’s memory:

Building a successful payload

On the home stretch, I thought about building an attack with multiple calls to strcpy. In one arbitrary memory I would copy the characters I want system to execute (e.g. filling it with the string “/usr/sbin/useradd -o -u 0 -p 64B0X1YfSO9v2 bobx00” which will create a superuser with credentials bob:bob).

However, finding all of these characters in the address range 0x8048000:0x804b000 is not guaranteed. It’s also very time consuming. And it turns out to be infeasible as there are 47 bytes, and therefore 47 functions calls required (over the 29 maximum provided by the buffer overflow length).

A more reliable and quicker option is to use the TCP connection that is already open and send data over that…

I updated my send_data Perl function to accept a second request parameter that would be sent after a short time after the first. (i.e. the request should not be swallowed up as part of wopr’s legitimate and only call to read, where it will consume up to 512 bytes. A short delay means it is will not be consumed and will be waiting for my own injected read call.)

Without further ado, here is the final script which I proceeded to run on Persistence, now very much defeated:

Proof

Conclusion

If you’ve read this far through my rather unwieldy post, then you deserve an award for persistence yourself! Whatever reason you read this – whether its a fast solution for Persistence, a desire to understand some of the tricks required on this challenge, or just a curiosity about how I waste my spare time – I hope that you enjoyed it.

Some of the things I learned from the Persistence challenge:

  • Realisation that some of the weirder ideas I have (e.g. sending data through ICMP) aren’t that weird in fact
  • That not only are these ideas possible but I can do them confidently when I try
  • Having to think outside the box can be very frustrating at times, but it is then so much more rewarding when a solution is found
  • I learned a lot more this month about assembly than I did in my University classes on the topic. Thanks very much to the creators.
  • Everything builds on top of something else. I couldn’t have done this if I hadn’t first done OSCP, and read Hacking. And the skills I learned on this will be crucial for something else in the future

I hope this is of some use for anyone that finds it. If you have any questions, suggestions, or corrections,  please feel free to leave me a comment and I will try my best to respond or update my post as soon as I can.