ROP chain and payload builder
Assemble an exploit payload from padding, addresses, and raw bytes, with a live hexdump and offset ruler - the pwntools flat() workflow.
Open in ctfpalA ROP chain is a list of return addresses that the ret instruction walks through, each gadget doing a small amount of work before returning into the next. Building one is bookkeeping - the difficulty is keeping the stack layout straight, and a wrong offset by eight bytes produces a crash indistinguishable from a wrong gadget.
Building the payload as a visible structure rather than a concatenated string is what keeps that bookkeeping honest. When the layout is a list of rows with running offsets, an eight-byte error is something you can see; when it is a chain of string concatenations, it is something you discover after an hour of debugging the wrong hypothesis.
The standard 64-bit shape
payload = b’A' * offset # fill to the saved return address
payload += p64(pop_rdi) # gadget: pop rdi ; ret
payload += p64(binsh_addr) # -> rdi = "/bin/sh"
payload += p64(ret_gadget) # stack alignment - see below
payload += p64(system_addr) # -> system("/bin/sh")Two things that break chains silently
- Stack alignment. The x86-64 ABI requires 16-byte alignment at a
call. glibc’ssystemuses SSE instructions that fault if it is not aligned. A bareretgadget inserted before the call shifts the stack by 8 and fixes it - this is the cause of the notorious 'crashes insidesystem' symptom. - Bad bytes. If the input goes through
scanf("%s"), whitespace terminates it; throughgets, newline does; throughstrcpy, null does. A gadget whose address contains a forbidden byte cannot be used, however correct it is.