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!

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:

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.

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”:

- 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.

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:

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.

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?