Nebula Shell Exploits (Solutions 15-19)
Shell-based exploit exercises
Published on 17 August 2012Overview
This is third and final post of my solutions for Exploit Exercises. This comes about a month after finishing 10-14 – I was distracted by some other projects.
Level 15
Description (full): strace
on the binary reveals that it searches for libraries based on hardware capabilities.
We want to either substitute our own version of libc, overriding the puts()
call in flag15
, or we want to execute code in the process. While the hard part isn’t creating our own shared library, we have to figure out how to prevent libc
from being linked into our library. This solution executes code in the makeshift libc.so
.
~/version
: preventing an error about requiring GLIBC_2.0
:
~/run.sh
: A makeshift Makefile that also executes our code.
~/getflag.c
: another interpretation of “libc,” but it happens to call execv
.
Level 16
Description (full): A Perl script running on port 1616 runs egrep
with user input. It has some red herrings about a username and a password, but the goal is to execute arbitrary code.
This solution uses null byte injection to execute arbitrary commands on the target account.
There are two parts to the solution:
- The Perl script converts all input to uppercase. We work around this by using a wildcard match, searching for
/tmp/RUN/
(our script) as/*/RUN
. - The Perl string doesn’t immediately allow us to execute arbitrary code. We have to terminate the string correctly with the right combination of
"
, “\
", and
%00`. The last one is the null character in a URL.
Level 17
Description (full): A Python script running on port 10007 loads pickled data from input.
The vulnerability of the Python pickle
module is well-documented. The Python docs say:
Warning: The pickle module is not intended to be secure against erroneous or maliciously constructed data. Never unpickle data received from an untrusted or unauthenticated source
This article gives us an example of malicious Pickle data.
Our pickled “data” goes in ~/cmd
:
We send the pickled data to the listening script.
Interpreting Pickled data
I was curious about the parts of the malicious pickle. I searched first for the pickle
format specification, but I wasn’t able to find it. I was a bit surprised that I was only able to find one article related to the semantics of the pickle format. Part of our input is explained:
The ‘(‘ is simply a marker. It is a object in the stack that tells the tuple builder, ‘t’, when to stop. The tuple builder pops items from the stack until it reaches a marker. Then, it creates a tuple with these items and pushes this tuple back on the stack. You can use multiple markers to construct a nested tuple…
Level 18
Description (full): flag18
is a program that mimics a login shell with various options such as login
, logout
, shell
(see full description). The flag directory contains an unreadable password file.
Discussion
This problem was more complex than the others, given the number of options provided initially.
There are flags on flag19
for a debug file and a verbose level. Using -d /dev/tty
saves us some effort.
If we iterate through the possible commands, we can rule out some paths.
- There’s no apparent path for dumping the contents of the password file through the code.
- The
notsupported()
andsetuser()
functions seem to deal with strings and buffers. These are potential solutions, but from a metagame perspective, Nebula solutions use shell exploits, not memory exploits.
We can confirm this by trying playing around with buffer overflows and format strings:
Solution
if(fp)
will fail if the file can’t be opened for any reason. Since the files opened by login
are never closed, we can open files until we reach the maximum number of file descriptors. fp
will then be NULL
once the maximum number is reached. Finally, we use closelog
to free a file descriptor.
If closelog
isn’t called, execve("/bin/sh", ...)
will fail with an error loading a shared library libncurses.so.5
.
The initial attempt looks like this:
sh
doesn’t have a -d
flag. The author left a hint to look at the options in the man page; sh
needs an option that ignores the input afterwards.
This actually opens a promptless shell reading from stdin.
The password doesn’t seem to work for logging into the flag18
account, but the shell can execute getflag
.
Level 19
Description (full): The flag19
executable checks if the root user started the process. If so, then it runs execve
on /bin/sh
.
CS61 Lecture Notes (Processes) was particularly valuable here. I won’t end up taking the class, but I should show my appreciation for the lecture notes – they’re all very well made.
We want to start flag19
in an orphan process. An orphan process is claimed by the program init
(PID 1), which is owned by root (UID 0). The plan looks like:
- Use
fork()
to create a child process, exit the parent process, andsleep()
to create an orphan. - Execute
/home/flag19/flag19
with our arguments Thestat
should complete successfully, as we control the time of check.
Tangent
I ran into an interesting issue caused by laziness (well, in reality, a desire for flexibility).
The code below does not run execve
as suid. It runs it as the user level19
instead of flag19
, which may have been caused by passing down envp
.
This meant that I could conveniently pass arguments through the executable:
Unfortunately, it also meant that getflag
wouldn’t execute on the correct user.