Write-up DEADFACE CTF 2023


Write-up DEADFACE CTF 2023

DEADFACE 2023

La DEADFACE édition 2023 nous est proposée par l’équipe Cyber hacktics et nous expose tout au long de la compétition un fil conducteur ce qui est plutôt rare et très appréciable en CTF.

Mon équipe et moi-même (Les Garagistes) avons fini 72eme.

Je vais vous proposer une correction de quelques épreuves que j’ai pu faire dans diverses catégories. N’hésitez pas à me contacter par mail ou mp Root-me pour de plus amples questions.

Vous pouvez aussi rendre visite au site d’un de mes coéquipiers pour avoir accès à la correction d’autres épreuves de la compétitions : jeanchaput.fr.

Reverse Engineering

Cereal Killer 1

En décompilant l’exécutable avec Ghidra, nous pouvons voir la fonction main :

undefined4 main(undefined param_1)
{
  int iVar1;
  undefined4 uVar2;
  int in_GS_OFFSET;
  char *local_1090;
  char *local_108c;
  char *local_1088;
  char local_1078 [100];
  char local_1014 [4096];
  int local_14;
  undefined1 *local_10;
  
  local_10 = &param_1;
  local_14 = *(int *)(in_GS_OFFSET + 0x14);
  local_108c = "I&_9a%mx_tRmE4D3DmYw_9fbo6rd_aFcRbE,D.D>Y[!]!\'!q";
  puts("Bumpyhassan loves Halloween, so naturally, he LOVES SPOOKY CEREALS!");
  puts("He also happens to be a fan of horror movies from the 1970\'s to the 1990\'s.");
  printf("What is bumpyhassan\'s favorite breakfast cereal? ");
  fgets(local_1014,0xfff,_stdin);
  for (local_1090 = local_1014; *local_1090 != '\0'; local_1090 = local_1090 + 1) {
    *local_1090 = *local_1090 + '\a';
  }
  *local_1090 = '\0';
  iVar1 = memcmp(&DAT_00012039,local_1014,0xe);
  if (iVar1 == 0) {
    puts("You are correct!");
    local_1088 = local_1078;
    for (; *local_108c != '\0'; local_108c = local_108c + 2) {
      *local_1088 = *local_108c;
      local_1088 = local_1088 + 1;
    }
    *local_1088 = '\0';
    printf("flag{%s}\n",local_1078);
  }
  else {
    puts("Sorry, that is not bumpyhassan\'s favorite cereal. :( ");
  }
  uVar2 = 0;
  if (local_14 != *(int *)(in_GS_OFFSET + 0x14)) {
    uVar2 = __stack_chk_fail_local();
  }
  return uVar2;
}

On pourrait résoudre l’épreuve de deux façons :

Première méthode :

On identifie le test qui permet d’afficher le flag :

