HTB Uni CTF 2021 Qualifier Writeup: The Vault
Category: Reversing
Difficulty: Medium
We’re given the binary vault
. After opening it in Ghidra, we start at the main function and after going through an intermediate function, reach the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
void FUN_0010c220(void)
{
bool bVar1;
byte bVar2;
long in_FS_OFFSET;
byte local_241;
uint local_234;
char local_219;
basic_ifstream<char,std--char_traits<char>> local_218 [520];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
basic_ifstream((char *)local_218,0x10e004);
bVar2 = is_open();
if ((bVar2 & 1) == 0) {
operator<<<std--char_traits<char>>((basic_ostream *)&cout,"Could not find credentials\n");
/* WARNING: Subroutine does not return */
exit(-1);
}
bVar1 = true;
local_234 = 0;
while( true ) {
local_241 = 0;
if (local_234 < 0x19) {
local_241 = good();
}
if ((local_241 & 1) == 0) break;
get((char *)local_218);
bVar2 = (***(code ***)(&PTR_PTR_00117880)[(byte)(&DAT_0010e090)[(int)local_234]])();
if ((int)local_219 != (uint)bVar2) {
bVar1 = false;
}
local_234 = local_234 + 1;
}
if (bVar1) {
operator<<<std--char_traits<char>>
((basic_ostream *)&cout,"Credentials Accepted! Vault Unlocking...\n");
}
else {
operator<<<std--char_traits<char>>
((basic_ostream *)&cout,
"Incorrect Credentials - Anti Intruder Sequence Activated...\n");
}
~basic_ifstream(local_218);
if (*(long *)(in_FS_OFFSET + 0x28) == local_10) {
return;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
We see that the program is looking for flag.txt
and that the contents of this file will go through some checks and the vault will open. The main check occurs in the following while( true )
loop:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bVar1 = true;
local_234 = 0;
while( true ) {
local_241 = 0;
if (local_234 < 0x19) {
local_241 = good();
}
if ((local_241 & 1) == 0) break;
get((char *)local_218);
bVar2 = (***(code ***)(&PTR_PTR_00117880)[(byte)(&DAT_0010e090)[(int)local_234]])();
if ((int)local_219 != (uint)bVar2) {
bVar1 = false;
}
local_234 = local_234 + 1;
}
Variable local_234
represents the position in the text file, going up to 0x19 == 25
. A check is performed on this character and if it is incorrect, bVar1
, the variable that determines acceptance, is false. The check is as follows:
1
bVar2 = (***(code ***)(&PTR_PTR_00117880)[(byte)(&DAT_0010e090)[(int)local_234]])();
Opening the program in Hopper makes this part of the code a bit easier to understand (at least for me).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
do {
local_241 = 0x0;
if (sign_extend_64(local_234) < 0x19) {
var_23A = std::basic_ios<char, std::char_traits<char> >::good();
local_241 = var_23A;
}
if ((local_241 & 0x1) == 0x0) {
break;
}
rax = std::istream::get(&local_218);
rsi = *(int8_t *)(sign_extend_64(local_234) + 0x10e090) & 0xff;
rdi = *(0x117880 + rsi * 0x8);
rcx = *rdi;
rcx = *rcx;
bVar2 = (rcx)(rdi, rsi, 0x8, rcx);
if (sign_extend_64(local_219) != (bVar2 & 0xff)) {
bVar1 = 0x0;
}
local_234 = local_234 + 0x1;
} while (true);
rsi
is the value at the address (local_234 + 0x10e090) & 0xff
, which leads us to:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
0010e090 e0 ?? E0h
0010e091 d1 ?? D1h
0010e092 bb ?? BBh
0010e093 27 ?? 27h '
0010e094 f6 ?? F6h
0010e095 72 ?? 72h r
0010e096 db ?? DBh
0010e097 a3 ?? A3h
0010e098 83 ?? 83h
0010e099 b9 ?? B9h
0010e09a 69 ?? 69h i
0010e09b 23 ?? 23h #
0010e09c db ?? DBh
0010e09d 63 ?? 63h c
0010e09e b9 ?? B9h
0010e09f 23 ?? 23h #
0010e0a0 05 ?? 05h
0010e0a1 2b ?? 2Bh +
0010e0a2 2b ?? 2Bh +
0010e0a3 83 ?? 83h
0010e0a4 23 ?? 23h #
0010e0a5 39 ?? 39h 9
0010e0a6 45 ?? 45h E
0010e0a7 39 ?? 39h 9
0010e0a8 92 ?? 92h
For the first character, the value we’re after for rsi
is 0xe0
. From here, we have to perform rdi = *(0x117880 + rsi * 0x8)
for each value. I performed these calculations with the Windows 10 built-in Programmer calculator. For brevity, I will only show this for the first character.
For the first character, we would do 0x117f80 = (0x117880 + 0xe0 * 0x8)
, resulting in rdi = *(0x117f80)
, which leads to:
1
2
3
4
PTR_FUN_001152a8 XREF[1]: 00117780(*)
001152a8 60 d2 10 addr FUN_0010d260
00 00 00
00 00
Following this, we reach this function which returns the character for the iteration, which in this case is H
.
1
2
3
4
5
undefined8 FUN_0010d260(void)
{
return 0x48;
}
After painstakingly calculating all of the characters and following the functions, we get the flag:
HTB{vt4bl3s_4r3_c00l_huh}