M30W Vault Tech

Flag: CYBN{0n_d1t_ch1ffr3m3nt_aHahAh_c3stM4rr4nt}

Challenge

Description


Agent, le message suivant de l'acteur [redacted] réputé membre du groupe M30W a été intercepté.

J'ai fini de mettre au point l'idée d'algo de chiffrement dont on a parlé l'autre soir.

[redacted] en a profiter pour faire un petit tool pour chiffrer/déchiffrer nos messages.

Je l'ai essayé et faut pas dire, ça m'a l'air bien solide.

On peut pas se rater comme la dernière fois ...

EOF :3

Solution

Analyse statique

Décompilation avec Ghidra. Dans l'entry, on trouve la fonction FUN_00402800 qui est le main

void entry(undefined8 param_1,undefined8 param_2,undefined8 param_3) {
  undefined8 in_stack_00000000;
  undefined auStack8 [0x8];
  
  __libc_start_main(FUN_00402800,in_stack_00000000,&stack0x00000008,0x0,0x0,param_3,auStack8);
}

J'en profite pour renommer les paramètres de main pour la lisibilité

int main(int argc, char **argv) {}

On trouve une partie lecture du fichier, on peut en déduire le nom des variables. De plus, quand on est en mode decryption, on skip les 16 premiers octets du fichier :

// [ ... ]
   // ligne 58
    decryption_mode = 0;
    // On déduit facilement que FUN_004023b0 compare les chaînes
    if ((0x3 < argc_) && (uVar2 = FUN_004023b0(argv_[0x3],"--decrypt"), (uVar2 & 0x1) != 0x0)) {
     // [...] lignes osef pour print dans le terminal
      decryption_mode = 1;
    }
  
    // ligne 74
    fseek(file_in, 0, 2);          // Va à la fin du fichier
    file_size = ftell(file_in);    // Recupère l'index où nous sommes
    fseek(file_in,0x0,0x0);        // Retourne au début du fichier
    file_content = malloc(file_size);           // Alloue un buffer de la taille du fichier
    fread(file_content,0x1,file_size,file_in);  // Lis le contenu du fichier et le place dans le buffer
    fclose(file_in);
    if ((decryption_mode & 0x1) != 0x0) {       // Si on est en mode decryption
      file_content = (void *)((long)file_content + 0x10);     // On avance le pointeur de 16 octets (donc on "skip" les 16 premiers octets)
      file_size = file_size - 0x10;   // Réduit la taille du fichier de 16

// [ ... ]

Génération du secret

En dessous, on peut voir quelque chose qui ressemble à de la génération de clé :

// ligne 90
pcVar4 = argv_[0x2];                    // argv[2] c'est le password
lVar3 = FUN_00402370(pcVar4);           // on fait un truc avec le password
local_60 = FUN_00402720(pcVar4,lVar3);  // on fait un second truc avec le password et le résultat de la fonction d'avant
local_68 = FUN_00402450((long)file_content,file_size,(long)local_60); // on fait un dernier truc avec le contenu du fichier + le password

Commençons par FUN_00402370, elle calcule la taille d'une chaîne de caractère, on la renomme en get_size.

size_t FUN_00402370(char *param_1) {
  size_t size;
  char *i;
  
  size = 0x0;
  for (i = param_1; *i != '\0'; i = i + 0x1) {
    size = size + 0x1;
  }
  return size;
}

Ensuite FUN_00402720, elle prend donc en paramètre le password et sa taille. Elle semble générer un secret. Les 8 premiers octets sont générés par FUN_004026c0 (elle fait un sha1 des 2 premiers caractères du password mais en s'en fou au final, donc je skip ce reverse).

Les 8 caractères suivants sont un XOR du password avec la clé M30W. On renomme cette fonction generate_secret.

void * FUN_00402720(char *password,long size) {
  uchar *puVar1;
  char *secret;
  uint j;
  uint i;
  
  puVar1 = FUN_004026c0(password,size);
  secret = (char *)calloc(0x10,0x1);
  for (i = 0x0; i < 0x8; i = i + 0x1) {
    secret[i] = puVar1[i];
  }
  for (j = 0x0; j < 0x8; j = j + 0x1) {
    secret[j + 0x8] = password[(long)(ulong)j % size] ^ "M30W"[j & 0x3];
  }
  return secret;
}

Avec les renommages, on se retrouve avec un code un peu plus compréhensible. On peut d'ailleurs en déduire que FUN_00402450 est la fonction de chiffrement et local_68 le résultat :

// ligne 90
password = argv_[0x2];
password_size = get_size(password);
secret = generate_secret(password,password_size);
encrypted = encrypt((long)file_content,file_size,(long)secret);

Retrouver la clé

// Ligne 117
if ((decryption_mode & 0x1) == 0x0) {    // En mode chiffrement
  useless_var1 = fwrite(secret,0x1,0x10,__s);  // On écrit le secret au début du fichier out, il fait bien 16 octets
}
useless_var2 = fwrite(encrypted,0x1,file_size,__s); // Ajoute le résultat du chiffrement dans le fichier out

On comprend que c'est un chiffrement symétrique puisque la fonction encrypt est utilisée dans les 2 modes.

De plus, on peut retrouver le mot de passe utilisé à partir du fichier chiffré. Il suffit de refaire le XOR sur les 8 derniers octets du secret. En python ça donne :

with open('flag.txt.inc', 'rb') as f:
  flag_encrypted = f.read()
  
password_encrypted = flag_encrypted[8:16]
key = b'M30W'
recovered = bytes([password_encrypted[i] ^ key[i % len(key)] for i in range(len(password_encrypted))])
print(recovered.decode())
# 1H0p31W1

Déchiffrement du flag

On a le password utilisé, on va juste lancer le binaire en mode decrypt avec :

┌──(kali㉿kali)-[~/Documents/CYBN 2024]
└─$ ./m30w_vault flag.txt.inc 1H0p31W1 --decrypt    
-=-=-=-=-=--=-=-=-=-=-=-=-=-=-=--
     SECURE M30W VAULT TECH      
              Unique,            
             Premium ,           
         State of the art,       
           CRYPTAGE :3           
-=-=-=-=-=--=-=-=-=-=-=-=-=-=-=--
Decrypting service selectionned
Writting plaintext to : flag.txt.inc.out
44 bytes written, enjoy
                                                                                                                                                             
┌──(kali㉿kali)-[~/Documents/CYBN 2024]
└─$ cat flag.txt.inc.out                        
CYBN{0n_d1t_ch1ffr3m3nt_aHahAh_c3stM4rr4nt}

Dernière mise à jour

Cet article vous a-t-il été utile ?