Nebula Shell Exploits (Solutions 15-19)
Shell-based exploit exercisesPublished on 17 August 2012
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.
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
~/version: preventing an error about requiring
~/run.sh: A makeshift Makefile that also executes our code.
~/getflag.c: another interpretation of “libc,” but it happens to call
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
- 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.
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
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…
flag18 is a program that mimics a login shell with various options such as
shell (see full description). The flag directory contains an unreadable password file.
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.
setuser()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:
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.
closelog isn’t called,
execve("/bin/sh", ...) will fail with an error loading a shared library
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
Description (full): The
flag19 executable checks if the root user started the process. If so, then it runs
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:
fork()to create a child process, exit the parent process, and
sleep()to create an orphan.
/home/flag19/flag19with our arguments The
statshould complete successfully, as we control the time of check.
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
This meant that I could conveniently pass arguments through the executable:
Unfortunately, it also meant that
getflag wouldn’t execute on the correct user.