Take notes on the parts that you didn't understand while reading "From Zero to One" for the first time.
Lengthy Discussion:#
Modern operating systems often have a well-developed MPU mechanism that allows setting process memory usage permissions based on the granularity of memory pages. Memory permissions include read (R), write (W), and execute (X). If the CPU executes code on memory without executable permissions, the operating system immediately terminates the program.
By default, based on vulnerability mitigation rules, programs do not have memory with both write and execute permissions. Therefore, it is not possible to execute arbitrary code by modifying the program's code segment or data segment. To exploit this vulnerability mitigation mechanism, there is an attack technique called Return-Oriented Programming (ROP), which controls the program's execution flow by returning to specific instruction sequences in the program.
A ROP chain is constructed using instruction fragments (gadgets) that end with the ret (0xc3) instruction to achieve arbitrary instruction execution and ultimately execute arbitrary code. The specific steps are as follows: find all ret instructions in the executable memory segments of the program and check if the bytes before ret contain valid instructions. If so, mark the fragment as an available fragment. After finding a series of ret-ending instructions, place the addresses of these instructions in order on the stack. In this way, after executing the corresponding instructions, the ret instruction at the end will pass the program control flow to the new gadget at the top of the stack for further execution. This continuous sequence of gadgets on the stack forms a ROP chain, enabling arbitrary instruction execution.
Procedure:#
Routine: https://buuoj.cn/challenges#[Chapter 6 CTF PWN Chapter]ROP
Use ROPgadget --binary rop
to obtain all gadgets.
There are too few gadgets, so we need to obtain the loading address of libc and then find syscall within it.
This payload can obtain the address of puts (pop_rdi is the gadget for pop rdi).
Specific principle:
The return address is 0x12 bytes ahead.
When executing leave
, pop_rdi
is at the top of the stack (the ones above it are cleared).
When executing ret
, it will jump to the address where pop_rdi
is located and pop out the top of the stack, so the top of the stack is puts_got
.
puts_got
is a pointer to the actual address of puts
.
Then execute pop_rdi
to put puts_got
into rdi
.
Return to puts
, according to the calling convention (refer to the existing puts
call in the program), rdi
is the argument for puts
, so the address is outputted.