iVar1 = memcmp(&DAT_00012039,local_1014,0xe);
  if (iVar1 == 0) {
    puts("You are correct!");

DAT_00012039 est égal à : My|p{\x80GRY\LNLY

local_1014 est la variable où est placé l’entrée du programme lorsqu’on l’exécute et qu’il appelle scanf :

fgets(local_1014,0xfff,_stdin)

Un traîtement est fait sur l’entrée local_1014 avant de le comparer :

  for (local_1090 = local_1014; *local_1090 != '\0'; local_1090 = local_1090 + 1) {
    *local_1090 = *local_1090 + '\a';
  }

Dans ce code, on voit que \a est ajouté à chaque caractère de local_1014 pour obtenir la bonne chaîne à comparer, il suffit d’enlever \a, c’est à dire 0x7 à chaque caractères pour obtenir le bon input. Petit programme :

print("".join([chr(i-0x7) for i in bytes.fromhex("4d797c707b804752595c4c4e4c59")]))
# Fruity@KRUEGER

On le rentre dans le programme et on obtient le flag : flag{I_am_REDDY_for_FREDDY!!!}

Seconde méthode :

On identifie la chaîne dans le programme qui lui permet de construire le flag ainsi que le bout de code qui le construit :

...
char *local_1088;
char local_1078 [100];
...
local_108c = "I&_9a%mx_tRmE4D3DmYw_9fbo6rd_aFcRbE,D.D>Y[!]!\'!q";
...
local_1088 = local_1078;
for (; *local_108c != '\0'; local_108c = local_108c + 2) {
    *local_1088 = *local_108c;
    local_1088 = local_1088 + 1;
}
*local_1088 = '\0';
printf("flag{%s}\n",local_1078);
...

On voit la boucle for met un caractère sur deux de la variable local_108c en partant du premier dans la variable local_1088 (== local_1078).

La chaîne sera donc I_am_REDDY_for_FREDDY!!! qu’il faudra mettre dans la chaîne flag{...}

Cereal Killer 2

En décompilant le programme avec Ghidra, dans la fonction main, on peut voir la partie la plus importante du programme :

  fgets(local_2014,0xfff,_stdin);
  decode_str((int)local_2014,0x3f,0x12094,(int)local_1014);
  iVar1 = strncmp(local_1014,"CORRECT!!!!!",0xc);
  if (iVar1 == 0) {
    puts(local_1014);
  }

L’entrée standard est placée dans la variable local_2014. decode_str met visiblement quelque chose dans local_1014 qui va devoir être égal à CORRECT!!!!!.

La fonction en question :

void decode_str(int param_1,int param_2,int param_3,int param_4)

{
  int local_10;
  int local_c;
  
  local_10 = 0;
  local_c = 0;
  while (local_c < param_2) {
    *(byte *)(param_4 + local_c) = *(byte *)(param_3 + local_c) ^ *(byte *)(param_1 + local_10);
    local_c = local_c + 1;
    local_10 = local_10 + 1;
    if (0xb < local_10) {
      local_10 = 0;
    }
  }
  *(undefined *)(param_4 + local_c) = 0;
  return;
}

Basiquement, la fonction met dans param_4 le contenu de param_1 xoré avec la chaîne param_3.

La chaîne passée en troisième argument de decode_str peut se trouver dans la variable DAT_00012094, nous pouvons donc construire un petit programme pour retrouver le flag :

A = "CORRECT!!!!!"
B = "\x08\x3d\x33\x3f\x15\x36\x32\x47\x52\x12\x1b\x65"
print("".join([chr(ord(A[i])^ord(B[i])) for i in range(len(A))]))
# KramPuffs3:D

On rentre KramPuffs3:D en input du programme, le flag s’affiche : flag{GramPa-KRAMpus-Is-Comin-For-Da-Bad-Kids!!!}

Cereal Killer 4

L’épreuve se présente sous la forme d’un .jar, nous le décompilons avec l’outil jadx.

Nous pouvons voir en ouvrant le jar le fichier décompilé CenobBytes.java.

Ce fichier dépend du dossier sheepy aussi fourni dans le jar.

Une solution assez simple est d’extraire le tout et de l’exécuter sans la partie qui arrête le programme si l’input donnée est fausse.

On enlève donc les lignes suivantes :

if (!amhoDecStr.equals(sayongchaAmho)) {
  System.out.println("Sorry, that is not the correct monster / cereal / password.  Please try again.");
  System.exit(0);
}

Le flag s’affiche sous forme d’ascii art, on voit : flag{OctoberIsSharkMonth}

My Daily Macros

En dézippant l’archive fournie, on peut voir un document Excel : HR_List.xlsm.

En prenant en compte le nom de l’épreuve, on déduit que le thème sera les macros contenus dans les documents Office.

On utilise olevba pour extraire les macros :

Sub Deadface()
function Invoke-RandomCode {
    $randomCodeList = @(
        {
            # Random code block 1
            Write-Host "Hello, World!"
            $randomNumber = Get-Random -Minimum 1 -Maximum 100
            Write-Host "Random number: $randomNumber"
        },
        {
            # Random code block 2
            # flag{youll_never_find_this_}
            $randomString = [char[]](65..90) | Get-Random -Count 5 | foreach { [char]$_ }
            Write-Host "Random string: $randomString"
        },
        {
            # Random code block 3
            $currentTime = Get-Date
            Write-Host "Current time: $currentTime"
        }
    )

    $randomIndex = Get-Random -Minimum 0 -Maximum $randomCodeList.Count
    $randomCodeBlock = $randomCodeList[$randomIndex]

    & $randomCodeBlock
}

Invoke -RandomCode

End Sub

Dans ce code PowerShell, nous pouvons voir le flag : flag{youll_never_find_this_}.

Steganography

Fetching secrets

Sur le forum fictif de la compétition, on peut trouver mention d’un programme nommé stegseek. On l’installe et on l’utilise sur l’image de chien fournie dans l’épreuve.

stegseek permet de trouver automatiquement le chiffrement appliqué, on lui fourni aussi une liste de mots afin qu’il puisse trouvé la clé de déchiffrement.

+--> stegseek --crack cyberdog.jpg -wl ~/Téléchargements/rockyou.txt
StegSeek 0.6 - https://github.com/RickdeJager/StegSeek

[i] Found passphrase: "kira")

[i] Extracting to "cyberdog.jpg.out".

L’image cyberdog.jpg.out contient le flag : flag{g00d_dawg_woofw00f}.

Electric Steel

En appliquant le programme binwalk sur l’image fournie dans l’épreuve, nous pouvons voir une archive gzip contenu dans l’image :

+--> binwalk -D='.*' electric-steel.png

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             PNG image, 1232 x 928, 8-bit/color RGB, non-interlaced
2767          0xACF           Zlib compressed data, default compression
1435378       0x15E6F2        TIFF image data, big-endian, offset of first image directory: 8
1435914       0x15E90A        Copyright string: "Copyright (c) 1998 Hewlett-Packard Company"
1467642       0x1664FA        gzip compressed data, from Unix, last modified: 2023-06-04 01:14:27

On la décompresse :

tar -xf 1664FA

On obtient le fichier flag.txt contenant le flag : flag{3L3ctr1c_5t33L_b1G_H41R}

Cryptography

Coin Code

L’image fournie dans l’épreuve indique un chiffrement césar de décalage 18, on l’applique et on trouve :

Next target for me will be Aurora Pharma

flag : flag{Aurora Pharma}

Color Me Impressed

Dans cette page du chat, nous pouvons trouver une image avec 5 couleurs :

labyrinthe

Si on concatène les codes hexa des couleurs (RGB), nous obtenons la suite hexadécimale suivante :

476c403535482375243324744f6e33566d4035680a

On décode la suite :

print(bytes.fromhex("476c403535482375243324744f6e33566d4035680a").decode())
# Gl@55H#u$3$tOn3Vm@5h\n

Le flag : flag{Gl@55H#u$3$tOn3Vm@5h}

Bonus

Off The Rails

On peut déduire à l’intitulé de l’épreuve que le message fourni est chiffré à l’aide du chiffre Rail Fance.

On peut s’aider du site dcode.fr afin de décoder automatiquement le message, on obtient :

ITS NOT RUBY BUT IT IS ON THE RAILS HAVE A SPOOKY HALLOWEEN AND ENJOY
SOME SCARY MOVIES YOUR FLAG IS GHOST AND GHOULS

flag : flag{GHOSTANDGHOULS}