Nebula Shell Exploits (Solutions 10-14)
Shell-based exploit exercises
Published on 28 June 2012Overview
This is the second post for my solutions of Exploit Exercises
Level 10
Description (full): Read a token file with the password to the target account. A suid program uses access()
to upload a file to a host.
According to man access(), there’s a race condition with this usage of the function:
Now, we can run this script repeatedly:
On our local machine (Linux or Mac), we also listen at intervals with netcat
:
On the virtual machine:
Level 11
Description (full): Execute getflag
with the given source code.
This solution should be examined with the source code (gist).
This level was great. These are the realizations that lead to a solution:
- The solution path must end with the
system()
call inprocess()
. - There are two ways to reach
process()
via the if-else branch inmain()
. - The
else
branch is extremely random. It usesmmap(NULL…)
(maps to a random memory address),getrand()
(returns a random file descriptor), and XOR encryption. - The
if
branch isif (fread(buf, length, 1, stdin) != length)
. The third argument tofread
is the number of members to read. The return value is the number of members read. - Things just got a lot simpler, since
length
must be 1. process()
uses basic XOR encryption with the caveat that the key changes for each letter in the buffer. The buffer only has one letter, andkey = 1 & 0xff
, which flips the last bit.- Looking at the ASCII table, if we want the final buffer to contain
b
(01100010
), the key needs to be applied to01100011
(c
). - Add an executable named
b
to the path, and let the program execute it until the buffer, by chance, ends with the string-terminating null byte.
While walking to work, I laughed (in advance) thinking that the problem would have a deceivingly simple solution – and it did.
Level 12
Description (full): Access the flag account through a Lua script that seems to send the token with a certan SHA1 checksum.
According to the Internet, SHA1 checksums are, for all practical purposes, irreversible. This means that the solution path probably doesn’t require us to find the unhashed input.
This turns out to be the case. We execute arbitrary code via this line, where our input is the variable ..password..
:
Level 13
Description (full): A program returns the token if the real uid is equal to FAKEUID
, defined by a preprocessor macro.
Given the source code, there seem to be two points of vulnerability for the program.
- The call to
getuid()
: can we substitute our own version of the function? - The preprocessor macro for
FAKEUID
: can we redefineFAKEUID
?
Overriding getuid()
We link a shared library with our own function definition of getuid()
, adding it through the LD_PRELOAD
environmental variable. Because suid
programs ignore LD_PRELOAD
, we copy the binary to our own directory and modify the permissions.
I wasn’t familiar with these shared library vulnerabilities before these levels. To cite my sources, here are posts that I found during my search for “overriding functions”:
- Overriding functions in C, Stack Overflow.
- Course notes for suid exploits, Syracuse University
Redefining fakeuid
Unfortunately, it turns out that we can’t quite “redefine” the preprocessor macro, since the binary is compiled without any debug flags from gcc
.
FAKEUID
is defined by 1000 (hex: 0x03e8
). We can take a look at the assembly using objdump
.
Our magic number is represented by 3d e8
because of the little-endian architecture. Our goal is to change 0x3e8
to 0x3f6
, which is 1014 in binary.
vim can be used to edit binary files. (I’m an emacs user, but vim comes with the VM). We change 3de8
to 3df6
in the binary.
Level 14
Description (full): flag14 -e
encrypts stdin. A token file needs to be decrypted.
If we play around more, there seems to be a simple pattern: the encryption scheme increments each character by its index.