July 5-7 Brooklynt Overflow participated in SIGINT CTF hosted by the good folks over at CCCAC in Germany. Despite the fact that Brooklynt Overflow is not always the most effective team during the summer owing to inability to gather in the same place and the fact this competition was over a holiday weekend (Go team USA!) we didn’t fare terribly, finishing #21 on the scoreboard. This is a write up of one of the pwning challenges, crash.
A crash won't help you here. Escape this feature-rich shell by
whacking it with a reliable exploit.
The program they give you is called crash. Crash is a shell that exposes some very basic functionality.
The shell doesn’t appear to support the creation or editing of files. Pipes are left unimplemented and other programs, like text editors, cannot be executed. None the less I decided to go with my gut and investigate the cat
command. One of the first few files I tried to cat
was the shell program itself. The resulting crash appeared to be not immediately exploitable but I figured I was on the right track and created my own binary file to read.
python -c "print ''.join(map(chr,range(0x100))*0x1000)">binary
cat
ing this binary file did not result in a crash so tried cat
ting some other files. I found when I cat
ed executable files like /bin/bash
and libc.so
the program would crash and when I would cat
random binary files of my own making the program would not crash. Eventually the root cause of the crashes became apparent. An uncontrolled format string vulnerability. I tested this hypothesis with a file containing a format string.
python -c "print '%08x.'*0x80" >fmtstr
And when I cat
ted fmtstr:
This confirmed my suspicion of an uncontrolled format string vulnerability. However this vulnerability doesn't do us very much good if it is only in the `cat` command because I don't have the facility to place files on the remote server. Lets check to see if it is in `echo`!
It appears that the echo
command also has the same vulnerability. This is much better for us because now we don’t need to finagle a file onto a server through this shell program.
Format strings as I’m sure the reader is aware are great bugs for not only reading memory but also for writing memory. What I would like to do now change this uncontrolled format string vulnerability from an info leak from the stack into a write anything anywhere exploitation primitive. In order to do that I need to be able to place a value on the stack to be used as a pointer. I discovered this was possible through the dec
command.
If we issue the dec
command before the echo
command the argument to dec
appears on the stack. This is perfect. we can use this value as a pointer to read arbitrary memory with the %s format specifier or we can use the %n specifier to write to that pointer instead. We now have the ability to read and write memory from any memory region that is marked R/W respectively.
At this point, I thought it made sense to go about acquiring control of the instruction pointer. There is a table of function pointers inside the program that correspond to the commands the shell exposes.
The program is compiled without PIE and statically such that it doesn’t load anything dynamically. That means we can rely on these pointers and all of the executable code always being at the same address each time the program is run. Not that it really matters, we could use our read primitives to derandomize the memory space if this wasn’t the case.
I chose to overwrite the quit
pointer, I figured we would have no occasion to call it unless we were giving up. It would be pretty sweet if instead of quit
ting we got a real shell instead.
Crafting commands that write arbitrary values to arbitrary places is not terribly difficult. The %n specifier will dereference a pointer and write the number of bytes that have been printed so far. Writing 4 bytes at once is ill advised because you may end up trying to print 4gb to the screen. Two byte writes are reasonable, the maximum number of things to print is 65535 and in practice it’s often much less. The following combination of commands will crash the shell with EIP=0x41414141
dec 81380a4
echo %16705x%25$hn
dec 81380a6
echo %16705x%25$hn
quit
Very simple, we have overwritten the quit
function pointer and then invoked it.
Now, many of these commands take an argument and that argument is passed on the stack at runtime. If we can control the function pointer and the argument we might be able to call a function like system
with an argument like /bin/sh
. Because the program is statically compiled and stripped finding system
is no easy task. We know that system
eventually calls execve
and usually references the string /bin/sh
. When running strings on the program I did not see the string /bin/sh
so I went about trying to identify a call to execve
. execve
is a systemcall. In a statically compiled 32bit application on a modernish Ubuntu operating system
will call execve
by putting 11(0xb) in EAX the arguments in EBX, ECX and EDX and then execute an int 0x80 instruction.
The int 0x80 instruction in most cases was refactored and became its own function.
Cross-referencing this function you can see most of the system calls being made. None of them place 11 in the EAX register. There are also a handful of inlined int 0x80 instructions but they do not result in execve
being called either. This means we need to start thinking about alternative exploitation methods, like ROP.
There is a tool I discovered once a long time ago for identifying ROP gadgets. It’s not my preferred tool because it will only look one instruction back from a RET (except for pops) and won’t consider sequences of instructions ending in jumps or calls but it will attempt to automatically generate a shell spawning ROP chain for you. I hoped this binary would be suitably large for its engine to work with. Sure enough it was able to stitch together a 33 gadget chain resulting in a shell being spawned. Now all that needs to be done is getting ESP pointing at it.
I don’t like very many gadget searching tools enough to pimp them out on the ISIS blog but I the ones I tried did not yield very many gadgets that looked promising. There was a pop ESP I tried to use briefly, it would have been perfect if there was a pop before it but alas, since when we overwrite the quit
function pointer when we get control of execution there is a return address on the stack. Setting the stack pointer to the .text segment generally leads to disastrous results. Overwriting function pointers doesn’t seem to be the way to go, time to get a little more creative.
Did you notice when we were leaking values from the stack that we also leaked some stack addresses?
Many but not all of these stack addresses are actually saved base pointers. Saved base pointers on the stacks of well formed c/c++ programs are interesting in that by a happy coincidence point to other saved base pointers. That means we can modify a saved base pointer on the stack by using another saved base pointer as a pointer to it. To understand why this is beneficial remember what the tail of a function looks like.
[code] mov esp,ebp pop ebp ret [/code]
Now when the the return instruction executes we will have control of the base pointer, not very exciting right? Consider what happens if we return near the end of another function. When the caller returns the basepointer that we control gets moved into the stack pointer, a pop occurs and then a ret. If we can precisely control the stack pointer and account for the pop before the return we can reliably execute a ROP chain.
Here we have written the lower two bytes of a saved base pointer with zero’s using the %6$hn format string. When we issue the quit
command the programs stack is unwound sufficiently to let the clobbered base pointer transfer to the stack pointer. The stack pointer as you can see is resting at 0xffff0008. The low byte is 0x08 because the program has poped twice, once into EBP with the value zero and once into EIP with the value zero.
Controlling execution is now very easy. We can leak the address of the stack and calculate an address to write our ROP chain. We can then write it two bytes at a time using the uncontrolled format string vulnerability. Then we can clobber the lower two bytes of a saved base pointer. Finally we can trigger the execution of our ROP payload by issuing the quit
command. Keep in mind that we are no longer overwriting quit
’s function pointer. We are allowing the stack to unwind as it normally would as the program prepares to exit.
Just a note as you read this code, the getSocket() and shell() functions are located inside of the ISIS library which can be found here. You will also notice that this exploit connects to localhost on port 2323 but the program does not do anything with sockets. That is because I’m running the program with socat
, for details look in the ISIS CTF-Solutions repository on github. Feel free to pull the repo, run handler.sh and then the exploit!