ROP

this is my in depth explanation on everything rop.

Return Oriented Programming (this post is not finished)

Return Oriented Programming

ROP is the process of stringing together existing code within the binary
to execute code/shellcode, this is important as ALL modern binaries will have NX/DEP enabled. This is
a stack protection which essentially sets the stack as non executable, so any shellcode pushed or written
on will not be able to execute. This is an exploit mitigation for out stack buffer overflows, another
simpler bypass of NX is return to libc, which does essentially the same thing but strings together
existing code within the libc and binary to execute instructions and or shellcode. A major downside to
the technique known as ret2libc is that it only targets linux machines, as ive never heard of ret2dll.
this will only work on linux binaries, which is fun and good to know for CTF challenges, but unless
we plan on exploiting linux, we should stay in the general area of ROP which is applicable in basically
all scenarios and enviroments. Since ret2libc is indeed interesting i will first explain that before
getting into ROP.

TALK ABOUT RET2LIBC, DEMO TOO <-- ignore, they are just notes to myself

Now that we have ret2libc out of the way, lets establish the baseline understanding of return oriented programming an important thing to keep in mind is that pretty much all rop gadgets will end with a RET instruction.

before i explain why this is the case, and sometimes not the case, i will first explain what RET does:

RET Transfers program control to a return address located on the top of the stack. The address is usually placed on the stack by a CALL instruction, and the return is made to the instruction that follows the CALL instruction.

no known assembler will interpret and assemble this since it is invalid in more ways than one

It's pseudocode for explaining what ret does.

Now that we understand that RET doesnt just mean, stop function, we can now begin to explain CALL

this is important due to the fact that call and ret work in tandem with each other:

in this case, 0xdeadbeef is the address in which our program will continue execution in many cases this would be after printf call within main()

since call pushes the return address onto the stack then jmp's to called function then "pop eip" makes sense, as we are continuing execution at the provided address on the stack

Why is this relevant?

Here is an example of a stack frame:

Here is a visual representation of how the stack frame is established and or deallocated

ALLOCATIION

  1. push ebp ; push on to stack

  2. mov ebp, esp

ebp and esp both point to the same address

  1. sub esp, 0x8

  2. mov dword [ebp-4], 0xcafebabe

DEALLOCATION

in these cases, deallocation of stack variables can come in the form of:

or:

This is the stack before deallocation

This is the stack after deallocation

Now that we hopefully have a slightly better understanding of the purpose of the stack frame we now understand what the leave instruction does, and how it deallocates memory on the stack

Now lets begin to explain how to pwn this binary at hand, the reason i did not provide the source to this challenge, is due to the fact that reverse engineering a binary is an important piece of knowledge as well, understanding how the program works is essential to pwning a binary.

With that said lets look at the disassembly again and walk through it step by step.

Challenge 1

This first challenge will not be about rop gadgets, instead understanding the concept of how they work together to create a rop chain of sorts

This will be focusing on a little challenge i prepared for you in challenge1.zip

Thats all I have to say here, lets begin the pwning!

Reverse Engineering Challenge 1

Lets take a look at our main function

Now for our foo function:

Our last function, will use the sendfile() systemcall to write the contents of the file to stdout

Exploiting Challenge 1

The purpose behing this challenge is to establish a ROP gadget, we will not have any stack protections enabled besides the good old NX, to prevent the usage of any shellcode. We are trying to create gadgets here!

In this case, the function will open the file, and return to main. This means it will not print anything to screen, which means we do not get our precious flag. If we want to get the flag, we can find where the open syscall is being setup, write that address over the return address, open and get fd for correct flag.txt, then when pop's back to rbp

So our payload will consist of:

Now you will notice the 0xdeadbeef junk address, that is needed due to the fact that the next location that we are overwriting is not the return address on the stack, it is ebp/rbp. We currently have no use for overwriting the base pointer.

So we will just overwrite a junk address over that so we can get back to our program execution and call sendfile

Lets Start!

The first thing we will do is overwrite our 16 byte buffer, the offset for this binary is 24 until we overwrite the return address placed on the stack.

Now once we find the offset, we can control eip/rip by sending whatever address we want into it.

ret is essentially POP EIP remember?, like we said before if eip pops that value off the stack and resumes execution at the location we provided, then we can hijack control. Except this time there is a twist, NX is enabled, so we need to get creative.

  1. foo() will open the correct flag.txt, but will not output it for us

  2. bar() will output our flag, but it reads the incorrect one

How can we work with this?

Lets think, so if we can open the correct file, but cannot print it and we can open the wrong file, but we can print that, how would we exploit this?

lets open the file up in radare2, and get the address of the sym.bar function:

Now that we have it's address, we can construct the payload:

wrapping the python2 command in a bash function and calling cat will keep standard input open when we exploit the binary.

This is not important for this particular challenge, and when using pwntools it is not important either, but when crafting quick exploits on the command line, normally your shell would not work unless stdin was kept open using a trick like this.

Output:

Oops, looks like we got the wrong flag.

How would we get the program to output the correct one?

lets walk through this step by step

First, open up gdb/gef

I am going to be using this project throughout this whole explanation: https://github.com/hugsy/gef

Open the rop binary in gdb-gef, and start the program:

Once we have the program running, we can generate a cyclic pattern with the command:

200 should be a long enough pattern

