Take notes on the parts that you didn't understand during the first read of "From Zero to One".
Lengthy Explanation:#
Modern operating systems often have a well-developed MPU mechanism that allows setting process memory permissions at the granularity of memory pages. Memory permissions include read (R), write (W), and execute (X). If the CPU executes code on memory that does not have execute 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 involves controlling the program's execution flow by returning to specific instruction sequences within the program.
A ROP chain is constructed using instruction fragments (gadgets) that end with the ret (0xc3) instruction. This chain allows for the execution of arbitrary instructions, ultimately achieving arbitrary code execution. The specific steps are as follows: find all ret instructions in the executable memory segments of the program and check if the bytes before the ret instruction contain valid instructions. If so, mark the fragment as a usable fragment. After finding a series of ret-ending instructions, place their addresses in order on the stack. This way, after executing the corresponding instruction, the ret instruction at the end will transfer program control flow to the new gadget at the top of the stack. This continuous sequence of gadgets on the stack forms a ROP chain, enabling the execution of arbitrary instructions.
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.
With this payload, we can obtain the address of puts (pop_rdi is the gadget for pop rdi).
Specific principle:
The return address is located 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 the pop_rdi
from the top of the stack. Therefore, the top of the stack will be 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 (referring to the existing puts
call in the program), rdi
is the argument for puts
, so the address will be printed.