Bugtraq mailing list archives

Buffer overflows using 'objects' hook


From: Paul Starzetz <paul () STARZETZ DE>
Date: Fri, 19 Jan 2001 18:48:52 +0100

1. Abstract:
------------

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


2. Details:
-----------

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. (I'm right?)

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
I 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 :-)


3. Conclusion:
--------------

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!


4. Impact:
----------

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.


Paul Starzetz


Current thread: