174 lines
10 KiB
Markdown
174 lines
10 KiB
Markdown
|
---
|
||
|
layout: post
|
||
|
slug: write-up-ndh-16
|
||
|
status: published
|
||
|
sitemap: true
|
||
|
title: Write-Up NDH16
|
||
|
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...
|
||
|
|
||
|
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é avec les autres membres de l'équipe - merci pour leur aide !
|
||
|
|
||
|
## César
|
||
|
|
||
|
César n'était pas un challenge proposé par la nuit du hack mais par des entreprises présentes sur l'évènement.
|
||
|
On récupérait un fichier texte nommé [communication_vBZvcbm.txt](/assets/code/communication_vBZvcbm.txt) qu'il fallait décoder.
|
||
|
|
||
|
Le nom du challenge nous indiquait que c'était un [chiffrement de César](https://fr.wikipedia.org/wiki/Chiffrement_par_d%C3%A9calage).
|
||
|
Il reste deux inconnus : le décallage et le nombre de symboles.
|
||
|
|
||
|
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.
|
||
|
|
||
|
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é que les caractès étaient encodés sur 7 bits et non 8 (donc 128 possibilités).
|
||
|
|
||
|
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 25.
|
||
|
|
||
|
On avait donc :
|
||
|
|
||
|
```python
|
||
|
decode = (encode + 75) % 128
|
||
|
```
|
||
|
|
||
|
Et voici un script python simple pour réaliser toutes les étapes :
|
||
|
|
||
|
```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):
|
||
|
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()
|
||
|
```
|
||
|
## Ghost in the PowerShell
|
||
|
|
||
|
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é obscurcit, c'est à dire rendu difficilement compréhensible pour un être humain.
|
||
|
|
||
|
Le fichier fournit se nommait [ghostinthepowershell.ps1](/assets/code/ghostinthepowershell.ps1) et commence comme ça :
|
||
|
|
||
|
```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.
|
||
|
|
||
|
Il ne nous reste plus qu'à afficher `z3` !
|
||
|
|
||
|
```powershell
|
||
|
Write-Host ${z3}
|
||
|
```
|
||
|
|
||
|
Le flag était : `ObFU5C4t3D-fl4G-i5-0BfU5c4T3d`.
|
||
|
|
||
|
## Orelsan
|
||
|
|
||
|
On commence par télécharger l'image suivante :
|
||
|
|
||
|
![ndh16-oreilles-sales.png](/assets/images/posts/)
|
||
|
|
||
|
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
|
||
|
```
|
||
|
|
||
|
![ndh16-basique.png](/assets/images/posts/)
|
||
|
|
||
|
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
|
||
|
```
|
||
|
|
||
|
Une ligne, qui n'a pas retenue mon attention au début, est cependant suspecte :
|
||
|
|
||
|
```
|
||
|
Special Instructions : 4D4A57564533324E4B524E474D5A4A544F5256553436544850424844455354494A555A4653364B5A474A4D5445544B584C453255344D5356474E475855514C5A4A555A4555334B5049524354435753584A4A5755365632474E5247573252544B4A56564655323232495247584F574C4E4A553245325243534E42484732534A524A5A4346534D433249354C47595453454E4D32453656324F4E4A4E4649554C324A564C565336434F4E4A4558515753584B4532553652434B4E564E4549554C594C4A57554B4E434E495241584F54544E4A553245365632534E4A4D5855524A544C4A4B464B36535A4B5249584F5432454C4A554655334B4B4E4A4D564F534C324C455A455532535049354954475454324A555A553256434B4E524846495A5A534A555A54434F493D
|
||
|
```
|
||
|
|
||
|
Tout celà ressemble à de l'hexadécimal.
|
||
|
On récupère le tout dans python :
|
||
|
|
||
|
```python3
|
||
|
exif_data = "4D4A57564533324E4B524E474D5A4A544F5256553436544850424844455354494A555A4653364B5A474A4D5445544B584C453255344D5356474E475855514C5A4A555A4555334B5049524354435753584A4A5755365632474E5247573252544B4A56564655323232495247584F574C4E4A553245325243534E42484732534A524A5A4346534D433249354C47595453454E4D32453656324F4E4A4E4649554C324A564C565336434F4E4A4558515753584B4532553652434B4E564E4549554C594C4A57554B4E434E495241584F54544E4A553245365632534E4A4D5855524A544C4A4B464B36535A4B5249584F5432454C4A554655334B4B4E4A4D564F534C324C455A455532535049354954475454324A555A553256434B4E524846495A5A534A555A54434F493D"
|
||
|
|
||
|
d1 = bytearray.fromhex(exif_data)
|
||
|
# On a en ASCII : MJWVE32NKRNGMZJTORVU46THPBHDESTIJUZFS6KZGJMTETKXLE2U4MSVGNGXUQLZJUZEU3KPIRCTCWSXJJWU6V2GNRGW2RTKJVVFU222IRGXOWLNJU2E2RCSNBHG2SJRJZCFSMC2I5LGYTSENM2E6V2ONJNFIUL2JVLVS6CONJEXQWSXKE2U6RCKNVNEIULYLJWUKNCNIRAXOTTNJU2E6V2SNJMXURJTLJKFK6SZKRIXOT2ELJUFU3KKNJMVOSL2LEZEU2SPI5ITGTT2JUZU2VCKNRHFIZZSJUZTCOI=
|
||
|
```
|
||
|
|
||
|
Et là c'est le drame. J'essaye de décoder en base64 et rien. Un peu d'aide exterieure (merci !) me suggère d'essayer la base32. Jamais entendu parler !
|
||
|
|
||
|
```python3
|
||
|
import base64
|
||
|
|
||
|
d2 = base64.b32decode(d1)
|
||
|
# On a en ASCII : bmRoMTZfe3tkNzgxN2JhM2YyY2Y2MWY5N2U3MzAyM2JmODE1ZWJmOWFlMmFjMjZkZDMwYmM4MDRhNmI1NDY0ZGVlNDk4OWNjZTQzMWYxNjIxZWQ5ODJmZDQxZmE4MDAwNmM4OWRjYzE3ZTUzYTQwODZhZmJjYWIzY2JjOGQ3NzM3MTJlNTg2M319
|
||
|
```
|
||
|
|
||
|
Cette fois-ci ça ressemble à de la base64 !
|
||
|
|
||
|
```python3
|
||
|
d3 = base64.b64decode()
|
||
|
# On a en ASCII : ndh16_{{d7817ba3f2cf61f97e73023bf815ebf9ae2ac26dd30bc804a6b5464dee4989cce431f1621ed982fd41fa80006c89dcc17e53a4086afbcab3cbc8d773712e5863}}
|
||
|
```
|
||
|
|
||
|
Le flag était donc : `ndh16_{{d7817ba3f2cf61f97e73023bf815ebf9ae2ac26dd30bc804a6b5464dee4989cce431f1621ed982fd41fa80006c89dcc17e53a4086afbcab3cbc8d773712e5863}}`
|
||
|
|
||
|
Et c'est tout pour cette édition !
|