Nice, we have overwritten the return address, now all we have to do is find the offset

So 24 is our offset

remember, the entire payload for this particular binary should be:

Lets find the two addresses required for this "rop" chain to work

Open the program up in radare2 again

Our first address will be 0x00401184

The reason behind starting our program execution at "mov esi, 0" is due to not needing to setup the stack frame, as we only need to ret anyways

"mov esi, 0" is important because it is initializing the file descriptor that the system call will be using.

Next it will open the program, and "ret" to the return address, which we can control.

Lets move onto the next address

Pop quiz, can you spot where our program should start next?

DONT LOOK, CHOOSE THE ADDRESS YOU THINK IS CORRECT BASED ON THE INFORMATION PROVIDED

ok are you ready?, do you have your answer??

the answer is: 0x004011d1

That is the only section we need because it is initializing and calling sendfile

This code up here is calling open on "wrong.txt", which is the incorrect file

if we were to call the address of sym.bar() instead of jumping into 0x004011d1, we would have opened the incorrect file again and printed that instead

Lets write a python script to print BOTH for us, since we're fancy

Challenge 1 Fin

Now that we have successfully created our first "rop" chain, we can move on to challenge 2

Here is the source code:

ret2system

Before I get into you can use this exploit, on your own linux binaries just so long as you have the same libc version as I do.

What ret2sys is & and what it's for:

if you would like an explanation on how the GOT and PLT work together to resolve addresses for dynamically linked executables, feel free to check out this post:

https://github.com/0xmanjoos/Exploit-Development/tree/main/fmt_bug/GOT-PLT

Let's go ahead and break down what libc is and system and exit functions:

Before we begin the lab, follow along 1. gcc -z -no-pie -fno-stack-protector lab.c 2. echo 0 > /proc/sys/kernel/randomize_via_space

Credits:

Github:https://github.com/NopSIed

Discord: Layta.#6424

Now I will show my ret2sys exploit and break it down.

create a working exploit, this will error out for anyone trying at home

Rop Gadgets

Next, we will be talking about rop gadgets, how they work, and how to use them

EXPLAN ROP GADGETS AND RET2SYSTEM

Challenge 3

note:

pop rop gadgets are stack fix-up gadgets

add is also a stack fix gadget, for possibly jumping over corrupted memory

pop rdi ; ret ; is an example

; this may be used as a skip over garbage gadget

sub rsp, 0x40

stuff goes here

add rsp, 0x40

ROP, rare and common gadgets

ret - at the end of every function, can be used as a ret(nop) sled

leave ; ret - pop ebp ; mov esp, ebp | dont corrupt ebp/rbp or you will break esp/rsp too

pop rdi ; ret - this does not have to be rdi | restoring callee saved registers before returning

mov rax, * ; ret - saves the return value before returning to return address(many returns)

ROP, storing addresses into registers(very situational!)

push rsp ; pop rax ; ret ; will mov stack address/pointer into rax

add rax, rsp ; ret ; not perfect but conceptually will move rsp into rax

xchg rax, rsp ; ret ; swap rax and rsp(DANGEROUS)

ROP stack pivoting:

there are many reasons not to like your particular stack, some of them includes limiting the amount

of bytes you can overwrite so you may not be able to create a long ROP chain. Here we have a solution

to that, stack pivoting.

xchg == exchange, this swaps rax and rsp

when the next ret is executed, the return address previouly pushed on the stack will be popped off on

to your new stack, which is where rax use to point. You obviously need to mov the address into rax

beforehand.

xchg, rax, rsp ; ret

pop rsp ; ... ; ret

ROP, transfering data:

we had previously explained how to mov addresses into registers and passing parameters on the stack

with rop gadgets, now we will talk about passing data and values with rop gadgets

example of common gadget:

add byte [rcx], al ; pop rbp ; ret

this would require a gadget to be able to set rcx(rare) and rax(more common).

ROP, syscalls are rare

extrememly rare, you will most likely never find a syscall within a binary

the shortest path to our flag will be calling a libc function like printf@plt through the PLT

procedure linkage table, and or calling system@plt for code execution

ROP, KNOW YOUR ENVIROMENT

the ropchain is located on a place on a stack, KNOW THAT ENVIROMENT WELL, there are tons of info and

things to use to get code execution. Think about the code, the stack, the heap in registers and or

on the stack.

use the registers that point to all of them, you might be able to use those registers :)

[EXPLAIN AN EXAMPLE ROP CHAIN EXPLOIT BE WRITING AND EXECVE ON STACK] This is essentially the values our stack will contain, now we just need the gadgets to pop them off in order then syscall to hopefully give us the shell

OUR GADGET SHOULD BE THIS FOR THE x86_64 CALLING CONVENTION

HERE TALK ABOUT WRITING /bin/sh TO WRITEABLE MEMORY

ALSO TALK ABOUT LEAKING CANARY

Stack Canaries (HAVENT REALLY STARTED YET)

lets take this c program as an example

labled/commented disassembled :

I have written a script to dynamically leak the stack canary of a binary with a format string vuln

There is nothing that is preventing you from finding the static offset between the input buffer and the stack canary

I just thought that doing it dynamically would be cooler. there really is no difference though, as long as the code does not change the offset between input and stack canary will stay static no matter what stack protections are put in place

Partial Overwrite

Last updated

Was this helpful?