Level text: Level15
This is no doubt the hardest level I’ve done so far in this series of write-ups. The challenging thing about it is that I needed to know how the mechanics of linking work, what are the differences between static and shared libraries, which one to use and when, and what do we actually need to do to pass this level.
INTRO:
To get from hand-written source code (in C for example) to the final executable file you need to go through a few steps. First you need to write that slick, well-structured, state-of-the-art code that you learned to write in your programming class. The second step is compiling (translation of one language – the source language, to the target language), which is done by the compiler program. Compiling alone isn’t that useful as it creates object files that you can’t actually run. Object files are machine language instructions that correspond to the source code you wrote. Now that you have your compilet source code in form of object files, you need to LINK them with the linker. A linker is a computer program that takes one or more object files generated by a compiler and combines them into a single executable file, library file, or another object file. This is how you get your final program or library. I don’t have space for all the details here, but I suggest you see how the linker works in more detail.
Differences between static and shared libraries:
When you write a program, most of the time you use some functions that you didn’t write yourself, for example printf(), system() and others. These functions are typically residing in a library. The standard C library in GNU/Linux systems is named glibc. Static library definition from wikipedia: “Static library or statically-linked library is a set of routines, external functions and variables which are resolved in a caller at compile-time and copied into a target application by a compiler, linker, or binder, producing an object file and a stand-alone executable.“, and shared libraries are: “Shared libraries are libraries that are loaded by programs when they start. When a shared library is installed properly, all programs that start afterwards automatically use the new shared library.“. So in simple words: static libraries are linked (at compile-time by the linker, that is a program) to the compiled program that you wrote and cannot be replaced unless you recompile the whole program again. When you work with shared libraries, the shared library is loaded into memory (by the loader, part of the OS) at run-time (NOT compile-time!), which gives us the ability to change the definitions in the library and use it without the need of recompiling our program. This is just a very short and simple explanation, but everything you need to know about it so you can clear this level is explained in the before mentioned link.
SOLUTION:
First thing we need to do is to strace the binary at /home/flag15/flag15 and see if we spot anything out of the ordinary.
level15@nebula:/home/flag15$ strace ./flag15
execve("./flag15", ["./flag15"], [/* 20 vars */]) = 0
brk(0) = 0x9e8f000
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) = 0xb7783000
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", 0xbf8c7444) = -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", 0xbf8c7444) = -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", 0xbf8c7444) = -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", 0xbf8c7444) = -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", 0xbf8c7444) = -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", 0xbf8c7444) = -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", 0xbf8c7444) = -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", 0xbf8c7444) = -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", 0xbf8c7444) = -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", 0xbf8c7444) = -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", 0xbf8c7444) = -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", 0xbf8c7444) = -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", 0xbf8c7444) = -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", 0xbf8c7444) = -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", 0xbf8c7444) = -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=4096, ...}) = 0
open("/etc/ld.so.cache", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=19771, ...}) = 0
mmap2(NULL, 19771, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb777e000
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) = 0xc19000
mmap2(0xd8f000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x176) = 0xd8f000
mmap2(0xd92000, 10776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xd92000
close(3) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb777d000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb777d8d0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xd8f000, 8192, PROT_READ) = 0
mprotect(0x8049000, 4096, PROT_READ) = 0
mprotect(0xfdf000, 4096, PROT_READ) = 0
munmap(0xb777e000, 19771) = 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) = 0xb7782000
write(1, "strace it!\n", 11strace it!
) = 11
exit_group(11) = ?
We can see that it tries to open /var/tmp/flag15/libc.so.6 but doesn’t find it. With readelf command we can examine the executable to see if we can find any useful thing, here’s the output:
As we can see, the needed library for this executable is the libc.so.6 shared library and it needs to be located in the /var/tmp/flag15 directory pointed to by the RPATH entry. You can read more about rpath HERE. Cool, now that we have a clue what library the program needs, we need to find out what functions from that library the program uses so we can make our own shared library with custom functions. We can do this with objdump -R option which gives us a list of all dynamic relocation entries of the given file, which will show us all the symbols we need to find in the shared library:

puts() looks like a good idea but the thing is that the main function needs to be called first, so we will make our custom main function by sacrificing a virgin to satan (j.k. we will search for __libc_start_main definition on Google). After we do that, we can start writing our custom library.
After we wrote our custom main function (which looks very ugly btw):

we now need to compile it and make it a shared library, here is a procedure how to do that. We now need to make a symbolic link from our library with the real name libc.so.6.0 to the directory specified by RPATH (/var/tmp/flag15) and name it libc.so.6. Now we can try and start our program.

Oops! We’ve got a bunch of errors. After a bit of googling, I found that we need to make the shared library in a way that includes a version script. Follow the steps in the link and make one. Now make a shared library with the following commands:
-
gcc -g -c -fPIC -Wall <file.c>
-
gcc -shared -Wl,--version-script,<scriptname>,-soname,<your_soname> -o <library_name> <file.o> -lc
After we recompile and once again make a new shared library, lets start our program again.

Errors again… Jesus… I remember from somewhere that you can use the environment variable LD_DEBUG for debugging the program and see the program flow, syntax is:
LD_DEBUG=all <executable_name>

Another version missing, wtf m8?! I recompiled the library once more with added version script for GLIBC_2.1.3, run it again and got another error, after debugging with LD_PRELOAD i get:

After reading the dlopen man page: “If the library has dependencies on other shared libraries, then these are also automatically loaded by the dynamic linker using the same rules. (This process may occur recursively, if those libraries in turn have dependencies, and so on.)“. Now, we can see that our library has dependencies on other shared libraries, that is the same libc.so.6 library. dlopen man page also says: “If the same library is loaded again with dlopen(), the same file handle is returned“, so we are stuck with a recursive error because we get the same file handle again. The problem is that our libc.so.6 library calls system() in our custom made main function, and the system() function is not defined in the library that libc.so.6 wants to resolve it from (that is the same libc.so.6 library), or, the library shares the same name as ours, but the hard-coded RPATH won’t allow the program to find the right libc.so.6 where it can pull the definitions of the missing symbols (I’m not sure about this one but it seems logical). This was the biggest problem in this level, but I managed to find the solution.
THIS IS IMPORTANT !
The solution: Instead of trying to link with a shared library, we need to make our shared library in a way that it links with the static standard c library that linux offers. The reason why is that when libc.so.6 says: “oh, i don’t have the system() function call, I need to find it somewhere else!”, that “somewhere else” won’t be the libc.so.6 again, instead it will be the statically linked standard C library that Linux provides us!
After checking with ld (linker) man page I foud the -Bstatic option: “Do not link against shared libraries. This is only meaningful on platforms for which shared libraries are supported. The different variants of this option are for compatibility with various systems. You may use this option multiple times on the command line: it affects library searching for -l options which follow it. This option also implies –unresolved-symbols=report-all. This option can be used with -shared. Doing so means that a shared library is being created but that all of the library’s external references must be resolved by pulling in entries from static libraries.“.
This time do the compilation like this:
-
gcc -g -c -fPIC -Wall <file.c>
-
gcc -shared -static-libgcc -Wl,--version-script,<scriptname>,-Bstatic,-soname,<your_soname> -o <library_name> <file.o> -lc
After you made your shared library, after you made a symbolic link from your real library name to your library soname and after you copied that soname in /var/tmp/flag15 directory, you can finall start the program again.

We did it! We actually did it! This bloody mess is finally over!