Insecure Programming by Example: abo2.c, not vulnerable…o rly?

Introduction

Note 02/13/2010: This post has been a long time coming (started on 01/15 I think), I’m sorry for the delay. At first, it took me a while to (SPOILER, YOU WILL DIE ALONE) find out that abo2.c was not exploitable under x86 due to the exit() call…I saw this immediately, but it took me a while to believe it. Then, I researched other possible ways it could be exploited, and then searched around for a machine on which to test. I ended up cutting the post short from what I had intended, because I couldn’t get my hands on a PA-RISC machine to test with, and QEMU support for PA-RISC is not quite there yet. The post may be a bit rough, any mistakes are all mine, and I’ll gladly accept corrections anywhere they are needed.

After a long time of head banging (and not the good kind), I finally have something good to report in regards to abo2.c, and I figured I’d write it all up for your enjoyment. Here’s the deal, abo2.c is not vulnerable to code execution on Linux using x86 architectures (the important bit here is x86, not so much Linux). That doesn’t mean it can’t be exploited…these are definitely not the same thing. Many folks state (correctly) that abo2.c is not vulnerable under x86 and then move on.

My thought process is that in my dream job, I’d be working with more architectures than just x86, so why limit myself? Keep in mind the object of the game. You are supposed to win, not just learn a valuable lesson. Ok, so if you just move on, you get the bit about why exit() is a deal breaker…big deal. Why not learn to win? Why not examine some different techniques that could have been applied if this code had been slightly different, or different techniques that could be applied (even better) with the code completely unchanged but compiled on a different processor architecture? The goal is to win and to learn something, so let’s do both! 😉

Let’s talk about the code real quick, and why it’s not vulnerable.

/* abo2.c                                       *
 * specially crafted to feed your brain by gera */

/* This is a tricky example to make you think   *
 * and give you some help on the next one       */

int main(int argv,char **argc) {
	char buf[256];

	strcpy(buf,argc[1]);
	exit(1);
}

What’s new here, as opposed to the abo1.c exercise which we successfully exploited only…weeks ago (I haven’t posted in a while, jeebus)? The only difference is a call to the exit() function at the end of the code. That is a real deal breaker for x86 exploitation, let’s examine why this is the case quickly.

