Ti Kallisti
OverTheWire "Leviathan" Write-Up
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:~$ strings check
/lib/ld-linux.so.2
libc.so.6
_IO_stdin_used
puts
[...]
PTRhp
QVh;
secrf
love
UWVS
t$,U
[^_]
[...]
That "love" there seems a bit like the odd one out. With our advanced Hackers trivia knowledge, we know that "love", "secret", "sex" and "god" are the most commonly used passwords. Let's try it out:
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
- Get a basic understanding of the C programming language
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:
(gdb) x/4xw $esp
0xffffd680: 0xffffd6ac 0xffffd6a8 0xffffd6b8 0x0804859c
We examine the first 4 hex words (word = 4 bytes) at the memory address where the stack pointer (esp) is pointing to.
Now lets examine the strings at the first two addresses, as those are the ones passed to the strcmp function:
(gdb) x/s 0xffffd6ac
0xffffd6ac: "tes"
(gdb) x/s 0xffffd6a8
0xffffd6a8: "sex"
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:
leviathan2@leviathan:~$ ./printfile
*** File Printer ***
Usage: ./printfile filename
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...
Hmmm. Let's take a look at the code:
leviathan2@leviathan:~$ gdb ./printfile
(gdb) set disassembly-flavor intel
(gdb) disass main
Dump of assembler code for function main:
0x0804852b <+0>: lea ecx,[esp+0x4]
0x0804852f <+4>: and esp,0xfffffff0
0x08048532 <+7>: push DWORD PTR [ecx-0x4]
0x08048535 <+10>: push ebp
0x08048536 <+11>: mov ebp,esp
0x08048538 <+13>: push ebx
0x08048539 <+14>: push ecx
0x0804853a <+15>: sub esp,0x200
0x08048540 <+21>: mov ebx,ecx
0x08048542 <+23>: cmp DWORD PTR [ebx],0x1
0x08048545 <+26>: jg 0x8048577
0x08048547 <+28>: sub esp,0xc
0x0804854a <+31>: push 0x8048690
0x0804854f <+36>: call 0x80483c0 <puts@plt>
0x08048554 <+41>: add esp,0x10
0x08048557 <+44>: mov eax,DWORD PTR [ebx+0x4]
0x0804855a <+47>: mov eax,DWORD PTR [eax]
0x0804855c <+49>: sub esp,0x8
0x0804855f <+52>: push eax
0x08048560 <+53>: push 0x80486a5
0x08048565 <+58>: call 0x80483a0 <printf@plt>
0x0804856a <+63>: add esp,0x10
0x0804856d <+66>: mov eax,0xffffffff
0x08048572 <+71>: jmp 0x80485fa <main+207>
0x08048577 <+76>: mov eax,DWORD PTR [ebx+0x4]
0x0804857a <+79>: add eax,0x4
0x0804857d <+82>: mov eax,DWORD PTR [eax]
0x0804857f <+84>: sub esp,0x8
0x08048582 <+87>: push 0x4
0x08048584 <+89>: push eax
0x08048585 <+90>: call 0x8048410 <access@plt>
0x0804858a <+95>: add esp,0x10
0x0804858d <+98>: test eax,eax
0x0804858f <+100>: je 0x80485a8 <main+125>
0x08048591 <+102>: sub esp,0xc
0x08048594 <+105>: push 0x80486b9
0x08048599 <+110>: call 0x80483c0 <puts@plt>
0x0804859e <+115>: add esp,0x10
0x080485a1 <+118>: mov eax,0x1
0x080485a6 <+123>: jmp 0x80485fa <main+207>
0x080485a8 <+125>: mov eax,DWORD PTR [ebx+0x4]
0x080485ab <+128>: add eax,0x4
0x080485ae <+131>: mov eax,DWORD PTR [eax]
0x080485b0 <+133>: push eax
0x080485b1 <+134>: push 0x80486d4
0x080485b6 <+139>: push 0x1ff
0x080485bb <+144>: lea eax,[ebp-0x208]
0x080485c1 <+150>: push eax
0x080485c2 <+151>: call 0x8048400 <snprintf@plt>
0x080485c7 <+156>: add esp,0x10
0x080485ca <+159>: call 0x80483b0 <geteuid@plt>
0x080485cf <+164>: mov ebx,eax
0x080485d1 <+166>: call 0x80483b0 <geteuid@plt>
0x080485d6 <+171>: sub esp,0x8
0x080485d9 <+174>: push ebx
0x080485da <+175>: push eax
0x080485db <+176>: call 0x80483e0 <setreuid@plt>
0x080485e0 <+181>: add esp,0x10
0x080485e3 <+184>: sub esp,0xc
0x080485e6 <+187>: lea eax,[ebp-0x208]
0x080485ec <+193>: push eax
0x080485ed <+194>: call 0x80483d0 <system@plt>
0x080485f2 <+199>: add esp,0x10
[...]
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:
leviathan3@leviathan:~$ ls -la
total 32
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 leviathan4 leviathan3 10288 Aug 26 22:26 level3
-rw-r--r-- 1 root root 675 May 15 2017 .profile
leviathan3@leviathan:~$ ./level3
Enter the password> abcde
bzzzzzzzzap. WRONG
leviathan3@leviathan:~$ gdb ./level3
(gdb) set disassembly-flavor intel
(gdb) disass main
Dump of assembler code for function main:
0x08048618 <+0>: lea ecx,[esp+0x4]
0x0804861c <+4>: and esp,0xfffffff0
0x0804861f <+7>: push DWORD PTR [ecx-0x4]
0x08048622 <+10>: push ebp
0x08048623 <+11>: mov ebp,esp
0x08048625 <+13>: push ecx
0x08048626 <+14>: sub esp,0x34
0x08048629 <+17>: mov DWORD PTR [ebp-0x13],0x626d6f62
0x08048630 <+24>: mov WORD PTR [ebp-0xf],0x6461
0x08048636 <+30>: mov BYTE PTR [ebp-0xd],0x0
0x0804863a <+34>: mov DWORD PTR [ebp-0x1d],0x732e2e2e
0x08048641 <+41>: mov DWORD PTR [ebp-0x19],0x33726333
0x08048648 <+48>: mov WORD PTR [ebp-0x15],0x74
0x0804864e <+54>: mov DWORD PTR [ebp-0x24],0x6f6e3068
0x08048655 <+61>: mov WORD PTR [ebp-0x20],0x3333
0x0804865b <+67>: mov BYTE PTR [ebp-0x1e],0x0
0x0804865f <+71>: mov DWORD PTR [ebp-0x2b],0x616b616b
0x08048666 <+78>: mov WORD PTR [ebp-0x27],0x616b
0x0804866c <+84>: mov BYTE PTR [ebp-0x25],0x0
0x08048670 <+88>: mov DWORD PTR [ebp-0x35],0x2e32332a
0x08048677 <+95>: mov DWORD PTR [ebp-0x31],0x785b2a32
0x0804867e <+102>: mov WORD PTR [ebp-0x2d],0x5d
0x08048684 <+108>: sub esp,0x8
0x08048687 <+111>: lea eax,[ebp-0x2b]
0x0804868a <+114>: push eax
0x0804868b <+115>: lea eax,[ebp-0x24]
0x0804868e <+118>: push eax
0x0804868f <+119>: call 0x80483d0 <strcmp@plt>
0x08048694 <+124>: add esp,0x10
0x08048697 <+127>: test eax,eax
0x08048699 <+129>: jne 0x80486a2 <main+138>
0x0804869b <+131>: mov DWORD PTR [ebp-0xc],0x1
0x080486a2 <+138>: sub esp,0xc
0x080486a5 <+141>: push 0x804877f
0x080486aa <+146>: call 0x80483e0 <printf@plt>
0x080486af <+151>: add esp,0x10
0x080486b2 <+154>: call 0x804855b <do_stuff>
0x080486b7 <+159>: mov eax,0x0
0x080486bc <+164>: mov ecx,DWORD PTR [ebp-0x4]
0x080486bf <+167>: leave
0x080486c0 <+168>: lea esp,[ecx-0x4]
0x080486c3 <+171>: ret
End of assembler dump.
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:
(gdb) disass do_stuff
Dump of assembler code for function do_stuff:
0x0804855b <+0>: push ebp
0x0804855c <+1>: mov ebp,esp
0x0804855e <+3>: push ebx
0x0804855f <+4>: sub esp,0x114
0x08048565 <+10>: mov DWORD PTR [ebp-0x113],0x706c6e73
0x0804856f <+20>: mov DWORD PTR [ebp-0x10f],0x746e6972
0x08048579 <+30>: mov WORD PTR [ebp-0x10b],0xa66
0x08048582 <+39>: mov BYTE PTR [ebp-0x109],0x0
0x08048589 <+46>: mov eax,ds:0x804a040
0x0804858e <+51>: sub esp,0x4
0x08048591 <+54>: push eax
0x08048592 <+55>: push 0x100
0x08048597 <+60>: lea eax,[ebp-0x108]
0x0804859d <+66>: push eax
0x0804859e <+67>: call 0x80483f0 <fgets@plt>
0x080485a3 <+72>: add esp,0x10
0x080485a6 <+75>: sub esp,0x8
0x080485a9 <+78>: lea eax,[ebp-0x113]
0x080485af <+84>: push eax
0x080485b0 <+85>: lea eax,[ebp-0x108]
0x080485b6 <+91>: push eax
0x080485b7 <+92>: call 0x80483d0 <strcmp@plt>
0x080485bc <+97>: add esp,0x10
0x080485bf <+100>: test eax,eax
0x080485c1 <+102>: jne 0x80485fe <do_stuff+163>
0x080485c3 <+104>: sub esp,0xc
0x080485c6 <+107>: push 0x8048750
0x080485cb <+112>: call 0x8048410 <puts@plt>
0x080485d0 <+117>: add esp,0x10
0x080485d3 <+120>: call 0x8048400 <geteuid@plt>
0x080485d8 <+125>: mov ebx,eax
0x080485da <+127>: call 0x8048400 <geteuid@plt>
0x080485df <+132>: sub esp,0x8
0x080485e2 <+135>: push ebx
0x080485e3 <+136>: push eax
0x080485e4 <+137>: call 0x8048430 <setreuid@plt>
0x080485e9 <+142>: add esp,0x10
0x080485ec <+145>: sub esp,0xc
0x080485ef <+148>: push 0x8048764
0x080485f4 <+153>: call 0x8048420 <system@plt>
0x080485f9 <+158>: add esp,0x10
0x080485fc <+161>: jmp 0x804860e <do_stuff+179>
0x080485fe <+163>: sub esp,0xc
0x08048601 <+166>: push 0x804876c
0x08048606 <+171>: call 0x8048410 <puts@plt>
0x0804860b <+176>: add esp,0x10
0x0804860e <+179>: mov eax,0x0
0x08048613 <+184>: mov ebx,DWORD PTR [ebp-0x4]
0x08048616 <+187>: leave
0x08048617 <+188>: ret
End of assembler dump.
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:
leviathan5@leviathan:~$ gdb ./leviathan5
(gdb) set disassembly-flavor intel
(gdb) disass main
Dump of assembler code for function main:
0x080485db <+0>: lea ecx,[esp+0x4]
0x080485df <+4>: and esp,0xfffffff0
0x080485e2 <+7>: push DWORD PTR [ecx-0x4]
0x080485e5 <+10>: push ebp
0x080485e6 <+11>: mov ebp,esp
0x080485e8 <+13>: push ecx
0x080485e9 <+14>: sub esp,0x14
0x080485ec <+17>: sub esp,0x8
0x080485ef <+20>: push 0x8048720
0x080485f4 <+25>: push 0x8048722
0x080485f9 <+30>: call 0x8048490 <fopen@plt>
0x080485fe <+35>: add esp,0x10
0x08048601 <+38>: mov DWORD PTR [ebp-0xc],eax
0x08048604 <+41>: cmp DWORD PTR [ebp-0xc],0x0
0x08048608 <+45>: jne 0x8048624 <main+73>
0x0804860a <+47>: sub esp,0xc
0x0804860d <+50>: push 0x8048730
0x08048612 <+55>: call 0x8048450 <puts@plt>
0x08048617 <+60>: add esp,0x10
0x0804861a <+63>: sub esp,0xc
0x0804861d <+66>: push 0xffffffff
0x0804861f <+68>: call 0x8048460 <exit@plt>
0x08048624 <+73>: sub esp,0xc
0x08048627 <+76>: push DWORD PTR [ebp-0xc]
0x0804862a <+79>: call 0x80484b0 <fgetc@plt>
0x0804862f <+84>: add esp,0x10
0x08048632 <+87>: mov BYTE PTR [ebp-0xd],al
0x08048635 <+90>: sub esp,0xc
0x08048638 <+93>: push DWORD PTR [ebp-0xc]
0x0804863b <+96>: call 0x8048470 <feof@plt>
0x08048640 <+101>: add esp,0x10
0x08048643 <+104>: test eax,eax
0x08048645 <+106>: jne 0x8048659 <main+126>
0x08048647 <+108>: movsx eax,BYTE PTR [ebp-0xd]
0x0804864b <+112>: sub esp,0xc
0x0804864e <+115>: push eax
0x0804864f <+116>: call 0x80484a0 <putchar@plt>
0x08048654 <+121>: add esp,0x10
0x08048657 <+124>: jmp 0x8048624 <main+73>
0x08048659 <+126>: nop
0x0804865a <+127>: sub esp,0xc
0x0804865d <+130>: push DWORD PTR [ebp-0xc]
0x08048660 <+133>: call 0x8048420 <fclose@plt>
0x08048665 <+138>: add esp,0x10
0x08048668 <+141>: call 0x8048430 <getuid@plt>
0x0804866d <+146>: sub esp,0xc
0x08048670 <+149>: push eax
0x08048671 <+150>: call 0x80484c0 <setuid@plt>
0x08048676 <+155>: add esp,0x10
0x08048679 <+158>: sub esp,0xc
0x0804867c <+161>: push 0x8048722
0x08048681 <+166>: call 0x8048440 <unlink@plt>
0x08048686 <+171>: add esp,0x10
0x08048689 <+174>: mov eax,0x0
0x0804868e <+179>: mov ecx,DWORD PTR [ebp-0x4]
0x08048691 <+182>: leave
0x08048692 <+183>: lea esp,[ecx-0x4]
0x08048695 <+186>: ret
End of assembler dump.
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:
leviathan5@leviathan:~$ ln -s /etc/leviathan_pass/leviathan6 /tmp/file.log
leviathan5@leviathan:~$ ./leviathan5
UgaoFee4li
We got that!
Level 6
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.
leviathan6@leviathan:~$ gdb ./leviathan6
(gdb) set disassembly-flavor intel
(gdb) disass 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,0x10
0x0804854d <+18>: mov eax,ecx
0x0804854f <+20>: mov DWORD PTR [ebp-0xc],0x1bd3
0x08048556 <+27>: cmp DWORD PTR [eax],0x2
0x08048559 <+30>: je 0x804857b <main+64>
0x0804855b <+32>: mov eax,DWORD PTR [eax+0x4]
0x0804855e <+35>: mov eax,DWORD PTR [eax]
0x08048560 <+37>: sub esp,0x8
0x08048563 <+40>: push eax
0x08048564 <+41>: push 0x8048660
0x08048569 <+46>: call 0x80483b0 <printf@plt>
0x0804856e <+51>: add esp,0x10
0x08048571 <+54>: sub esp,0xc
0x08048574 <+57>: push 0xffffffff
0x08048576 <+59>: call 0x80483f0 <exit@plt>
0x0804857b <+64>: mov eax,DWORD PTR [eax+0x4]
0x0804857e <+67>: add eax,0x4
0x08048581 <+70>: mov eax,DWORD PTR [eax]
0x08048583 <+72>: sub esp,0xc
0x08048586 <+75>: push eax
0x08048587 <+76>: call 0x8048420 <atoi@plt>
0x0804858c <+81>: add esp,0x10
0x0804858f <+84>: cmp eax,DWORD PTR [ebp-0xc]
0x08048592 <+87>: jne 0x80485bf <main+132>
0x08048594 <+89>: call 0x80483c0 <geteuid@plt>
0x08048599 <+94>: mov ebx,eax
0x0804859b <+96>: call 0x80483c0 <geteuid@plt>
0x080485a0 <+101>: sub esp,0x8
0x080485a3 <+104>: push ebx
0x080485a4 <+105>: push eax
0x080485a5 <+106>: call 0x8048400 <setreuid@plt>
0x080485aa <+111>: add esp,0x10
0x080485ad <+114>: sub esp,0xc
0x080485b0 <+117>: push 0x804867a
0x080485b5 <+122>: call 0x80483e0 <system@plt>
0x080485ba <+127>: add esp,0x10
0x080485bd <+130>: jmp 0x80485cf <main+148>
0x080485bf <+132>: sub esp,0xc
0x080485c2 <+135>: push 0x8048682
0x080485c7 <+140>: call 0x80483d0 <puts@plt>
0x080485cc <+145>: add esp,0x10
0x080485cf <+148>: mov eax,0x0
0x080485d4 <+153>: lea esp,[ebp-0x8]
0x080485d7 <+156>: pop ecx
0x080485d8 <+157>: pop ebx
0x080485d9 <+158>: pop ebp
0x080485da <+159>: lea esp,[ecx-0x4]
0x080485dd <+162>: ret
End of assembler dump.
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.