OverTheWire - Leviathan
Introduction
Leviathan is another wargame available at overthewire.org.
In this walkthrough I'm going to show the process for solving each challenge while also providing some insight into what each challenge is trying to teach and why it's useful. Keep an eye out for callouts like those below.
Access to each level is made over SSH. The username will correspond to the index of the level starting at 0. Always make sure you're logging into the correct level!
If you're new to CTFs and wargames you may want to check out the Bandit wargame walkthrough first.
None of the Leviathan levels have any additional descriptions. Presumably, since they're relatively simple, any hints would probably give away the solution. Instead you're dropped into a regular user session after login with the task of identifying any important files available to that user and using them to find the password to the next level.
Walkthrough
Level 0
To start, there only appears to be a few hidden dotfiles in the home directory.
The only atypical thing here is the ~/.backup
directory which contains a
bookmarks.html
file.
Solution
It's certainly possible to manually read through the bookmarks for anything
interesting, but grep
can be utilized to search for some keywords which might
be useful. For example, "password" or "flag".
This example is a bit contrived for the sake of the wargame, but this is a
common tactic used by attackers. Searching a user's files with grep
for
things like usernames, passwords, bank or credit card details, and any other
kind of sensitive information is one of the first things an attacker might do
once they've compromised a user's account.
It should go without saying that you should never store any passwords or other sensitive information in plain text files if it can be avoided. Ideally use a password manager with secure encryption to store any digital account details.
Level 1
This level presents us with a setuid
binary. On some systems this is evident by the red background color, but to
verify, you just need to check the file permissions with ls -l
. There will be
an s
in place of the typical x
for the user level execution permissions.
After running it with an input of abc
, it's clear from the output that the
input is being checked against some password string for correctness.
We can do some preliminary analysis of the binary's execution with the
ltrace
program
which will record the C level dynamic library function calls made by check
.
This includes common standard library functions like
strcmp
.
Solution
From the output of the above ltrace
, we can see the parameters passed to the
strcmp
function call. The first is our test password, and the second is the
target password that check
is using to verify the input.
The ltrace
program can be quite useful to perform some initial analysis of a
binary's execution before transitioning to more advanced tools like the gdb
debugger.
Level 2
Here's another setuid
binary.
Executing prints from usage information to the console.
Following the usage description, a file path may be passed as an argument and be printed to the console.
Unfortunately, printing the level3
password file isn't allowed.
To investigate, let's use the ltrace
command again.
The ltrace
output shows calls to
access
,
snprintf
, and
system
.
Attempting to read another file for which we have read permissions, e.g.
.bashrc
, shows that printfile
executes the shell command /bin/cat <FILEPATH>
when the access
function call succeeds where <FILEPATH>
is
whatever you pass as the first argument to printfile
.
The above output was truncated since the .bashrc
file is actually quite long.
In fact, you might want to redirect stdout to /dev/null
to only display the
results from ltrace
. Just to make it a bit more readable.
The setuid bit
We saw this in the previous level as well, but I wanted to call attention to
the fact that printfile
has the setuid
bit set.
We can even see the function call to setreuid
which will set the real and
effective user ID for the process. This level and the last are showcasing the
dangers of giving any file setuid permissions. Without it printfile
wouldn't
be able to access the password file for level 3.
Our goal is to craft some input to printfile
such that the system
call will
execute for the password file (/etc/leviathan_pass/leviathan3
). But, to reach
that point, the path passed in must also be readable by the leviathan2
user.
With that in mind, one method we can use to trick printfile
is by providing a
path with a variable. This way the variable will be expanded when the path is
passed to the system
function, but will be interpreted literally by the call
to access
.
When attacking the input of a program, always try to identify different points
of entry. In this case there are two points at which the input path is passed
into the program. The first is to the call to access
and the second to
system
. Both of these functions process input differently. When you have
multiple points like this without some sort of input validation or
normalization, there's always an opportunity to disrupt the expected behavior
of the program.
On the flip-side of this, as a developer this showcases why input validation is such an important concept. Never implicitly trust user input.
More specifically for this level, system
should never be used in a setuid
program and user input should never be passed directly to a call to system
without sanitization. The system
manual
page explicitly warns
against this practice due to the potential for compromising system security.
Solution
Level 3
This challenge is almost identical to level 1, except that you are
dropped into a shell where you can easily cat
the password file for level 4.
The intended solution for level 1 probably differs slightly from the one provided here since having near duplicate solutions usually isn't done by wargame and CTF authors.
Either way, this one is left as a challenge to the reader. Have fun!
Level 4
In this level, we have some more hidden files. In the .trash
directory is
another setuid binary.
Solution
All the leviathan challenges are possible without any programming, but in this case, you could also solve it with a short python script.
print(
"".join(
[chr(int(x, 2)) for x in "00110000 01100100 01111001 01111000 01010100 00110111 01000110 00110100 01010001 01000100 00001010".split()]
)
)
This script splits the text into a list of binary numbers and converts each
binary number into decimal with int(x, 2)
. Each integer is then translated
into the corresponding ASCII letter with the chr
function before joining each
letter back into a single string.
Of course it's also possible to translate it by hand with an ASCII table though you'll need to convert the binary numbers into either hexadecimal or decimal. Another option is to use one of the many online translators, for example Binary to Text Translator.
Level 5
It looks like the binary expects a file at /tmp/file.log
. Let's make that
file with some dummy content and ltrace
the binary to see what's going on.
We have two important things going on in this binary.
- The contents of the
/tmp/file.log
are being printed. /tmp/file.log
is deleted. See theunlink
manual page for details.
The deletion of the file is less of a concern since we can remake the file as needed. More importantly, we have control of the file being printed.
Solution
Obviously we don't care too much about the content of /tmp/file.log
. But,
what if, we could link /tmp/file.log
to a more interesting file. Like
/etc/leviathan_pass/leviathan6
for example.
Here we've simply made a symbolic link to /etc/leviathan_pass/leviathan6
and
placed the link file at /tmp/file.log
. When leviathan5
is executed, the
link is followed to the password file for the next level which is printed to
stdout.
Level 6
Hopefully you've gotten the hang of the first few steps now.
- Check for interesting files in the home directory
- Execute the binary and provide any required input
- Run the binary via
ltrace
to see what function calls it's making
Blast it, this time they've managed to foil our use of ltrace
. There's
clearly some sort of check going on to verify the input code. We can probably
assume the check is happening in a simple if
statement comparing the input
code to some hardcoded number. Which would explain why it doesn't show up in
ltrace
since no library function call is being made.
At this point we're reaching beyond the capabilities of most basic Linux
commands. Instead you may have to reach for something like
gdb
, the GNU
Debugger. The debugger will allow you to step through the program one
instruction at a time. Unfortunately it does require you understand a bit about
C and Assembly language.
In the disassembly of leviathan6
above there are a few important lines.
- At line
main+76
we can see the function call toatoi
which you should recognize from the preliminary analysis withltrace
. Another important concept to understand is that for x86 binaries like this one, the return results of functions are usually assigned to theeax
register. - A
cmp
instruction which is comparing the returned value of theatoi
function call ineax
to another value on the stack at[ebp-0xc]
.
Use the run
command to start the program. The program will begin executing
and stop at the first breakpoint it finds.
After the program stops, we can examine the arguments of the cmp
instruction.
The command info registers
will display the values for all the registers, or
the target register can be passed as an argument as shown below.
The format for the register listing is register name | hexadecimal | decimal
.
You can see the decimal representation matches the PIN provided to the program
1234
.
The other argument to cmp
is a bit trickier. It's actually using a pointer.
Meaning the value at ebp - 0xc
is actually a memory address and it must be
dereferenced to examine the value. The x
command can be used to examine
memory like this. You can read more in the Examining Memory
section of
the GDB
documentation,
but, in summary, the command used here (x/1d $ebp-0xc
) examines the memory at
the address computed by subtracting 0xc
from the value in the ebp
register.
One integer value at the resolved memory address is then printed to the console
thanks to the display format of 1d
.
Conclusion
You can do quite a lot with just some basic terminal commands.
Supposedly all the Leviathan levels are possible without any programming knowledge, so I suspect level 6 has a simpler intended solution.
Either way, know your tools. I think ltrace
in particular is useful for
understanding the actions of a binary without needing to resort to tools for
debugging and reverse engineering like gdb or Ghidra.
Don't forget to clean up any files you've created.
rm -rf /tmp/MY_TEMP_DIRECTORY