You’ll recall that we’ve been reliably exploiting all of our vulnerable programs so far by overwriting the return address which is saved on main()’s stack frame (frame #0). The thing is, exit never returns to the main() function. As a matter of fact, if you disassemble the main() function you’ll see that there is nothing below the call to exit.

(gdb) disas main
Dump of assembler code for function main:
0x080483b4 :    push   ebp
0x080483b5 :    mov    ebp,esp
0x080483b7 :    sub    esp,0x118
0x080483bd :    and    esp,0xfffffff0
0x080483c0 :   mov    eax,0x0
0x080483c5 :   sub    esp,eax
0x080483c7 :   mov    eax,DWORD PTR [ebp+12]
0x080483ca :   add    eax,0x4
0x080483cd :   mov    eax,DWORD PTR [eax]
0x080483cf :   mov    DWORD PTR [esp+4],eax
0x080483d3 :   lea    eax,[ebp-0x108]
0x080483d9 :   mov    DWORD PTR [esp],eax
0x080483dc :   call   0x80482c4
0x080483e1 :   mov    DWORD PTR [esp],0x1
0x080483e8 :   call   0x80482d4
End of assembler dump.

What this means is that execution never returns to the original program from exit. You can verify this behavior yourself by trying to set a breakpoint after exit, or trying to step over the call to exit, you’ll see that the program merely exits. We can’t overflow the return address and have it matter, because the processor will never get back there. Game over man, game over.

The only way to beat this would be to prevent exit() from being called, which is a valid strategy that we should explore. To me, that still fulfills the idea of “gaining control”, sometimes preventing a program from doing something is just as important as making a program do something else…you think that all the crackers out there make programs authenticate themselves fraudunlently, or just prevent them from authenticating the validity of the license? Either path has merit.

Insert Coin to Continue

I was a big fan of arcade games growing up. I had a mean hadouken and dragon punch. Part of getting pretty good at arcade games was not giving up. Keep putting in coins. Keep playing. Get better. If some bastard had the machine monopolized and was beating all comers, well, beg your parents for change, because nothing would make you good faster than getting the tar kicked out of you. For me, abo2.c is that bastard at the machine.

If I have one regret about how I handled this exercise, it’s that I spent too much time pondering and not enough time on the debugger. Too much time Googling, not enough time GDBing. This exercise taught me to not be afraid of disassembling everything, even libc calls, to determine the flow of execution, and it also taught me that the assembled code is really what matters. In the end, I wasted a lot of time re-thinking thoughts when I already knew the answer, abo2.c was NOTVULN on x86 Linux (at least, not vulnerable to code execution…a Denial-of-Service condition exists of course by way of segmentation fault). Once a friend helped me see that this was the case (thanks @kpyke), I resolved to beat the program anyway, in any way I could. I also learned some new stuff I’d like to show you which looked at first blush like solutions on x86, but ended up being inadequate.

What Could Have Been

From “Overwriting the .dtors section.” by Juan M. Bello Rivas:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>

static void bleh(void);

int
main(int argc, char *argv[])
{
        static u_char buf[] = "bleh";

        if (argc < 2)
                exit(EXIT_FAILURE);

        strcpy(buf, argv[1]);

        exit(EXIT_SUCCESS);
}

void
bleh(void)
{
        printf("goffio!\n");
}

This paper details the process of exploiting situations in which you may not control execution via arbitrary memory writes OR return address overwrites. This paper taught me a lot in reading it even though it wasn’t a solution for abo2.c on x86, I suggest you read it as well and I figured it was worth outlining what it could have been a solution to.

Note the differences; he’s declared the buf character array as a static variable, and declared it initialized (with value/data) as well. When he does this, the variable is no longer located on the stack, since by definition a static variable must persist through a stack frame and be available to other functions that wish to use it. The variable ends up residing in the .data section of the executable file, and in older versions of GCC (used at the time of the writing of the paper, but no longer the case since at least 2006, if not earlier) .data comes before .dtors in memory. What is .dtors? .dtors is a mnemonic for “destructors” (while .ctors is for “constructors”), in the C programming language you have constructors and destructors, which are attributes you can assign to a function to have it be automatically executed on enter or exit (for instance, to clean up allocated memory on the heap or something like that which C does not do automatically). The .dtors and .ctors sections are the GCC implementation of constructors for the ELF file format. Even if there are no constructor or destructor attributes defined in the program, GCC still defines the .dtors section in an ELF file, it just leaves the section empty. When destructors are called, the program jumps execution to an address described in the .dtors section, if it hits NULL bytes it does nothing. Here is what an empty .dtors section looks like:

$ objdump -s -j .dtors bleh

bleh:     file format elf32-i386

Contents of section .dtors:
 804955c ffffffff 00000000                    ........

To make it quick(er), if we overwrite the value 00000000 with an address containing instructions, that address will be jumped to on program exit. If we place our shellcode in an environment variable (see my post on abo1.c or stack5.c for details), we can simply overwrite with the address of the shellcode and execute whatever we wish, or as in the author’s example we can redirect execution flow to another function in memory that otherwise would not be hit. Remember that our statically-declared variable buf is right next to .dtors in memory, and since strcpy() is not doing bounds checking, we can overwrite it after we determine the offset and execute arbitrary code. The paper really is a fun read, I suggest you take a look.

It is also worth mentioning one more paper that is unfortunately not applicable here, but is still an interesting read. “How to hijack the Global Offset Table with pointers for root shells” by c0ntex is an excellent overview of the concepts regarding the Procedure Linkage Table and the Global Offset Table, two ripe areas for controlling execution if you are fortunate enough to be able to overwrite the pointers contained therein. In short, the PLT and GOT are essentially how a given program knows where to find a shared library call (such as exit in libc). If we could overwrite the pointers in the table, we could execute arbitrary code in place of the call to exit(). Unfortunately, abo2.c does not present an opportunity to do this, nor do stack buffer overflows in general. A format string bug would probably be the best way to execute this attack, so far as I know, but it’s still a very interesting read that I encountered doing research for this article.

Architecture is Important

All of our examples so far have been exploited on an x86 virtual machine (powered by the great free tool VirtualBox) running an old, vulnerable version of Linux with many security features disabled, such as non-executable stack protection, or address space layout randomization. But in this case, we’ve merely been defeated by the design of the x86 stack. Due to the way the stack is arranged in memory, and the fact that our overflowed buffer is located therein, there is nothing we control that gives us any value.

What if we compiled it on a different architecture? Would it work then? How about an architecture that arranged it’s stack differently. Some architectures don’t actually store the stack in main memory, some of them (such as ARM) implement the stack in registers, which I suppose speeds things up, but limits the amount of data that can be stored therein (just speculation, I know dick all about most of this stuff ;-), I’m sure there’s a trade-off though). Some processors do not grow the stack from high-to-low memory addresses, they instead grow it from low-to-high just like other constructs such as the heap. What this means is that those processors are protected from return-address overwrites, because the return addresses are actually on lower addresses than the beginning of the vulnerable buffer. That does not mean that the processor is a safe haven for unbounded functions, though, not at all.

You probably already realize what this means if you’ve been following along and paying attention. This means that we can overwrite the return address of the strcpy() function itself. This means that we never need to leave the strcpy() function to gain control (conceptually, at least), and that we don’t have to wait for the end of the program (main’s return address) like we do on x86. This means that we’ll never get to the exit() call, and that abo2.c is exploitable under certain conditions. In researching this technique, I found out that it’s been done through Phrack #58 Article 11 by Zhodiac, and that it’s a really good read if you’ve never done any low level work on “exotic” architectures. So, instead of doing the exploit myself (mainly due to lack of resources, PA-RISC machines are ~$200.00 on eBay and virtualization is essentially non-existent), I’ll just recommend you give Zhodiac’s paper a read.

Man, I’m glad this post is done with. Procrastination is the devil, on to the next challenge!

Advertisements

One response

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: