Skip to main content
  1. Posts/

Write-Up Impossible Password HTB

·8 mins· 0 · 0 ·
WriteUp HTB Challenge Reverse Ghidra
Table of Contents

To solve this challenge I’ve used one of the most famous tool to do reverse engineering Ghidra. For sure there are more ways to solve this challenge, expecially with dynamic analysis. I will solve this challenge with static analysis. First I’ll show how to reverse the algorithm with Ghidra and then I’ll rewrite the algorithm in python.

Initial Analysis>

Initial Analysis #

First of all we need to download the binary from the htb page and unzip it. Then to undestand what we are dealing with we can use the command file to check the type of the file.

┌──(marcocampione㉿kali)-[~/Desktop/CTF_files/HTB_files]
└─$ file impossible_password.bin 
impossible_password.bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, 
interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=ba116ba1912a8c3779ddeb579404e2fdf34b1568, stripped

We obtained some information about the file, it’s a 64-bit Linux ELF and it’s stripped. That means that it does not contain any debug information.

Now let’s run the file and see what happens.

┌──(marcocampione㉿kali)-[~/Desktop/CTF_files/HTB_files]
└─$ ./impossible_password.bin 
* flag
[flag]

As we can see the program ask us to enter something after the *. If we enter something we get the same string in square brackets. So we can assume that the program is checking if the string we entered is the same as the one the program is looking for.

We can also use the command strings to see if there are some interesting strings in the file. We can often find some very interesting strings in there, like flags, passwords and other hints.

┌──(marcocampione㉿kali)-[~/Desktop/CTF_files/HTB_files]
└─$ strings impossible_password.bin 
/lib64/ld-linux-x86-64.so.2
libc.so.6
exit
srand
__isoc99_scanf
time
putchar
printf
malloc
strcmp
__libc_start_main
__gmon_start__
GLIBC_2.7
GLIBC_2.2.5
UH-x
UH-x
=1       
[]A\A]A^A_
SuperSeKretKey
%20s
[%s]
;*3$"
GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-11)
.shstrtab
.interp
.note.ABI-tag
.note.gnu.build-id
.gnu.hash
.dynsym
.dynstr
.gnu.version
.gnu.version_r
.rela.dyn
.rela.plt
.init
.text
.fini
.rodata
.eh_frame_hdr
.eh_frame
.init_array
.fini_array
.jcr
.dynamic
.got
.got.plt
.data
.bss
.comment

We can see that there is a string called SuperSeKretKey that could be the password we are looking for. Let’s try it.

┌──(marcocampione㉿kali)-[~/Desktop/CTF_files/HTB_files]
└─$ ./impossible_password.bin
* SuperSeKretKey
[SuperSeKretKey]
** SuperSeKretKey

We are going in the right direction, but we are still not there. Indeed the program is asking us to enter another password, but the one we found is not working anymore. Now let’s try to use ltrace to see what the program is doing.

