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
push ebp ; push on to stack
mov ebp, esp
ebp and esp both point to the same address
sub esp, 0x8
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.
foo() will open the correct flag.txt, but will not output it for us
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?