quentin.dufour.io/_posts/2018-07-01-ndh16.md

205 lines
13 KiB
Markdown
Raw Normal View History

2018-07-01 21:03:02 +00:00
---
layout: post
slug: write-up-ndh-16
status: published
sitemap: true
2018-07-02 05:57:37 +00:00
title: Write-Up Wargame Nuit du Hack 16
2018-07-01 21:03:02 +00:00
description: Powershell et compagnie
categories:
- securite
tags:
---
La seizième édition de la Nuit du Hack s'est tenue ce week-end à la cité des sciences à Paris.
Au programme conférences, ateliers et capture de drapeaux. D'ailleurs la capture de drapeau s'appelle wargame, alors que le wargame s'appelle CTF privé. C'est toujours bon à savoir...
2018-07-07 16:58:44 +00:00
Notre nouvelle équipe éphémère de choc (sans logo cette fois-ci) se nommait **Blockchain Cyber Digital**.
Voici le compte-rendu de quelques challenges sur lesquels j'ai travaillés avec les autres membres de l'équipe - merci pour leur aide !
2018-07-01 21:03:02 +00:00
## César
2018-07-02 05:48:15 +00:00
César n'était pas un challenge proposé par la nuit du hack mais par l'entreprise I-Tracing présente sur l'évènement.
2018-07-07 16:58:44 +00:00
Les challenges étaient disponibles à l'adresse suivante : [cyberpunk2048.i-tracing.com](https://cyberpunk2048.i-tracing.com/).
2018-07-01 21:15:15 +00:00
2018-07-01 21:03:02 +00:00
On récupérait un fichier texte nommé [communication_vBZvcbm.txt](/assets/code/communication_vBZvcbm.txt) qu'il fallait décoder.
2018-07-02 05:49:37 +00:00
Le nom du challenge nous indiquait que c'était un [chiffrement de César](https://fr.wikipedia.org/wiki/Chiffrement_par_d%C3%A9calage) qu'il fallait décoder.
2018-07-07 16:58:44 +00:00
Il reste deux inconnues : le décalage et le nombre de symboles.
2018-07-01 21:03:02 +00:00
2018-07-02 05:50:33 +00:00
En général, on a 26 symboles, les 26 lettres de l'alphabet, pas d'accent, la casse (majuscules et minuscules) n'est pas prise en compte. Donc pour un décalage de 5, si on a un Y au départ, on obtient un D (Z, A, B, C, D). Ici, on n'a pas que des caractères de l'alphabet au départ, donc ça se complique.
2018-07-01 21:03:02 +00:00
2018-07-07 16:58:44 +00:00
Nous avons commencé par supposer un chiffrement de césar sur un octet (donc 256 possibilités). Cela nous donnait quelque chose d'à moitié cohérent. Après avoir longuement admiré [une table ASCII](https://fr.wikipedia.org/wiki/American_Standard_Code_for_Information_Interchange#Table_des_128_caract%C3%A8res_ASCII), nous nous sommes rappelés que les caractès étaient encodés sur 7 bits et non 8 (donc 128 possibilités), ce qui nous donnait quelque chose de beaucoup plus cohérent. Nous aurions pu également regarder la répartition des octets de départ et observer qu'ils étaient tous compris entre 0 et 127.
2018-07-01 21:03:02 +00:00
2018-07-02 05:49:37 +00:00
Il nous restait alors à trouver le décalage. Pour ça nous avons tenté toutes les possibilités. Dans notre cas, il s'agissait d'un décalage de 75.
2018-07-01 21:03:02 +00:00
On avait donc :
```python
decode = (encode + 75) % 128
```
2018-07-07 16:32:43 +00:00
Et voici un script python 3 simple pour réaliser toutes les étapes (appuyez sur entrée pour essayer une nouvelle valeur de n) :
2018-07-01 21:03:02 +00:00
```python
with open('communication_vBZvcbm.txt', 'rb') as f:
all_bytes = f.readlines()
all_bytes = all_bytes[0] + all_bytes[1]
for n in range(1,128):
2018-07-07 16:32:43 +00:00
print("\033c")
2018-07-07 16:34:25 +00:00
print("==== try number " + str(n) + " ==== \n\n")
2018-07-01 21:03:02 +00:00
dec = [0] * len(all_bytes)
for i in range(len(all_bytes)):
dec[i] = (all_bytes[i] + n) % 128
print(''.join([chr(x) for x in dec]))
input()
```
2018-07-01 21:12:49 +00:00
2018-07-07 16:15:30 +00:00
*NB: si vous obtenez un `TypeError: cannot concatenate 'str' and 'int' objects` c'est que vous venez d'exécuter le script avec python 2 au lieu de python 3. [Voir le script en python 2](/assets/code/cesar-python2.py).*
2018-07-07 16:13:33 +00:00
2018-07-01 21:12:49 +00:00
Le message une fois déchiffré était le suivant :
> Bonjour,
> Je suis Anna Gonsalves, membre d'APORIA (Alliance de PrOtection contRe les Intelligences Artificielles). Vous ne connaissez probablement pas cette organisation et cela est normal ... elle n'a ete cree qu'en 2048.
> Si vous ne participez pas a ce challenge, voila ce qu'il va se passer !
> Les intelligences artificielles ont pris le controle du monde et ont decide d'exterminer l'humanite.
2018-07-07 16:58:44 +00:00
> Tout a commence en 2020, avec l'arrive d'une toute nouvelle facon de concevoir les intelligences artificielles. Pour que ces dernieres comprennent notre monde, des gigantesques fichiers ont servi de base a ces dernieres pour leur apprendre l'histoire de la planete.
2018-07-01 21:12:49 +00:00
> Son objectif : sauver l'environnement. Pour chaque donnee historique, il y avait des indicateurs associes indiquant les impacts sur la nature, la biodiversite.
> Apres des annees et des annees d'apprentissage, celle-ci trouva un moyen pour le moins radical de resoudre ces problemes : exterminer l'humanite pour mieux se concentrer sur la reparation de l'environnement. Et nous devons bien admettre qu'elle n'est pas loin d'avoir rempli tous ses objectifs.
>
> Enfin bref, nous n'avons pas pu trouver Sarah Connor donc on va dire que vous ferez l'affaire, quiconque que vous soyez. Je vous expliquerai plus tard la suite de notre histoire.
> Voici le code pour continuer votre mission : S05\_Fu7Ur\_Ap0RiA
> Bonne chance !
Le drapeau était donc : `S05_Fu7Ur_Ap0RiA`.
2018-07-01 21:03:02 +00:00
## Ghost in the PowerShell
2018-07-07 16:58:44 +00:00
Ce challenge consiste à retouver le drapeau dans un code écrit dans un langage de script, ici Powershell mais souvent Python ou Javascript et qui a été obscurci, c'est à dire rendu difficilement compréhensible pour un être humain.
2018-07-01 21:03:02 +00:00
2018-07-07 16:58:44 +00:00
Le fichier fourni se nommait [ghostinthepowershell.ps1](/assets/code/ghostinthepowershell.ps1) et commence comme ça :
2018-07-01 21:03:02 +00:00
```powershell
<#████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████#>
${DONTREv`ERs`E`ME1}=($([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('ewAxADEAfQB7ADEAMgB9AHsANgB9AHsAOQB9AHsAMQAwAH0AewA4AH0AewAwAH0AewAyAH0AewA0AH0AewA3AH0AewA1AH0AewAzAH0AewAxAH0A'))
<#████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████#>
) -f($([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('ewAwAH0AewAxAH0A')))-f $([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('LwB3AGEA'))),'tc'),($([Text.Encoding]::
<#████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████#>
Unicode.GetString([Convert]::FromBase64String('ewAwAH0AewAxAH0A')))-f 'gX','cQ'),'h','9W',($([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('ewAwAH0AewAxAH0A'))) -f '?v','=d'),'4w',
```
Sa structure n'est pas très claire aux premiers abords. On commence par enlever ces lignes blanches qui sont en réalité des commentaires. On se retrouve avec beaucoup de texte encodé en base64 au début, puis beaucoup de chaines de caractères formattées du type `"{2}{0}{1}" -f 'b', 'VARi', 'a'` - ce qui renvoie la chaine de caractère suivante `VARiab`, pour probablement former variable plus tard.
En réalité, la structure gloable du code ressemble à ça :
```powershell
${DONTREv`ERs`E`ME1}=( <# beaucoup de code #> );
do {
${01100101010000101} = Read-Host
if (${01100101010000101} -nomatch ${z3}) {
Write-Host ${z4}
${input} = ${z5}
} else {
${input} = "ok"
Write-Host ${z6}
}
} while (${input} -ne "ok")
```
On peut être tenté d'afficher le contenu de la variable `DontReverseMe1`, mais à part une vidéo de Rick Astley vous ne serez pas bien plus avancé.
En réalité c'est la fin du code qui nous intéresse. On se rend compte que l'entrée utilisateur est comparée à la variable `z3`.
Cette variable est définie lors du calcul de la variable `DontReverseMe1` mais son résultat n'est pas stockée dedans.
2018-07-07 16:43:17 +00:00
Il ne nous reste plus qu'à afficher `z3` ! Le meilleur moyen est d'ajouter la ligne suivante juste avant le `do {} while ()`, la boucle qui vous demande le mot de passe en boucle.
2018-07-01 21:03:02 +00:00
```powershell
Write-Host ${z3}
```
2018-07-01 21:12:49 +00:00
Le drapeau était : `ObFU5C4t3D-fl4G-i5-0BfU5c4T3d`.
2018-07-01 21:03:02 +00:00
## Orelsan
On commence par télécharger l'image suivante :
2018-07-01 21:04:08 +00:00
![ndh16-oreilles-sales.png](/assets/images/posts/ndh16-oreilles-sales.png)
2018-07-01 21:03:02 +00:00
Puis on l'examine avec zsteg, un outil bien pratique :
```bash
gem install zsteg
zsteg ndh16-oreilles-sales.png
```
zsteg nous informe que des données ont été ajoutées à la fin de l'image :
```raw
[?] 215426 bytes of extra data after image end (IEND), offset = 0x2e563
extradata:0 .. file: PNG image data, 680 x 510, 8-bit/color RGBA, non-interlaced
...
```
On extrait ces données :
```bash
zsteg -E extradata:0 ndh16-oreilles-sales.png > basiq.png
```
2018-07-01 21:04:08 +00:00
![ndh16-basique.png](/assets/images/posts/ndh16-basique.png)
2018-07-01 21:03:02 +00:00
Cette fois-ci zsteg ne nous donne rien d'intéressant.
Pas d'information cachée non plus en changeant la luminosité ou le contrate de l'image.
Il reste encore un point à étudier, les méta-données de l'image, et particulièrement ses données EXIF.
Pour ça :
```
sudo dnf install /usr/bin/exiftool
exiftool basiq.png
```
2018-07-02 05:56:01 +00:00
Une ligne, qui n'a pas retenue mon attention au début, était cependant la clé du challenge :
2018-07-01 21:03:02 +00:00
```
Special Instructions : 4D4A57564533324E4B524E474D5A4A544F5256553436544850424844455354494A555A4653364B5A474A4D5445544B584C453255344D5356474E475855514C5A4A555A4555334B5049524354435753584A4A5755365632474E5247573252544B4A56564655323232495247584F574C4E4A553245325243534E42484732534A524A5A4346534D433249354C47595453454E4D32453656324F4E4A4E4649554C324A564C565336434F4E4A4558515753584B4532553652434B4E564E4549554C594C4A57554B4E434E495241584F54544E4A553245365632534E4A4D5855524A544C4A4B464B36535A4B5249584F5432454C4A554655334B4B4E4A4D564F534C324C455A455532535049354954475454324A555A553256434B4E524846495A5A534A555A54434F493D
```
2018-07-02 05:56:01 +00:00
C'est de l'hexadécimal. On récupère le tout dans python sous la forme d'un buffer :
2018-07-01 21:03:02 +00:00
2018-07-07 16:35:25 +00:00
```python
2018-07-01 21:03:02 +00:00
exif_data = "4D4A57564533324E4B524E474D5A4A544F5256553436544850424844455354494A555A4653364B5A474A4D5445544B584C453255344D5356474E475855514C5A4A555A4555334B5049524354435753584A4A5755365632474E5247573252544B4A56564655323232495247584F574C4E4A553245325243534E42484732534A524A5A4346534D433249354C47595453454E4D32453656324F4E4A4E4649554C324A564C565336434F4E4A4558515753584B4532553652434B4E564E4549554C594C4A57554B4E434E495241584F54544E4A553245365632534E4A4D5855524A544C4A4B464B36535A4B5249584F5432454C4A554655334B4B4E4A4D564F534C324C455A455532535049354954475454324A555A553256434B4E524846495A5A534A555A54434F493D"
d1 = bytearray.fromhex(exif_data)
2018-07-07 16:41:16 +00:00
print(d1)
# Ce qui affiche : MJWVE32NKRNGMZJTORVU46THPBHDESTIJUZFS6KZGJMTETKXLE2U4MSVGNGXUQLZJUZEU3KPIRCTCWSXJJWU6V2GNRGW2RTKJVVFU222IRGXOWLNJU2E2RCSNBHG2SJRJZCFSMC2I5LGYTSENM2E6V2ONJNFIUL2JVLVS6CONJEXQWSXKE2U6RCKNVNEIULYLJWUKNCNIRAXOTTNJU2E6V2SNJMXURJTLJKFK6SZKRIXOT2ELJUFU3KKNJMVOSL2LEZEU2SPI5ITGTT2JUZU2VCKNRHFIZZSJUZTCOI=
2018-07-01 21:03:02 +00:00
```
2018-07-02 05:56:01 +00:00
J'essaye de décoder l'ASCII du buffer comme de la base64 mais rien. Un peu d'aide exterieure (merci !) me suggère d'essayer la base32 dont je n'avais jamais entendu parler.
2018-07-01 21:03:02 +00:00
2018-07-07 16:35:25 +00:00
```python
2018-07-01 21:03:02 +00:00
import base64
d2 = base64.b32decode(d1)
2018-07-07 16:37:03 +00:00
print(d2)
2018-07-07 16:38:32 +00:00
# Ce qui affiche : bmRoMTZfe3tkNzgxN2JhM2YyY2Y2MWY5N2U3MzAyM2JmODE1ZWJmOWFlMmFjMjZkZDMwYmM4MDRhNmI1NDY0ZGVlNDk4OWNjZTQzMWYxNjIxZWQ5ODJmZDQxZmE4MDAwNmM4OWRjYzE3ZTUzYTQwODZhZmJjYWIzY2JjOGQ3NzM3MTJlNTg2M319
2018-07-01 21:03:02 +00:00
```
2018-07-02 05:56:01 +00:00
On avance, cette fois-ci ça ressemble à de la base64 :
2018-07-01 21:03:02 +00:00
2018-07-07 16:35:25 +00:00
```python
2018-07-01 21:05:21 +00:00
d3 = base64.b64decode(d2)
2018-07-07 16:37:03 +00:00
print(d3)
2018-07-07 16:38:32 +00:00
# Ce qui affiche : ndh16_{% raw %}{{d7817ba3f2cf61f97e73023bf815ebf9ae2ac26dd30bc804a6b5464dee4989cce431f1621ed982fd41fa80006c89dcc17e53a4086afbcab3cbc8d773712e5863}}{% endraw %}
2018-07-01 21:03:02 +00:00
```
2018-07-07 16:41:16 +00:00
[Télécharger le script au complet](/assets/code/basiq.py)
2018-07-01 21:12:49 +00:00
Le drapeau était donc :
2018-07-01 21:08:40 +00:00
```raw
ndh16_{% raw %}{{d7817ba3f2cf61f97e73023bf815ebf9ae2ac26dd30bc804a6b5464dee4989cce431f1621ed982fd41fa80006c89dcc17e53a4086afbcab3cbc8d773712e5863}}{% endraw %}`
```
2018-07-01 21:03:02 +00:00
Et c'est tout pour cette édition !