┌──(marcocampione㉿kali)-[~/Desktop/CTF_files/HTB_files]
└─$ ltrace ./impossible_password.bin
__libc_start_main(0x40085d, 1, 0x7ffcf0807168, 0x4009e0 <unfinished ...>
printf("* ")                                   = 2
__isoc99_scanf(0x400a82, 0x7ffcf0807030, 0, 0* SuperSeKretKey
) = 1
printf("[%s]\n", "SuperSeKretKey"[SuperSeKretKey]
)             = 17
strcmp("SuperSeKretKey", "SuperSeKretKey")     = 0
printf("** ")                                  = 3
__isoc99_scanf(0x400a82, 0x7ffcf0807030, 0, 0** SuperSekretkey
) = 1
time(0)                                        = 1679079209
srand(0xd31bf645, 10, 0xd19e4f34, 0)           = 1
malloc(21)                                     = 0xe02ac0
rand(0xe02ac0, 21, 0, 0xe02ac0)                = 0x4112912a
rand(0x7fbdb4c08840, 0x7ffcf0806f94, 0xe02ac0, 94) = 0x2f546ca
rand(0x7fbdb4c08840, 0x7ffcf0806f94, 0xe02ac1, 94) = 0x7f2af784
rand(0x7fbdb4c08840, 0x7ffcf0806f94, 0xe02ac2, 94) = 0x627744bc
rand(0x7fbdb4c08840, 0x7ffcf0806f94, 0xe02ac3, 94) = 0x9e97b38
rand(0x7fbdb4c08840, 0x7ffcf0806f94, 0xe02ac4, 94) = 0x786022e7
rand(0x7fbdb4c08840, 0x7ffcf0806f94, 0xe02ac5, 94) = 0x7172dc2f
rand(0x7fbdb4c08840, 0x7ffcf0806f94, 0xe02ac6, 94) = 0x130120df
rand(0x7fbdb4c08840, 0x7ffcf0806f94, 0xe02ac7, 94) = 0x17b9c2fc
rand(0x7fbdb4c08840, 0x7ffcf0806f94, 0xe02ac8, 94) = 0x41cb3ec
rand(0x7fbdb4c08840, 0x7ffcf0806f94, 0xe02ac9, 94) = 0x25ea9fd9
rand(0x7fbdb4c08840, 0x7ffcf0806f94, 0xe02aca, 94) = 0x184d9bd9
rand(0x7fbdb4c08840, 0x7ffcf0806f94, 0xe02acb, 94) = 0x6ce9f739
rand(0x7fbdb4c08840, 0x7ffcf0806f94, 0xe02acc, 94) = 0x70f068de
rand(0x7fbdb4c08840, 0x7ffcf0806f94, 0xe02acd, 94) = 0x572caba3
rand(0x7fbdb4c08840, 0x7ffcf0806f94, 0xe02ace, 94) = 0x5ac296ec
rand(0x7fbdb4c08840, 0x7ffcf0806f94, 0xe02acf, 94) = 0x43bca72e
rand(0x7fbdb4c08840, 0x7ffcf0806f94, 0xe02ad0, 94) = 0x1ef74ee2
rand(0x7fbdb4c08840, 0x7ffcf0806f94, 0xe02ad1, 94) = 0x60f7f821
rand(0x7fbdb4c08840, 0x7ffcf0806f94, 0xe02ad2, 94) = 0x65470193
strcmp("SuperSekretkey", "1go'E~t,yKxb4yd)ei*$") = 34
+++ exited (status 34) +++

__isoc99_scanf is reading or input, then printf is printing out the output.

printf("[%s]\n", "SuperSeKretKey"[SuperSeKretKey]

and then compare our input with the expected string SuperSeKretKey using strcmp.

strcmp("SuperSeKretKey", "SuperSeKretKey")

Since the two strings are equal we ere able to pass the first check. Now let’s see what happens when we enter for the second time the same password.

strcmp("SuperSekretkey", "1go'E~t,yKxb4yd)ei*$")

We can see that the program is comparing our input with a string that is not the same as the one we entered before. This is because the program is generating a random string using rand and then compare it with our input. So the second password seems to be dynamically generated. Let’s try to understand how reversing the binary with ghidra.

Reversing with Ghidra>

Reversing with Ghidra #

Let’s open the binary with ghidra and see what we can find.

Main Function>

Main Function #

First we need to find the main function. We can do this by searching for the __libc_start_main in the Symbol Tree. As we can see in the pic the main function is called FUN_0040085d. We can edit the name of the function by right clicking on it and then selecting Rename Function. Let’s rename it to main. Now let’s see what the main function is doing.

void main(void)

{
  int iVar1;
  char *__s2;
  undefined local_48;
  undefined local_47;
  undefined local_46;
  undefined local_45;
  undefined local_44;
  undefined local_43;
  undefined local_42;
  undefined local_41;
  undefined local_40;
  undefined local_3f;
  undefined local_3e;
  undefined local_3d;
  undefined local_3c;
  undefined local_3b;
  undefined local_3a;
  undefined local_39;
  undefined local_38;
  undefined local_37;
  undefined local_36;
  undefined local_35;
  char local_28 [20];
  int local_14;
  char *local_10;
  
  local_10 = "SuperSeKretKey";
  local_48 = 0x41;
  local_47 = 0x5d;
  local_46 = 0x4b;
  local_45 = 0x72;
  local_44 = 0x3d;
  local_43 = 0x39;
  local_42 = 0x6b;
  local_41 = 0x30;
  local_40 = 0x3d;
  local_3f = 0x30;
  local_3e = 0x6f;
  local_3d = 0x30;
  local_3c = 0x3b;
  local_3b = 0x6b;
  local_3a = 0x31;
  local_39 = 0x3f;
  local_38 = 0x6b;
  local_37 = 0x38;
  local_36 = 0x31;
  local_35 = 0x74;
  printf("* ");
  __isoc99_scanf(&DAT_00400a82,local_28);
  printf("[%s]\n",local_28);
  local_14 = strcmp(local_28,local_10);
  if (local_14 != 0) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  printf("** ");
  __isoc99_scanf(&DAT_00400a82,local_28);
  __s2 = (char *)FUN_0040078d(0x14);
  iVar1 = strcmp(local_28,__s2);
  if (iVar1 == 0) {
    FUN_00400978(&local_48);
  }
  return;
}

We can start to rename some the variables to better understand what the code is doing. We will obtain something like this.

void main(void)

{
  int did_psw_match_2;
  char *random_psw;
  undefined local_48;
  undefined local_47;
  undefined local_46;
  undefined local_45;
  undefined local_44;
  undefined local_43;
  undefined local_42;
  undefined local_41;
  undefined local_40;
  undefined local_3f;
  undefined local_3e;
  undefined local_3d;
  undefined local_3c;
  undefined local_3b;
  undefined local_3a;
  undefined local_39;
  undefined local_38;
  undefined local_37;
  undefined local_36;
  undefined local_35;
  char user_input_psw [20];
  int did_psw_match_1;
  char *psw_lvl_1;
  
  psw_lvl_1 = "SuperSeKretKey";
  local_48 = 'A';
  local_47 = ']';
  local_46 = 'K';
  local_45 = 'r';
  local_44 = '=';
  local_43 = '9';
  local_42 = 'k';
  local_41 = '0';
  local_40 = '=';
  local_3f = '0';
  local_3e = 'o';
  local_3d = '0';
  local_3c = ';';
  local_3b = 'k';
  local_3a = '1';
  local_39 = '?';
  local_38 = 'k';
  local_37 = '8';
  local_36 = '1';
  local_35 = 't';
  printf("* ");
  __isoc99_scanf("%20s",user_input_psw);
  printf("[%s]\n",user_input_psw);
  did_psw_match_1 = strcmp(user_input_psw,psw_lvl_1);
  if (did_psw_match_1 != 0) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  printf("** ");
  __isoc99_scanf("%20s",user_input_psw);
  random_psw = (char *)generate_psw(0x14);
  did_psw_match_2 = strcmp(user_input_psw,random_psw);
  if (did_psw_match_2 == 0) {
    print_flag(&local_48);
  }
  return;
}

Some observations:

  1. local_48 is used as an argument in the print_flag function.
  2. local_35 to local_48 contain single characters.

Assumpions:

  1. local_35 to local_48 is containing our encrypted flag.
  2. They are not individual variables. Since we are passing local_48 as an argument to the print_flag function. It means that local_48 is the a pointer / array.

Let’s re-type the local_48 variable. it is a byte array not only a byte. Now the code will look like this.

void main(void)

{
  int did_psw_match_2;
  char *random_psw;
  byte encrypted_flag [20];
  char user_input_psw [20];
  int did_psw_match_1;
  char *psw_lvl_1;
  
  psw_lvl_1 = "SuperSeKretKey";
  encrypted_flag[0] = 'A';
  encrypted_flag[1] = ']';
  encrypted_flag[2] = 'K';
  encrypted_flag[3] = 'r';
  encrypted_flag[4] = '=';
  encrypted_flag[5] = '9';
  encrypted_flag[6] = 'k';
  encrypted_flag[7] = '0';
  encrypted_flag[8] = '=';
  encrypted_flag[9] = '0';
  encrypted_flag[10] = 'o';
  encrypted_flag[11] = '0';
  encrypted_flag[12] = ';';
  encrypted_flag[13] = 'k';
  encrypted_flag[14] = '1';
  encrypted_flag[15] = '?';
  encrypted_flag[16] = 'k';
  encrypted_flag[17] = '8';
  encrypted_flag[18] = '1';
  encrypted_flag[19] = 't';
  printf("* ");
  __isoc99_scanf("%20s",user_input_psw);
  printf("[%s]\n",user_input_psw);
  did_psw_match_1 = strcmp(user_input_psw,psw_lvl_1);
  if (did_psw_match_1 != 0) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  printf("** ");
  __isoc99_scanf("%20s",user_input_psw);
  random_psw = (char *)generate_psw(0x14);
  did_psw_match_2 = strcmp(user_input_psw,random_psw);
  if (did_psw_match_2 == 0) {
    print_flag(encrypted_flag);
  }
  return;
}

Print Flag Function #

Now let’s look at the print_flag function.

After renaming the variables we will get something like this.

void print_flag(byte *param_1)

{
  int i;
  byte *encrypted_flag;
  
  i = 0;
  encrypted_flag = param_1;
  while ((*encrypted_flag != 9 && (i < 20))) {
    putchar((int)(char)(*encrypted_flag ^ 9));
    encrypted_flag = encrypted_flag + 1;
    i = i + 1;
  }
  putchar(10);
  return;
}

The program is looping over our encrypted flag. The two conditions to exit the loop are:

  1. *encrypted_flag != 9, 9 is the ASCII code for the tab character.
  2. i < 20

Then we call putchar and Xoring the current byte with 9. After that increasing encrypted_flag with 1 and increasing i with 1.

Solving with Python>

Solving with Python #

Now that we have a better understanding of the program. Let’s write a python script to solve it.

#!/usr/bin/env python3

flag_characters = ['A',']','K','r','=','9','k','0','=','0','o','0',';','k','1','?','k','8','1','t']
xor_key = 9

flag = []
i = 0
while i < len(flag_characters):
	xored = ord(flag_characters[i]) ^ xor_key
	flag.append(chr(xored))
	i += 1

flag_string = "".join(flag)
print "Flag is: {}".format(flag_string)
Solving with CyberChef>

Solving with CyberChef #

We can also solve this challenge using CyberChef. The recipe is as follows: