Jason Turley's Website

OverTheWire: Leviathan Writeup


Leviathan is a wargame from overthewire. It mainly focuses on Unix commands and reverse engineering. I have redacted the answers, replacing them with XXXXXXXX instead. I encourage readers to try and solve the exercises on their own before reading this writeup.

Level 0

The password for the first level is provided on the intro page. ssh into leviathan0@leviathan.labs.overthewire.org.

Level 0 –> 1

There is a file named .backup/bookmarks.html. I opened it in vim and searched for all occurrences of the word “password” with :/password

This will be fixed later, the password for leviathan1 is XXXXXXXX"

Level 1 –> 2

There is a binary called check that will print the flag if the correct password is entered.

leviathan1@leviathan:~$ file check
check: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID\[sha1\]=c735f6f3a3a94adcad8407cc0fda40496fd765dd, not stripped
leviathan1@leviathan:~$ ./check
password: hello
Wrong password, Good Bye ...

Looks like “hello” was incorrect.

Next, I ran strings ./check to see if it would reveal the correct password.

I wanted to take a closer look at the binary, so I fired up gdb:

0x0804857f <+68>:    call   0x80483c0 <printf@plt>
0x08048584 <+73>:    add    esp,0x10
0x08048587 <+76>:    call   0x80483d0 <getchar@plt>
0x0804858c <+81>:    mov    BYTE PTR \[ebp-0xc\],al
0x0804858f <+84>:    call   0x80483d0 <getchar@plt>
0x08048594 <+89>:    mov    BYTE PTR \[ebp-0xb\],al
0x08048597 <+92>:    call   0x80483d0 <getchar@plt>
0x0804859c <+97>:    mov    BYTE PTR \[ebp-0xa\],al
0x0804859f <+100>:   mov    BYTE PTR \[ebp-0x9\],0x0
-- redacted for readability --

The printf call prints the password: prompt seen earlier. The three calls to getchar store the first three characters we entered into a buffer. The buffer is null terminated with 0x0 on line <+100>. Continuing on:

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>
-- redacted for readability --

Our input is stored in ebp-0xc and the correct password is in ebp-0x10. The strcmp call checks to see if the passwords match.

0xffffd69c:     "hel"
(gdb) x/s $ebp-0x10
0xffffd698:     XXX

Run the check binary again and enter the correct password to get a shell:

leviathan1@leviathan:~$ ./check
password: XXX
$ whoami
$ cat /etc/leviathan\_pass/leviathan2

Level 2 –> 3

This level contains an binary executable called printfile that will only print files that our user has permission to read.

After examining the binary in gdb and radare2, I reverse engineered the following C pseudo-code:

