# Cryptohack Challenges
###### tags: `cryptohack` `writeup`
https://cryptohack.org
:::info
**Activation du conteneur Docker.**
```bash
sudo docker run --name cryptohack -p 127.0.0.1:8888:8888 -it hyperreality/cryptohack:latest
```
Cela met à disposition une session Jupyter avec tout le nécessaire à l'adresse
http://127.0.0.1:8888
très pratique pour peu que l'on ait Docker installé sur la machine :+1:
:::
## Great Snake
:::info
Modern cryptography involves code, and code involves coding. CryptoHack provides a good opportunity to sharpen your skills.
Of all modern programming languages, Python 3 stands out as ideal for quickly writing cryptographic scripts and attacks. For more information about why we think Python is so great for this, please see the FAQ.
Run the attached Python script and it will output your flag.
:::
le script Python est le suivant
```python=
#!/usr/bin/env python3
import sys
# import this
if sys.version_info.major == 2:
print("You are running Python 2, which is no longer supported. Please update to Python 3.")
ords = [81, 64, 75, 66, 70, 93, 73, 72, 1, 92, 109, 2, 84, 109, 66, 75, 70, 90, 2, 92, 79]
print("Here is your flag:")
print("".join(chr(o ^ 0x32) for o in ords))
```
L'execution du script donne directement la solution, sans analyse préalable du code
:::spoiler

:::
## Network attack
:::info
Several of the challenges are dynamic and require you to talk to our challenge servers over the network. This allows you to perform man-in-the-middle attacks on people trying to communicate, or directly attack a vulnerable service. To keep things consistent, our interactive servers always send and receive JSON objects.
Such network communication can be made easy in Python with the pwntools module. This is not part of the Python standard library, so needs to be installed with pip using the command line pip install pwntools.
For this challenge, connect to socket.cryptohack.org on port 11112. Send a JSON object with the key buy and value flag.
:::
On peut se connecter avec netcat (nc), au lieu d'utiliser la bibliothèque pwntools comme préconisé.

Le dialogue indique qu'il faut dialoguer en envoyant les requêtes au format json
:::spoiler
On peut visiblement acheter (buy) des objets. On souhaite obtenir le drapeau (flag), essayons de l'acheter au format json.

:::
## Encoding Challenge
:::info
Now you've got the hang of the various encodings you'll be encountering, let's have a look at automating it.
Can you pass all 100 levels to get the flag?
The 13377.py file attached below is the source code for what's running on the server. The pwntools_example.py file provides the start of a solution.
For more information about connecting to interactive challenges, see the FAQ. Feel free to skip ahead to the cryptography if you aren't in the mood for a coding challenge!
Connect at socket.cryptohack.org 13377
:::
voici le code de 13377.py
```python
#!/usr/bin/env python3
from Crypto.Util.number import bytes_to_long, long_to_bytes
from utils import listener # this is cryptohack's server-side module and not part of python
import base64
import codecs
import random
FLAG = "crypto{????????????????????}"
ENCODINGS = [
"base64",
"hex",
"rot13",
"bigint",
"utf-8",
]
with open('/usr/share/dict/words') as f:
WORDS = [line.strip().replace("'", "") for line in f.readlines()]
class Challenge():
def __init__(self):
self.challenge_words = ""
self.stage = 0
def create_level(self):
self.stage += 1
self.challenge_words = "_".join(random.choices(WORDS, k=3))
encoding = random.choice(ENCODINGS)
if encoding == "base64":
encoded = base64.b64encode(self.challenge_words.encode()).decode() # wow so encode
elif encoding == "hex":
encoded = self.challenge_words.encode().hex()
elif encoding == "rot13":
encoded = codecs.encode(self.challenge_words, 'rot_13')
elif encoding == "bigint":
encoded = hex(bytes_to_long(self.challenge_words.encode()))
elif encoding == "utf-8":
encoded = [ord(b) for b in self.challenge_words]
return {"type": encoding, "encoded": encoded}
#
# This challenge function is called on your input, which must be JSON
# encoded
#
def challenge(self, your_input):
if self.stage == 0:
return self.create_level()
elif self.stage == 100:
self.exit = True
return {"flag": FLAG}
if self.challenge_words == your_input["decoded"]:
return self.create_level()
return {"error": "Decoding fail"}
listener.start_server(port=13377)
```
Tentative de connexion en mode manuel avec netcat permet de voir l'exécution du code ci-dessus

Il faut résoudre le challenge de decodage en utilisant le type d'encodage demandé. Il faut enchainer 100 résolutions réussies pour obtenir le flag.
La lecture du code Python du serveur de challenge fait apparaître le flag

On peut toujours tenter de le rentrer

Pas si simple que ça :smile_cat:
On retente en mode manuel

