This is an old revision of the document!
A shellcode is a little piece of binary data that is meant to be executed by a process as part of an attack vector. An attacker would usually place a shellcode in the process memory and aim to execute it to trigger an advantageous effect for the attacker.
While a shellcode would typically result in the attacker gaining a shell process by the means the of the execve system call, this needn't always be the case. Some shellcodes may result in writing data to a socket, scanning the memory, opening/creating a file and many others.
A shellcode is typically written in assembly language and then compiled into binary object code and fed to the vulnerable program. There are three actions an attacker must undertake to run a shellcode in a vulnerable program:
It may so happen that the buffer we overflow is to small to store the entire payload (including the shellcode). In such a situation we can't store the shellcode and then do the overflow in the same buffer. Similar to one of the tasks above we used for storing the shellcode in a global variable.
In the small-stack-buffer/
folder in the tasks archive you have a setup where the local buffer
variable is only 20 bytes large, unable to store the entire payload. However you may use the input_buffer
variable for storing the shellcode.
Make the attack and get a shell.
input_buffer
variable when using strcpy()
. Be careful!
Let's make things a bit more challenging. Let's assume we have no room to store the shellcode in the local buffers.
In the shellcode-in-envvar/
subfolder of the tasks archive both the input_buffer
and the buffer
variables are now 28
bytes wide and 8
bytes wide, respectively. We have no room to store the shellcode.
In this case we will can use another trick. We can define an environment variable, fill it with the shellcode, determine its address and overwrite the function return address with that address.
We recommend you create a file named shellcode_payload
storing the shellcode. The contents of this file are to fill an environment variable.
export SHELLCODE=$(cat shellcode_payload)
In order to identify the address where this environment variable is stored, we recommend you add a padding prefix and look for that. For example, when creating the shellcode_payload
file, use 32
A
characters and then add the shellcode.
gdb-peda$ find "AAAAAAAA" $esp $esp+1000
This above command means look for the "AAAAAAAA"
string in the $esp, $esp+1000
range (i.e. starting from the value of the esp
register and ending 1000
bytes later).
You should also create an overflow_payload
file for the actual attack, overflowing the buffer
variable and rewriting the return address with that of the start of the shellcode.
In the end you would be able to run the attack through a command such as
cat overflow_payload - | SHELLCODE=$(cat shellcode_payload) ./vuln
One of the most interesting and approachable security wargames is io.netgarage.org. It's highly recommended you go through as many tasks as possible.
For this session, we will use the 3rd and 5th tasks. They are found in the io.smashthestack.org/
subfolder in the tasks archive.
Go through them, exploit them and profit (i.e. get a shell running).
$ echo 0 | sudo tee /proc/sys/kernel/randomize_va_space $ linux32 -3 -R bash -l
Tutorial: Prevent NUL-bytes in shellcode for string management functions
Bonus: Call trampoline
$ linux32 -3 -R bash -l
Switch to the 2-bruteforce directory.
In this scenario we won't be using gdb to find the buffer address, since this method is very theoretical anyway. Instead we'll try every address in some range. To find out this range, we'll use a simple program that prints the value of its stack pointer.
You'll use the exploit.py script from the previous task for generating the payload. However, you'll have to modify it so that the return address won't be hardcoded in the script anymore, but taken as a parameter.
Then you have to edit the run.sh script to do the following:
You'll do this using the print_stack executable. Keep in mind that you can't use the value you get from print_stack directly. There are a couple of facts you have to take into account:
In order to minimize the number of tries required for a successful exploitation you'll use a NOP sled. Start from the exploit.py file from the previous task. You can also use print_stack to obtain a guess for the return address. In the end you should be able to run the exploit with a line like:
$ ./vuln "$(exploit.py 0xbfffXXXX)"
In this scenario the stack buffer is too small to hold the entire shellcode. To overcome this you'll have to place the shellcode in an environment variable. To increase the chance of succeeding you will also prepend the shellcode with NOPs, as much as the system will allow for an environment variable (around 128K).
The shell.py file will generate the shellcode to be placed in the environment var. The exploit.py file will generate the actual exploit for the overflow.
This is how you run them:
export A="$(./shell.py)" ./vuln "$(./exploit.py 0xbfffXXXX)"
You still have to know what value to overwrite the return address with, that is, the address of the environment variable. To do this, you can write a small program that searches the variable in the environment and prints its address. Then you use the same address (or something around it) in your exploit, assuming that the environment is roughly the same when passed from the shell to its child processes.