int main(int argc, char \*argv)
    const char\* filename= argv\[1\]
    if (argc < 2) {
      printf("Usage: ./printfile filename")
      return 1;
    // open file in mode 4 (read only mode)
    if (access(filename, 4) != 0) {
      puts("You cant have that file...")
      return 1;
    // user is allowed to read this file, set up command to print it
    const char \*format = "/bin/cat %s";
    size\_t size = 511; // 0x1ff
    char \*s;
    // build the string buffer to pass to system()
   // note: buffer is never free'd, resulting in a memory leak
    snprintf(s, size, format, filename);

    // enable setuid bit to run as leviathan3
    // run /bin/cat filename
    return 0;

The program calls access(filename, 4), which checks to see if our user has read permission for the given file. If it does, run /bin/cat. Otherwise, exit the program.

I took a look at the man page for access to see if it has any common vulnerabilities, and found the following:

Warning: Using these calls to check if a user is authorized to, for example, open a file before actually doing so using open(2) creates a security hole, because the user might exploit the short time interval between checking and opening the file to manipulate it. For this reason, the use of this system call should be avoided. (In the example just described, a safer alternative would be to temporarily switch the process’s effective user ID to the real ID and then call open(2).)

I explored implementing an access race condition exploit, but was not successful. If anyone is able to pull this off, I’d love to hear about it!

Solution 1

The first solution is to exploit cat. Observe below:

leviathan2@leviathan:/tmp/level02$ echo 1 > a
leviathan2@leviathan:/tmp/level02$ echo 2 > b
leviathan2@leviathan:/tmp/level02$ echo 3 > "a b"
leviathan2@leviathan:/tmp/level02$ cat a b
leviathan2@leviathan:/tmp/level02$ cat "a b"
leviathan2@leviathan:/tmp/level02$ ~/printfile a b
leviathan2@leviathan:/tmp/level02$ ~/printfile "a b"

Notice that cat a b prints two separate files “a” and “b”, while cat "a b" prints out one file “a b”. Likewise, printfile a b only prints the contents of a.

How can this be used to print the password? The answer is use symbolic links!

leviathan2@leviathan:/tmp/level02$ rm a
leviathan2@leviathan:/tmp/level02$ ln -s /etc/leviathan\_pass/leviathan3 a
leviathan2@leviathan:/tmp/level02$ ~/printfile "a b"

Solution 2

In bash, commands can be ended with a semicolon ;. Since the file is being passed directly to system after the access check, what if we had a file named “somefile;sh”?

leviathan2@leviathan:/tmp/level02$ touch "somefile;sh"
leviathan2@leviathan:/tmp/level02$ ~/printfile "somefile;sh"
$ whoami

Level 3 –> 4

This is one of the more simple exercises. The password is shown in ltrace.

leviathan3@leviathan:~$ ltrace ./level3
\_\_libc\_start\_main(0x8048618, 1, 0xffffd784, 0x80486d0 <unfinished ...>
strcmp("h0no33", "kakaka")                                                    = -1
printf("Enter the password> ")                                                = 20
fgets(Enter the password> happy
"happy\\n", 256, 0xf7fc55a0)                                                 = 0xffffd590
strcmp("happy\\n", "snlprintf\\n")                                                  = -1
puts("bzzzzzzzzap. WRONG"bzzzzzzzzap. WRONG
)                                                        = 19
+++ exited (status 0) +++
leviathan3@leviathan:~$ ./level3
Enter the password> snlprintf
\[You've got shell\]!
$ whoami
$ /bin/bash
leviathan4@leviathan:~$ cat /etc/leviathan\_pass/leviathan4

Level 4 –> 5

There is a binary file named .trash/bin. When it is run, it prints several of binary numbers. I searched to see if there is a simple way to convert binary to ascii, but failed to do so with xxd, hd, or strings. If you have a solution, please let me know.

Thanks to this Stack Overflow post, we can use the following perl command:

leviathan4@leviathan:~/.trash$ ./bin | perl -lape '$\_=pack"(B8)\*",@F'

Level 5 –> 6

We are given a binary named ./leviathan5. Running ltrace ./leviathan5 shows that this binary searches for a file named “/tmp/file.log”. If the file exists, it prints out its contents.

leviathan5@leviathan:~$ echo "hello world" > /tmp/file.log
leviathan5@leviathan:~$ ./leviathan5
hello world

The ltrace output also shows that there is no call to access. Meaning, the binary does not check if we have read permission before printing! We can use a symbolic link to create a link between etc/leviathan_pass/leviathan6 and /tmp/file.log:

leviathan5@leviathan:~$ ln -s /etc/leviathan\_pass/leviathan6 /tmp/file.log
leviathan5@leviathan:~$ ./leviathan5

Level 6 –> 7

In this level, we are given a binary called leviathan6 that needs a 4 digit code. Observer the below ltrace output

leviathan6@leviathan:~$ ltrace ./leviathan6 1234
atoi(0xffffd8ad, 0, 0xf7e40890, 0x804862b)

The program calls atoi, to convert our string into an integer, and then prints “Wrong”.

I opened the binary in gdb to get a better understanding of what it is doing. Of particular interest are the following lines:

0x0804854f <+20>:    mov    DWORD PTR \[ebp-0xc\],0x1bd3
-- redacted for readability --
0x08048587 <+76>:    call   0x8048420 <atoi@plt>
0x0804858c <+81>:    add    esp,0x10
0x0804858f <+84>:    cmp    eax,DWORD PTR \[ebp-0xc\]

The value 0x1bd3 is stored into register $ebp-0xc, and the value we entered is passed to atoi and stored in register $eax. These two values are compared on line <+84>. If they match, we get a shell. gdb can be used to get the decimal value of 0x1bd3:

(gdb) p 0x1bd3
$1 = 7123

Is this the correct 4 digit code? Only one way to find out:

leviathan6@leviathan:~$ ./leviathan6 7123
$ whoami
$ cat /etc/leviathan\_pass/leviathan7

That’s all folks!

Hope you enjoyed my writeup, and please feel free to comment below!