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.