Post

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.

This post is licensed under CC BY 4.0 by the author.