Teleport
9 minutes to read
We have a binary called 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
If we run it, it asks for a password:
$ ./teleport
Missing password
We can try to add it as a command line argument:
$ ./teleport asdf
Something's wrong...
$ ./teleport 'HTB{asdf}'
Something's wrong...
Using ltrace
we see that the input is copied at some memory address and then there are a lot of jumps (44 calls to _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) +++
At this point, we can open the binary in Ghidra and analyze it. This is the main
function:
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;
}
Almost all the jumps (43) are done inside the for
loop, and the last jump is right after it. Inside the loop it is calling some functions stored in an array (PTR_FUN_00303020
). If we examine this memory space, we will see a lot of functions (43 in 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
Let’s take a look at the first three functions:
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);
}
They are calling _setjmp
(as expected) and then comparing some byte to a given character. If they are different, it calls longjmp
using 101
as a parameter. In main
we saw that if 101
is returned from the last _setjmp
, then the password is not correct.
Hence, these functions are checking the bytes of the input password, but not in order. Also, notice that our password is copied into DAT_00303280
, and the three functions above are checking bytes at DAT_00303296
, DAT_003032a5
and DAT_00303281
, respectively. So p
, n
and T
might be valid characters of the password.
At this point, we can export the decompiled C code from Ghidra into a file and use some shell scripting to extract these instructions, sort them and join them to get the password:
$ 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 == '}
And there we have the password sorted. And it’s the 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!