:::success
Le format de la réponse est bien celui indiqué ci-dessus, il faut rappeler l'encodage suivit de la réponse associée à la clé **decoded**
:::
Il va falloir résoudre le challenge en automatisant la réponse dans un script Python par exemple.
On peut utiliser le code donner avec le challenge notamment pour le décodage et on utilise les infos ci-dessus pour formatter la réponse en json.
```python
from Crypto.Util.number import *
import base64
import codecs
def decode(encoding, challenge_words):
if encoding == "base64":
decoded = base64.b64decode(challenge_words.encode()).decode() # wow so encode
elif encoding == "hex":
decoded = bytes.fromhex(challenge_words).decode('utf-8')
elif encoding == "rot13":
decoded = codecs.decode(challenge_words, 'rot_13')
elif encoding == "bigint":
decoded = long_to_bytes(int(challenge_words,16)).decode('utf-8')
elif encoding == "utf-8":
decoded = bytes(challenge_words).decode('utf-8')
return {"type": encoding, "decoded": decoded}
# exemple reçu b'{"type": "rot13", "encoded": "vgrzf_vapyhqrq_fhesnpr"}\n'
# decode("rot13", "vgrzf_vapyhqrq_fhesnpr") # {'type': 'rot13', 'decoded': 'items_included_surface'}
# decode("utf-8", [111, 114, 103, 97, 110, 105, 122, 97, 116, 105, 111, 110, 95, 118, 97, 95, 102, 111, 111, 116, 98, 97, 108, 108]) # {'type': 'utf-8', 'decoded': 'organization_va_football'}
# decode("bigint","0x637265616d5f646966666572656e7469616c5f726563656976696e67") # {'type': 'bigint', 'decoded': 'cream_differential_receiving'}
```
:::success
Il s'agit pour chaque type d'encodage d'inverser le processus.
- Base 64

devient

- Hexadecimal

devient

- rot13

devient

- bigint

devient

- utf-8

devient

:::
Il faut maintenant intégrer tout cela de manière à envoyer les réponses aux challenges successifs, le code gérant cette partie est
```python=
from Crypto.Util.number import *
from pwn import *
import json
r = remote("socket.cryptohack.org", 13377) # connexion au serveur de challenge
for i in range(100): # on effectue 100 challenges
print(f"challenge {i}") # on affiche le numero du challenge pour connaitre l'avancement
raw_data = r.recvline() # on récupere le challenge en bytes
print(raw_data)
# decodage des bytes reçu avant traitement json
json_string = raw_data.decode('utf-8')
# on récupère les données dans un dictionnaire Python classique
challenge = json.loads(json_string) # on peut maintenant utiliser challenge['type'] et challenge['encoded']
# résolution avec notre fonction decode et envoie du résultat au serveur
p = decode(challenge['type'], challenge['encoded']) # decode() resoud et formatte la réponse
print(p) # pour le debug
r.sendline(json.dumps(p)) # on envoie directemnt le résultat au format json
# on sort de la boucle, on attend la réponse du serveur après 100 challenges réussi
r.recvline()
```
:::spoiler


:::
## Introduction to cryptohack
https://cryptohack.org/courses/intro/enc2/
> When we encrypt something the resulting ciphertext commonly has bytes which are not printable ASCII characters. If we want to share our encrypted data, it's common to encode it into something more user-friendly and portable across different systems.
>
> Hexadecimal can be used in such a way to represent ASCII strings. First each letter is converted to an ordinal number according to the ASCII table (as in the previous challenge). Then the decimal numbers are converted to base-16 numbers, otherwise known as hexadecimal. The numbers can be combined together, into one long hex string.
>
> Included below is a flag encoded as a hex string. Decode this back into bytes to get the flag.
>
> 63727970746f7b596f755f77696c6c5f62655f776f726b696e675f776974685f6865785f737472696e67735f615f6c6f747d
>
```python=
chaineHex = "63727970746f7b596f755f77696c6c5f62655f776f726b696e675f776974685f6865785f737472696e67735f615f6c6f747d"
def hex2octets(chaine):
"""
renvoie une chaine
"""
resultat=""
for i in range(0, len(chaine), 2):
resultat+=(bytes.fromhex(chaine[i:i+2]).decode("utf-8"))
return resultat
print(hex2octets(chaineHex))
```
:::spoiler
crypto{You_will_be_working_with_hex_strings_a_lot}
:::
## Base64
https://cryptohack.org/courses/intro/enc3/
https://fr.wikipedia.org/wiki/Base64
> Another common encoding scheme is Base64, which allows us to represent binary data as an ASCII string using an alphabet of 64 characters. One character of a Base64 string encodes 6 binary digits (bits), and so 4 characters of Base64 encode three 8-bit bytes.
>
> Base64 is most commonly used online, so binary data such as images can be easily included into HTML or CSS files.
>
> Take the below hex string, decode it into bytes and then encode it into Base64.
>
> 72bca9b68fc16ac7beeb8f849dca1d8a783e8acf9679bf9269f7bf
:::info
base64 permet d'encoder la chaine binaire par paquets de 6 bits. Chaque paquet est associée à un des 64 symboles (caractères alphabétiques minuscule et majuscule, chiffres). Le signe = permet de compléter les paquets manquants.
:::


