ret2win

Jump to a predefined function in the binary, even with arguments

When you have found a buffer overflow, you can set the Instruction Pointer to any value by overwriting it. In some simple cases, there is a predefined function in the binary that may not even be used but can be jumped to. This is always an easy win, so check if this is the case in your binary. You can use objdump -d ./binary to view the disassembly of all known functions, or you can look at a decompiler of your choice to find these functions.

To exploit it, you can use the simple PwnTools ROP functionality to find any required ROP gadgets automatically and resolve function names for you.

elf = ELF("./binary")
rop = ROP(elf)
rop.win()

payload = flat({
    OFFSET: rop.chain()
})

The example above will call a function named win() in your binary, and then use the flat() function to add the required amount of padding before the return address (rop.chain). If you have the correct offset, it will now jump to and run your function.

In some cases, the stack might be misaligned causing segmentation faults even when doing it correctly like this. In such a case, you can simply realign the stack by inserting a single ret instruction before actually jumping to the desired function.

rop = ROP(elf)
rop.call(rop.ret)
rop.win()

Adding arguments

With some more PwnTools magic adding arguments to a function call can also be really easy:

rop = ROP(elf)
rop.win(42, 1337)

But sometimes you need a string, which is a little harder. Strings are stored as pointers (addresses) to the string. This means the string itself is not actually stored on the stack we are overflowing, only the address is. We can only set the address, so we need to find some address where the string is stored.

If you can leak addresses like the stack pointer, you can simply calculate and point it to the address of your own payload, where you completely control the value as it is your input.

If you can only leak an address like libc, you can search in that binary to find the string you need. For example:

libc = ELF("./libc.so.6")
bin_sh = next(libc.search(b"/bin/sh"))  # 0x7fcdf0446698

rop.system(bin_sh)  # Call with string argument

This way you can still call functions with specific string arguments, to get a shell in this case.

Last updated