Sunday 23 October 2016

Part 4 | Stack-based Buffer Overflow exploitation to shell by example

This is the next part of guide about exploiting buffer overflows, this time we will cover return-to-libc but for x64 architecture as well as just a little bit of Return Oriented Programming, generally what is the idea behind it, going into details in the next parts.

8. x86_64 / x86 differences

We are going to use the same source code again, mostly because I think that in learning it's good to have as little variables changing at the same time as possible so you can focus on things that really matter.
To know what we need to change in our 32 bit version to adopt it for 64bit architecture, let's look again at Agner's Fog paper on calling conventions(page 16/17) or at wikipedia. As you can see, it turns out that on the newer architecture 6 arguments are passed by registers(Linux) and following are pushed on the stack. I explained how calling a function works in more detail in part 1 of this series.

What it means for us, is that we can't push argument to system() function on the stack. We need it inside register, in rdi to be exact. To be able to do so, we need something called a gadget.
Gadget is just instruction or set of instructions inside the mapped memory sections of the executable that will allow us to do something really basic, like putting a value inside rdi.
You can use 'ropper' or 'rop' command inside pwndbg to find some of the gadgets(use it while running the binary, because if you don't, scripts won't be able to find all they could)

Gadgets found by ropper when executable is not running, as you can see by addresses, all of those are only inside binary code itself, and not for example from dynamically loaded library.

We are going to use only one gadget for our purposes, pop rdi; ret;  - on my system it's at 0x400703 (as you can see on the image above). So let's see want we are trying to achieve:

1. Stack layout (how to find address of "sh" and system() function addresses, check part3):
 |JUNK| eip overwritten with 0x400703 | &"sh" (0x7ffff7a1fe70) | *system (0x7ffff7a53380) |

2. What's going to happen:
0x400703 → pop rdi ( we are popping "sh" from the stack into rdi, after this operation, top of the stack points to pointer to system())
0x400704 → ret (ret instruction takes address from top of the stack and jumps to it)

0x7ffff7a53380 → system("sh")
Using those gadgets that very often end with ret instruction is called Return Oriented Programming, this technique allows you to bypass DEP/NX and run any code you like, but not in straightforward fashion but through jumping to gadgets and returning every time you want to do even the simplest thing, like popping to one of the registers. Tool that I used to find gadgets is even capable of finding ROP-chains that gives you shell, you can try that and see how well it performs(it's not perfect).

9. Exploit them all!

Again, let's see how example of an exploit would look like:

import struct

def dq(v):
    return struct.pack("Q", v)

buf = "A" * 1032
buf += dq(0x400703)  # pop rdi; ret
buf += dq(0x7ffff7a1fe70) # pointer to "sh"
buf += dq(0x7ffff7a53380) # system()
with open("payload", "wb") as f:

Exploit is really similar to what we have seen before. Generating payload with it and providing it to vulnerable binary gives us shell again. Remember that we disabled ASLR for this so if you can't make it work, that's probably why, also keep in mind that those addresses will vary across versions of libc.


Post a Comment