Hackfest 2022: Just a bit of reverse and coding
Writeup for the “Just a bit of reverse and coding” challenge created by @salt for the Hackfest CTF 2022.
Just a bit of reverse and coding
For this challenge, an executable named chal1
is provided along with the address to a TCP server.
1
2
3
4
5
6
7
8
9
10
11
12
13
$ file chal1
chal1: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=9f0776b32e3f34a4c6da8c7341491d2156d1096e, not stripped
$ checksec --file=chal1
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH 87 Symbols No 0 2 chal1
$ ./chal1
|------- WELCOME TO THE B&V shop -------|
| 1. Alcohol list |
| 2. About us |
| 3. Quit |
|---------------------------------------|
By decompiling the executable with Ghidra, we can see in the main
function that there is a secret option admin
available by choosing 1337
from the menu.
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
int main()
{
seed = time((time_t *)0x0);
srand((uint)seed);
choice = 0;
max = 999;
min = 100;
iVar1 = rand();
rand_value = min + iVar1 % ((max - min) + 1);
while (choice != 3) {
print_menu();
__isoc99_scanf(&DAT_00102270,&choice);
if (choice == 1) {
print_alcohol();
}
if (choice == 2) {
about();
}
if (choice == 1337) {
admin(rand_value);
}
}
puts("Bye!");
return 0;
}
The admin()
function displays the flag contained in a .txt
file, but first, a password needs to be found.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void admin(int random_value)
{
static_password = get_password(0xccccccc);
random_password = static_password * random_value;
printf("Admin password: ");
__isoc99_scanf(&DAT_00102270,&input);
if (random_password == input) {
puts("Good job!");
flag_filestream = fopen("flag1.txt","r");
if (flag_filestream == (FILE *)0x0) {
puts("ERROR! The flag cannot be read! Try again and if the problem persists contact the designe r!");
}
else {
puts("This is your flag:");
fgets((char *)&local_48,0x19,flag_filestream);
}
puts((char *)&local_48);
fclose(flag_filestream);
}
else {
puts("Wrong password!");
}
return;
}
To generate the password, the executable calls a series of nested functions to generate a static value 44160
. This value is then multiplied with a random value.
1
2
static_password = get_password(0xccccccc);
random_password = static_password * random_value;
Locally, we can set a breakpoint at address 00101384
or <admin+105>
to see the password in the RAX register.
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
gdb-peda$
[----------------------------------registers-----------------------------------]
RAX: 0x58f200
RBX: 0x7fffffffded8 --> 0x7fffffffe252 ("/home/vincent/Downloads/chal1")
RCX: 0x0
RDX: 0x0
RSI: 0x539
RDI: 0x55555555625f ("Admin password: ")
RBP: 0x7fffffffdda0 --> 0x7fffffffddc0 --> 0x1
RSP: 0x7fffffffdd50 --> 0x555555556008 ("|------- WELCOME TO THE B&V shop -------|\n| 1. Alcohol list", ' ' <repeats 23 times>, "|\n| 2. About us", ' ' <repeats 27 times>, "|\n| 3. Quit", ' ' <repeats 31 times>, "|\n|", '-' <repeats 31 times>...)
RIP: 0x555555555384 (<admin+105>: mov eax,0x0)
R8 : 0x1999999999999999
R9 : 0xa ('\n')
R10: 0x7ffff7f35ac0 --> 0x100000000
R11: 0x7ffff7f363c0 --> 0x2000200020002
R12: 0x0
R13: 0x7fffffffdee8 --> 0x7fffffffe270 ("ALACRITTY_LOG=/tmp/Alacritty-6229.log")
R14: 0x0
R15: 0x7ffff7ffd000 --> 0x7ffff7ffe2c0 --> 0x555555554000 --> 0x10102464c457f
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x555555555376 <admin+91>: imul eax,DWORD PTR [rbp-0x44]
0x55555555537a <admin+95>: mov DWORD PTR [rbp-0xc],eax
0x55555555537d <admin+98>: lea rdi,[rip+0xedb] # 0x55555555625f
=> 0x555555555384 <admin+105>: mov eax,0x0
0x555555555389 <admin+110>: call 0x555555555050 <printf@plt>
0x55555555538e <admin+115>: lea rax,[rbp-0x1c]
0x555555555392 <admin+119>: mov rsi,rax
0x555555555395 <admin+122>: lea rdi,[rip+0xed4] # 0x555555556270
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdd50 --> 0x555555556008 ("|------- WELCOME TO THE B&V shop -------|\n| 1. Alcohol list", ' ' <repeats 23 times>, "|\n| 2. About us", ' ' <repeats 27 times>, "|\n| 3. Quit", ' ' <repeats 31 times>, "|\n|", '-' <repeats 31 times>...)
0008| 0x7fffffffdd58 --> 0x84f7e2bbfa
0016| 0x7fffffffdd60 --> 0x0
0024| 0x7fffffffdd68 --> 0x0
0032| 0x7fffffffdd70 --> 0x0
0040| 0x7fffffffdd78 --> 0x7fffffffdd00 --> 0x1
0048| 0x7fffffffdd80 --> 0x0
0056| 0x7fffffffdd88 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x0000555555555384 in admin ()
The hexadecimal value 0x58f200
in the RAX
register converts to 5829120
in decimal.
1
2
3
Admin password: 5829120
Good job!
ERROR! The flag cannot be read! Try again and if the problem persists contact the designer!
This works locally, but on the server, it won’t be possible to debug the execution.
There doesn’t seem to be any vulnerabilities that could leak the random value or the password during the program’s execution.
There are 899 different passwords, and it might be possible to repeatedly run the execution with a locally identified password and eventually be lucky, but there is a better method.
1
2
3
4
max = 999;
min = 100;
iVar1 = rand();
rand_value = min + iVar1 % ((max - min) + 1);
Since the seed used is the date and time at the very beginning of the execution, we can connect to the server at the same time as running the executable on our local machine to have exactly the same password.
1
2
seed = time((time_t *)0x0);
srand((uint)seed);
Then, we can extract the password from the RAX register locally and provide it to the server to obtain the flag.