Teleport
9 minutos de lectura
Se nos proporciona un binario llamado teleport
:
$ file teleport
teleport: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=1f87fe68fd7d1deaffefcf08ed2b30d660ee2d0b, stripped
Si lo ejecutamos, nos pregunta por una contraseña:
$ ./teleport
Missing password
Podemos probar a añadirla como argumento de línea de comandos:
$ ./teleport asdf
Something's wrong...
$ ./teleport 'HTB{asdf}'
Something's wrong...
Con ltrace
podemos ver que nuestra entrada se copia en una dirección de memoria y luego hay un montón de saltos (44 llamadas a _setjmp
):
$ ltrace ./teleport asdf
strncpy(0x5571e7a03280, "asdf", 100) = 0x5571e7a03280
_setjmp(0x5571e7a04430, 5, 0x5571e7800e16, 0x666473) = 0
_setjmp(0x5571e7a04fe8, 0, 0x5571e7800d8e, 0x666473) = 0
_setjmp(0x5571e7a033c8, 0, 0x5571e7801256, 0x666473) = 0
_setjmp(0x5571e7a04b38, 0, 0x5571e78015ca, 0x666473) = 0
_setjmp(0x5571e7a05240, 0, 0x5571e7800f26, 0x666473) = 0
_setjmp(0x5571e7a041d8, 0, 0x5571e78014ba, 0x666473) = 0
_setjmp(0x5571e7a04e58, 0, 0x5571e7800bf6, 0x666473) = 0
_setjmp(0x5571e7a04f20, 0, 0x5571e78013aa, 0x666473) = 0
_setjmp(0x5571e7a04c00, 0, 0x5571e7801146, 0x666473) = 0
_setjmp(0x5571e7a03620, 0, 0x5571e7800d4a, 0x666473) = 0
_setjmp(0x5571e7a037b0, 0, 0x5571e7801542, 0x666473) = 0
_setjmp(0x5571e7a04688, 0, 0x5571e780129a, 0x666473) = 0
_setjmp(0x5571e7a03558, 0, 0x5571e7800b2a, 0x666473) = 0
_setjmp(0x5571e7a04048, 0, 0x5571e780160e, 0x666473) = 0
_setjmp(0x5571e7a03a08, 0, 0x5571e7800ee2, 0x666473) = 0
_setjmp(0x5571e7a04818, 0, 0x5571e7800bb2, 0x666473) = 0
_setjmp(0x5571e7a04d90, 0, 0x5571e7800fae, 0x666473) = 0
_setjmp(0x5571e7a04cc8, 0, 0x5571e780107a, 0x666473) = 0
_setjmp(0x5571e7a04a70, 0, 0x5571e78013ee, 0x666473) = 0
_setjmp(0x5571e7a044f8, 0, 0x5571e78012de, 0x666473) = 0
_setjmp(0x5571e7a03eb8, 0, 0x5571e7801432, 0x666473) = 0
_setjmp(0x5571e7a049a8, 0, 0x5571e7801212, 0x666473) = 0
_setjmp(0x5571e7a05308, 0, 0x5571e7800ff2, 0x666473) = 0
_setjmp(0x5571e7a053d0, 0, 0x5571e7801652, 0x666473) = 0
_setjmp(0x5571e7a036e8, 0, 0x5571e7800b6e, 0x666473) = 0
_setjmp(0x5571e7a03490, 0, 0x5571e7800d06, 0x666473) = 0
_setjmp(0x5571e7a03d28, 0, 0x5571e7800c3a, 0x666473) = 0
_setjmp(0x5571e7a05178, 0, 0x5571e7801322, 0x666473) = 0
_setjmp(0x5571e7a03878, 0, 0x5571e78014fe, 0x666473) = 0
_setjmp(0x5571e7a03b98, 0, 0x5571e7800c7e, 0x666473) = 0
_setjmp(0x5571e7a04368, 0, 0x5571e7801586, 0x666473) = 0
_setjmp(0x5571e7a03300, 0, 0x5571e78011ce, 0x666473) = 0
_setjmp(0x5571e7a042a0, 0, 0x5571e7800e9e, 0x666473) = 0
_setjmp(0x5571e7a04110, 0, 0x5571e7801036, 0x666473) = 0
_setjmp(0x5571e7a03f80, 0, 0x5571e7801366, 0x666473) = 0
_setjmp(0x5571e7a03df0, 0, 0x5571e7800cc2, 0x666473) = 0
_setjmp(0x5571e7a03c60, 0, 0x5571e7800dd2, 0x666473) = 0
_setjmp(0x5571e7a03940, 0, 0x5571e7801476, 0x666473) = 0
_setjmp(0x5571e7a03ad0, 0, 0x5571e780118a, 0x666473) = 0
_setjmp(0x5571e7a04750, 0, 0x5571e7801102, 0x666473) = 0
_setjmp(0x5571e7a045c0, 0, 0x5571e7800e5a, 0x666473) = 0
_setjmp(0x5571e7a050b0, 0, 0x5571e7800f6a, 0x666473) = 0
_setjmp(0x5571e7a048e0, 0, 0x5571e78010be, 0x666473) = 0
_setjmp(0x5571e7a031a0, 0, 0x4b055124abc880dc, 0x666473) = 0
longjmp(0x5571e7a03300, 1, 0x5571e7a03300, 0x666473 <unfinished ...>
longjmp(0x5571e7a031a0, 101, 0x5571e78011de, 0x666473 <unfinished ...>
puts("Something's wrong..."Something's wrong...
) = 21
<... longjmp resumed> ) = 21
+++ exited (status 0) +++
En este punto, podemos abrir el binario en Ghidra para analizarlo. Esta es la función main
:
undefined8 main(int param_1, long param_2) {
int iVar1;
undefined8 uVar2;
uint i;
if (param_1 == 2) {
strncpy(&DAT_00303280, *(char **) (param_2 + 8), 100);
for (i = 0; i < 43; i++) {
(*(code *) (&PTR_FUN_00303020)[(int) i])();
}
iVar1 = _setjmp((__jmp_buf_tag *) &DAT_003031a0);
if (iVar1 == 100) {
puts("Looks good to me!");
} else {
if (iVar1 != 101) {
/* WARNING: Subroutine does not return */
longjmp((__jmp_buf_tag *) (&DAT_00303300 + (long) iVar1 * 200), 1);
}
puts("Something\'s wrong...");
}
uVar2 = 0;
} else {
puts("Missing password");
uVar2 = -1;
}
return uVar2;
}
Casi todos los saltos (43) se realizan en el bucle for
, y el último salto está justo después. Dentro del bucle se llama a unas funciones almacenadas en un vector (PTR_FUN_00303020
). Si examinamos este espacio de memoria, veremos un montón de funciones (43 en total):
PTR_FUN_00303020 XREF[2]: main:001016f6(*),
main:001016fd(R)
00303020 16 0e 10 addr FUN_00100e16
00 00 00
00 00
00303028 8e 0d 10 addr FUN_00100d8e
00 00 00
00 00
00303030 56 12 10 addr FUN_00101256
00 00 00
00 00
00303038 ca 15 10 addr FUN_001015ca
00 00 00
00 00
00303040 26 0f 10 addr FUN_00100f26
00 00 00
00 00
00303048 ba 14 10 addr FUN_001014ba
00 00 00
00 00
00303050 f6 0b 10 addr FUN_00100bf6
00 00 00
00 00
00303058 aa 13 10 addr FUN_001013aa
00 00 00
00 00
00303060 46 11 10 addr FUN_00101146
00 00 00
00 00
00303068 4a 0d 10 addr FUN_00100d4a
00 00 00
00 00
00303070 42 15 10 addr FUN_00101542
00 00 00
00 00
00303078 9a 12 10 addr FUN_0010129a
00 00 00
00 00
00303080 2a 0b 10 addr FUN_00100b2a
00 00 00
00 00
00303088 0e 16 10 addr FUN_0010160e
00 00 00
00 00
00303090 e2 0e 10 addr FUN_00100ee2
00 00 00
00 00
00303098 b2 0b 10 addr FUN_00100bb2
00 00 00
00 00
003030a0 ae 0f 10 addr FUN_00100fae
00 00 00
00 00
003030a8 7a 10 10 addr FUN_0010107a
00 00 00
00 00
003030b0 ee 13 10 addr FUN_001013ee
00 00 00
00 00
003030b8 de 12 10 addr FUN_001012de
00 00 00
00 00
003030c0 32 14 10 addr FUN_00101432
00 00 00
00 00
003030c8 12 12 10 addr FUN_00101212
00 00 00
00 00
003030d0 f2 0f 10 addr FUN_00100ff2
00 00 00
00 00
003030d8 52 16 10 addr FUN_00101652
00 00 00
00 00
003030e0 6e 0b 10 addr FUN_00100b6e
00 00 00
00 00
003030e8 06 0d 10 addr FUN_00100d06
00 00 00
00 00
003030f0 3a 0c 10 addr FUN_00100c3a
00 00 00
00 00
003030f8 22 13 10 addr FUN_00101322
00 00 00
00 00
00303100 fe 14 10 addr FUN_001014fe
00 00 00
00 00
00303108 7e 0c 10 addr FUN_00100c7e
00 00 00
00 00
00303110 86 15 10 addr FUN_00101586
00 00 00
00 00
00303118 ce 11 10 addr FUN_001011ce
00 00 00
00 00
00303120 9e 0e 10 addr FUN_00100e9e
00 00 00
00 00
00303128 36 10 10 addr FUN_00101036
00 00 00
00 00
00303130 66 13 10 addr FUN_00101366
00 00 00
00 00
00303138 c2 0c 10 addr FUN_00100cc2
00 00 00
00 00
00303140 d2 0d 10 addr FUN_00100dd2
00 00 00
00 00
00303148 76 14 10 addr FUN_00101476
00 00 00
00 00
00303150 8a 11 10 addr FUN_0010118a
00 00 00
00 00
00303158 02 11 10 addr FUN_00101102
00 00 00
00 00
00303160 5a 0e 10 addr FUN_00100e5a
00 00 00
00 00
00303168 6a 0f 10 addr FUN_00100f6a
00 00 00
00 00
00303170 be 10 10 addr FUN_001010be
00 00 00
00 00
Vamos a mirar las tres primeras funciones:
void FUN_00100e16() {
int iVar1;
iVar1 = _setjmp((__jmp_buf_tag *) &DAT_00304430);
if (iVar1 == 0) {
return;
}
if (DAT_00303296 == 'p') {
/* WARNING: Subroutine does not return */
longjmp((__jmp_buf_tag *) &DAT_003031a0, 23);
}
/* WARNING: Subroutine does not return */
longjmp((__jmp_buf_tag *) &DAT_003031a0, 101);
}
void FUN_00100d8e() {
int iVar1;
iVar1 = _setjmp((__jmp_buf_tag *) &DAT_00304fe8);
if (iVar1 == 0) {
return;
}
if (DAT_003032a5 == 'n') {
/* WARNING: Subroutine does not return */
longjmp((__jmp_buf_tag *) &DAT_003031a0, 38);
}
/* WARNING: Subroutine does not return */
longjmp((__jmp_buf_tag *) &DAT_003031a0, 101);
}
void FUN_00101256() {
int iVar1;
iVar1 = _setjmp((__jmp_buf_tag *) &DAT_003033c8);
if (iVar1 == 0) {
return;
}
if (DAT_00303281 == 'T') {
/* WARNING: Subroutine does not return */
longjmp((__jmp_buf_tag *) &DAT_003031a0, 2);
}
/* WARNING: Subroutine does not return */
longjmp((__jmp_buf_tag *) &DAT_003031a0, 101);
}
Están llamando a _setjmp
(como era de esperar) y luego comparando un byte con un carácter dado. Si son diferentes, se llama a longjmp
usando 101
como parámetro. En el main
vimos que si 101
es devuelto del último _setjmp
, entonces la contraseña no es correcta.
Por tanto, estas funciones están chequeando los bytes de nuestra contraseña, pero no en orden. Además, nótese que la contraseña se copua en DAT_00303280
, y estas tres funciones están mirando los bytes de DAT_00303296
, DAT_003032a5
y DAT_00303281
, respectivamente. Por tanto, p
, n
y T
pueden ser caracteres válidos de la contraseña.
En este punto, podemos exportar el código descompilado en C desde Ghidra a un archivo y hacer un poco de shell scripting para extraer las instrucciones, ordenarlas y juntarlas para obtener la contraseña:
$ grep \(DAT_003032 teleport.c
if (DAT_00303283 == '{') {
if (DAT_00303285 == 'u') {
if (DAT_0030329b == 't') {
if (DAT_003032a3 == 't') {
if (DAT_0030328d == 'h') {
if (DAT_0030328b == '_') {
if (DAT_0030328e == 'r') {
if (DAT_00303282 == 'B') {
if (DAT_00303284 == 'j') {
if (DAT_003032a5 == 'n') {
if (DAT_0030328c == 't') {
if (DAT_00303296 == 'p') {
if (DAT_00303298 == 'c') {
if (DAT_00303294 == '_') {
if (DAT_00303289 == 'n') {
if (DAT_003032a8 == 'm') {
if (DAT_003032a6 == 'u') {
if (DAT_003032a2 == 'n') {
if (DAT_003032a9 == '!') {
if (DAT_00303292 == 'h') {
if (DAT_003032a1 == '0') {
if (DAT_0030329c == '1') {
if (DAT_0030329a == '_') {
if (DAT_003032a0 == 'c') {
if (DAT_0030328a == 'g') {
if (DAT_00303280 == 'H') {
if (DAT_0030329d == 'm') {
if (DAT_00303281 == 'T') {
if (DAT_00303299 == '3') {
if (DAT_00303297 == '4') {
if (DAT_003032a7 == 'u') {
if (DAT_00303290 == '_') {
if (DAT_003032a4 == '1') {
if (DAT_0030329e == '3') {
if (DAT_0030328f == 'u') {
if (DAT_00303288 == '1') {
if (DAT_00303293 == '3') {
if (DAT_00303287 == 'p') {
if (DAT_00303286 == 'm') {
if (DAT_00303295 == 's') {
if (DAT_0030329f == '_') {
if (DAT_00303291 == 't') {
if (DAT_003032aa == '}') {
$ grep \(DAT_003032 teleport.c | cut -c7-24
DAT_00303283 == '{
DAT_00303285 == 'u
DAT_0030329b == 't
DAT_003032a3 == 't
DAT_0030328d == 'h
DAT_0030328b == '_
DAT_0030328e == 'r
DAT_00303282 == 'B
DAT_00303284 == 'j
DAT_003032a5 == 'n
DAT_0030328c == 't
DAT_00303296 == 'p
DAT_00303298 == 'c
DAT_00303294 == '_
DAT_00303289 == 'n
DAT_003032a8 == 'm
DAT_003032a6 == 'u
DAT_003032a2 == 'n
DAT_003032a9 == '!
DAT_00303292 == 'h
DAT_003032a1 == '0
DAT_0030329c == '1
DAT_0030329a == '_
DAT_003032a0 == 'c
DAT_0030328a == 'g
DAT_00303280 == 'H
DAT_0030329d == 'm
DAT_00303281 == 'T
DAT_00303299 == '3
DAT_00303297 == '4
DAT_003032a7 == 'u
DAT_00303290 == '_
DAT_003032a4 == '1
DAT_0030329e == '3
DAT_0030328f == 'u
DAT_00303288 == '1
DAT_00303293 == '3
DAT_00303287 == 'p
DAT_00303286 == 'm
DAT_00303295 == 's
DAT_0030329f == '_
DAT_00303291 == 't
DAT_003032aa == '}
$ grep \(DAT_003032 teleport.c | cut -c7-24 | sort
DAT_00303280 == 'H
DAT_00303281 == 'T
DAT_00303282 == 'B
DAT_00303283 == '{
DAT_00303284 == 'j
DAT_00303285 == 'u
DAT_00303286 == 'm
DAT_00303287 == 'p
DAT_00303288 == '1
DAT_00303289 == 'n
DAT_0030328a == 'g
DAT_0030328b == '_
DAT_0030328c == 't
DAT_0030328d == 'h
DAT_0030328e == 'r
DAT_0030328f == 'u
DAT_00303290 == '_
DAT_00303291 == 't
DAT_00303292 == 'h
DAT_00303293 == '3
DAT_00303294 == '_
DAT_00303295 == 's
DAT_00303296 == 'p
DAT_00303297 == '4
DAT_00303298 == 'c
DAT_00303299 == '3
DAT_0030329a == '_
DAT_0030329b == 't
DAT_0030329c == '1
DAT_0030329d == 'm
DAT_0030329e == '3
DAT_0030329f == '_
DAT_003032a0 == 'c
DAT_003032a1 == '0
DAT_003032a2 == 'n
DAT_003032a3 == 't
DAT_003032a4 == '1
DAT_003032a5 == 'n
DAT_003032a6 == 'u
DAT_003032a7 == 'u
DAT_003032a8 == 'm
DAT_003032a9 == '!
DAT_003032aa == '}
Y ahi tenemos la contraseña ordenada. Y resulta que es la flag:
$ grep \(DAT_003032 teleport.c | cut -c7-24 | sort | cut -c18- | tr -d '\n'
HTB{jump1ng_thru_th3_sp4c3_t1m3_c0nt1nuum!}
$ ./teleport 'HTB{jump1ng_thru_th3_sp4c3_t1m3_c0nt1nuum!}'
Looks good to me!