:::info
On regroupe par paquet de 3 octets (soit 6 caractères) dans la chaine initial, on encode avec la fonction bytes.fromhex(). On encode base64 ensuite ce paquet de 3 octets.
:::
<div style="border:1pt solid slateblue; border-radius:5pt; width:15%;
color:slateblue; padding:3px; background-color: lightcyan">Solution</div>
:::spoiler
Recette cyber chef

```python=
import base64
message_encoded = "72bca9b68fc16ac7beeb8f849dca1d8a783e8acf9679bf9269f7bf"
valeur=""
for i in range(0, len(message_encoded), 6):
print(base64.b64encode(bytes.fromhex(message_encoded[i:i+6])))
```
crypto/Base+64+Encoding+is+Web+Safe/
:::
## Bytes and Big Integers
Cryptosystems like RSA works on numbers, but messages are made up of characters. How should we convert our messages into numbers so that mathematical operations can be applied?
The most common way is to take the ordinal bytes of the message, convert them into hexadecimal, and concatenate. This can be interpreted as a base-16/hexadecimal number, and also represented in base-10/decimal.
To illustrate:
message: HELLO
ascii bytes: [72, 69, 76, 76, 79]
hex bytes: [0x48, 0x45, 0x4c, 0x4c, 0x4f]
base-16: 0x48454c4c4f
base-10: 310400273487
:::info
Python's PyCryptodome library implements this with the methods bytes_to_long() and long_to_bytes(). You will first have to install PyCryptodome and import it with from Crypto.Util.number import *
Installer avec la commande : **pip install pycryptodome**
:::
<div style="border:1pt solid slateblue; border-radius:5pt; width:15%;
color:slateblue; padding:3px; background-color: lightcyan">Solution</div>
:::spoiler
```python=
from Crypto.Util.number import *
longNombre = 11515195063862318899931685488813747395775516287289682636499965282714637259206269
hexChaine = long_to_bytes(longNombre)
print(hexChaine)
```
crypto{3nc0d1n6_4ll_7h3_w4y_d0wn}
:::
## You either know, XOR you don't
I've encrypted the flag with my secret key, you'll never be able to guess it.
Remember the flag format and how it might help you in this challenge!
0e0b213f26041e480b26217f27342e175d0e070a3c5b103e2526217f27342e175d0e077e263451150104
<div style="border:1pt solid slateblue; border-radius:5pt; width:15%;
color:slateblue; padding:3px; background-color: lightcyan">Solution 1</div>
:::spoiler
Le flag est de la forme **crypto{FLAG}**
Partant de la on peut supposer que les premiers octets du chiffré correspondent aux octets de crypto{
Ainsi en faisant un XOR entre le clair et le chiffré sur ces octets, on devrait obtenir les 7 premiers octets de la clé.
En utilisant un convertisseur ascii vers Hexa, on obtient les 7 octets de **crypto{** soit :
63727970746f
Outil : https://www.rapidtables.com/convert/number/ascii-to-hex.html

En faisant un XOR avec les 7 premiers octets du chiffré, on obtient les 7 premiers octets de la clé
63727970746f XOR 0e0b213f26041e = 6d79584f526b65
https://cryptii.com/

En faisant de même avec la fin du drapeau, le caractère **'}'** est codé **7D**, on XOR avec le dernier octet du chiffré
7D XOR 04 = 79
La clé se termine donc par **79**
En faisant un XOR entre le chiffré et la clé complète **6d79584f526b6579** (8 octets répliqués pour s'adapter à la longueur du clair), on obtient le clair
crypto{1f_y0u_Kn0w_En0uGH_y0u_Kn0w_1t_4ll}

:::
<div style="border:1pt solid slateblue; border-radius:5pt; width:15%;
color:slateblue; padding:3px; background-color: lightcyan">Solution 2</div>
:::spoiler
Une autre solution en Python, avec la fonction xor() intégrée à pwntools
```python=
"""code from @oushanmu"""
from pwn import xor
flag = bytes.fromhex('0e0b213f26041e480b26217f27342e175d0e070a3c5b103e2526217f27342e175d0e077e263451150104')
print(xor(flag, 'crypto{'.encode())) # oh, it says 'myXORke+y...'
print(xor(flag, 'myXORkey'.encode())) # try this? yay, it works! sometimes simpler is better
```
:::
https://fr.wikipedia.org/wiki/Fonction_OU_exclusif
## Lemur XOR

:::spoiler
On doit faire un XOR d'une image vers l'autre. On peut utiliser le logiciel Gimp par exemple.
Charger la premiere image lemur dans Gimp, puis glisser déposer l'image flag.png dans un autre calc.
Modifier le mode "d'empilage" des calques de **normal** à **Exclusion**

Remarque le mode **soustraction** fonctionne également.
On peut également utiliser l'utilitaire Linux gmic en ligne de commande
gmic lemur.png flag.png -blend xor -o lemurXorFlag.png
:::