COMMAND
gcc
SYSTEMS AFFECTED
gcc
PROBLEM
Paul Starzetz found following. On i386 systems using gcc/g++ it
is possible to change memory values if a global variable 'objects'
is overwritten. Sample vulnerable programm looks like this:
vuln()
{
static char buf[16];
strcpy(buf, much_more_than_buf);
}
main()
{
// calling vuln() will be ok
vuln();
printf("\nI'm alive");
fflush(stdout);
} <-- here it would sigsegv
In many (if not all) i386 binaries compiled with gcc there is a
global variable called 'objects'. It is a pointer to a list
holding information about unwinding the stack if an c++ exception
occurs.
To see this, try with some binary:
root@phoenix:/proc > objdump --syms /usr/sbin/checkdisk|grep object
0804a89c l O .bss 00000018 object.8
0804a7b4 l O .data 00000004 object_mutex
0804a8b4 l O .bss 00000004 objects
In particular the 'objects' variable is set to '0' if the program
doesn't use exceptions. If 'objects' is set, it will point to a
structure of type 'struct object':
struct object {
void *pc_begin;
void *pc_end;
struct dwarf_fde *fde_begin;
struct dwarf_fde **fde_array;
size_t count;
struct object *next;
};
The 'objects' variable will be inspected on the end_of_execution
of any programm by the gcc stub function called
__deregister_frame_info:
(gdb) bt
#0 0x804cc24 in __deregister_frame_info (begin=0x804f1e0) at
./frame.c:581
#1 0x8048d01 in __do_global_dtors_aux ()
#2 0x804cf55 in _fini ()
#3 0x400320f5 in exit (status=1) at exit.c:55
#4 0x804b2cc in usage ()
#5 0x804b7a4 in main ()
So lets look at the code of __deregister_frame_info, it is in
frame.c in your gcc distribution:
/* Called from crtbegin.o to deregister the unwind info for an object.
*/
void *
__deregister_frame_info (void *begin)
{
struct object **p;
init_object_mutex_once ();
__gthread_mutex_lock (&object_mutex);
p = &objects;
while (*p)
{
if ((*p)->fde_begin == begin)
{
struct object *ob = *p;
*p = (*p)->next;
/* If we've run init_frame for this object, free the FDE array. */
if (ob->pc_begin)
free (ob->fde_array);
__gthread_mutex_unlock (&object_mutex);
return (void *) ob;
}
p = &((*p)->next);
}
__gthread_mutex_unlock (&object_mutex);
abort ();
}
It looks promissing. So what will happen if due to a buffer
overflow 'objects' is pointing to an arbitrary address during
__deregister_frame_info call? __deregister_frame_info will go
over the object list like the ascii drawing below shows until the
condition fde_begin == begin (where begin is the argument given
to __deregister_frame_info = MAGICVALUE, it will be the address
of __FRAME_END__ in section .eh_frame) matches or the end of the
list is encountered:
objects
|
|
V
------------------------------------------------------------
| pc_begin | pc_end | fde_begin | fde_array | count | next |
------------------------------------------------------------
xxxx xxxx != begin xxxx xxxx |
|
-------------------------------------------------------|
|
|
|
V
------------------------------------------------------------
| pc_begin | pc_end | fde_begin | fde_array | count | next |
------------------------------------------------------------
|
|
This may lead to an endless loop, if you can fool 'objects' to
point to an area containing adresses of itself.
The interessting part begins, if fde_begin == begin
(begin=MAGICVALUE) in some element in the 'objects' list. Then
the following code will be executed:
struct object *ob = *p;
*p = (*p)->next;
/* If we've run init_frame for this object, free the FDE array. */
if (ob->pc_begin)
free (ob->fde_array);
So we are able to write to the address where the current object
structure begins (call it ADR) the value at the memory address
ADR+0x14 !
Lets now look at the asm code of __deregister_frame_info, the
piece we need is:
0x804cc10 <__deregister_frame_info>: pushl %ebp
0x804cc11 <__deregister_frame_info+1>: movl %esp,%ebp
0x804cc13 <__deregister_frame_info+3>: pushl %esi
0x804cc14 <__deregister_frame_info+4>: pushl %ebx
0x804cc15 <__deregister_frame_info+5>: call 0x804cc1a
0x804cc1a <__deregister_frame_info+10>: popl %ebx
0x804cc1b <__deregister_frame_info+11>: addl $0x25da,%ebx
0x804cc21 <__deregister_frame_info+17>: movl 0x8(%ebp),%eax
So we see that the magic value is taken from the stack at
0x8(%ebp).
Imagine now, we let 'objects' point to the address eax is taken
from - 8, which would be the pushed EBP. Then fde_begin == begin
will allways match in __deregister_frame_info and we are able to
overwrite EBP with arbitrary value from the memory location at
objects + 0x14.
The second thing we can do is supplying free() with some corrupted
chunk data if ob->pc_begin != NULL, as we see from:
if (ob->pc_begin)
free (ob->fde_array);
In this case we don't need 'objects' to point to the original
MAGICVALUE region on the stack, it may be easier to put some
address there we have full controll of (like environ). A free()
call on corrupted chunk data may be sufficient to overwrite the
return address, remeber the traceroute article...
Note that if ob->fde_array == NULL, free wouldn't have any effect.
Now let's think about modyfying the ESP and the return address!
Seems impossible? No! After looking at the asm dump of
__do_global_dtors_aux we found that it is sufficient to overwrite
the EBP in order to overwrite the ESP because:
0x8048cf7 <__do_global_dtors_aux+39>: pushl $0x804f1e0
0x8048cfc <__do_global_dtors_aux+44>: call 0x804cc10
which is <__deregister_frame_info> so we return here with confused
EBP!
0x8048d01 <__do_global_dtors_aux+49>: movl $0x1,0x804f128
0x8048d0b <__do_global_dtors_aux+59>: movl %ebp,%esp
esp is overwritten with _our_ confused ebp!
0x8048d0d <__do_global_dtors_aux+61>: popl %ebp
increments esp
0x8048d0e <__do_global_dtors_aux+62>: ret
so finally we jump to *(ebp + 4), which would be looking like
this:
(gdb) set $ebp=0x400b3320 (set to the value from &MAGICVALUE + 0x0c
taken from the stack)
(gdb) si
0x8048d0d in __do_global_dtors_aux ()
(gdb) si
0x8048d0e in __do_global_dtors_aux ()
(gdb) si
0x4000c5c8 in ?? ()
Program received signal SIGSEGV, Segmentation fault.
0x4000c5e0 in ?? ()
At this point we are executing some (junk?) data from 0x4000c5c8.
Of course, in practice this may be still hard to exploit.
It is _not_ necessary to overwrite the RET address on the stack,
it may be sufficient to overwrite the EBP value pushed on the
stack to gain controll over the programm execution!
Programms which are overwriting any local or global static buffer
are vulnerable to this sort of attack. Note that gcc will store
local _static_ buffers before the global (static) variables in
the .bss and they will be before the 'objects' hook.
SOLUTION
Nothing yet.