User Tools

Site Tools


Sidebar

session:08

This is an old revision of the document!


0x08. Shellcodes (advanced)

Resources

Reminder: Shellcode

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:

  1. Write the shellcode: typically done in assembly and then convert it in binary object code.
  2. Inject the shellcode into the memory address space of the vulnerable process. This is fed through some form of input to the process (standard input, program arguments, sockets, I/O, environment variables etc.).
  3. Trigger the running of the shellcode by jumping to the shellcode address, usually done through a buffer overflow.

10.b. Challenge: Buffer is too small: Use another buffer for storing the shellcode

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.

One of the nastiest aspects of this task is the way you create the payload. Think where you will store the shellcode when feeding it as standard input to the program. One of most troublesome thing is the possible overwriting of the input_buffer variable when using strcpy(). Be careful!

10.c. Challenge: Buffer is too small: Use environment variable to store the shellcode

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.

Use the command bellow to define an environment variable and fill it with data:
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.

In GDB in order to find a string you may use:
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

Extra: io.netgarage.org (former io.smashtestack.org)

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

You must disable ASLR for the tasks to go. You may do that using either of the two commands below:
$ echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
$ linux32 -3 -R bash -l

TODO

Tutorial: Prevent NUL-bytes in shellcode for string management functions

Bonus: Call trampoline

Tasks

When running the vulnerable executables for the tasks below make sure you have disabled ASLR in your shell:
$ linux32 -3 -R bash -l

Brute-forcing the buffer address

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:

  • iterate over a range of values for the return address
  • for each value use the exploit.py script to generate a payload
  • run the vulnerable binary using that payload
Guessing a range of values for the return address

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:

  • the vuln binary has the buffer on the stack. That translates into a “sub esp, something” instruction. You have to find this value and subtract it from the value you get from print_stack
  • you're passing the payload as a paramter. This also causes the stack pointer to decrease. So make sure you also run print_stack with a parameter of the same length as the one you're passing to vuln

NOP sled

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)"

Environment variables

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)"
Finding the environment variable address

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.

session/08.1530623861.txt.gz · Last modified: 2018/07/03 16:17 by Razvan Deaconescu