46 views
# 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 ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_33693275532780f0f4b800b07e22385c.png) ::: ## 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é. ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_56b76ff203dbe7a2678f7d0f1553915c.png) 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. ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_4c0fa2a84cf33396517f5d7e21b6f4fa.png) ::: ## 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 ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_eababb40c500b50952489bfc1ebb178a.png) 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 ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_db9849dc8e14587bf886412c8db7085c.png) On peut toujours tenter de le rentrer ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_9fc30cc822240ca2c9bd7daaf6f69b32.png) Pas si simple que ça :smile_cat: On retente en mode manuel ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_46d7450e0f32d0fecb66c4d41c9d0868.png) :::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 ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_5a6d3517ddae1cc6d868851465cf317b.png) devient ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_f83fe28edbe294095564318ea965d669.png) - Hexadecimal ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_ac648b59c93c78440805512fa2464840.png) devient ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_c172f40d559a0e2591479b527e03aba1.png) - rot13 ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_8a3a0df9017b33b69cb0a3c0ad7e7666.png) devient ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_6a59b81c90a86e9e9fbd643232524e11.png) - bigint ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_08635c44ceeecaa95c4d043612321267.png) devient ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_2fb5aa18f857ea392b4644d2dd2882d3.png) - utf-8 ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_1fc13987cf313440204b55ce8bdd2d40.png) devient ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_a107d6f67692aa0d97ed0c3b0a5353cd.png) ::: 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 ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_9ef414724dc8d8ef1a0705a968615b31.png) ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_6a90eb3b597eb17966e6eda8d2bdab9b.png) ::: ## 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. ::: ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_5cb0512caadc06bc695c2368c327d8b2.png) ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_1ffdba707310543b754d9726298392ea.png) :::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 ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_b54c675f62e2e5c8332bafc1d201119a.png) ```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 ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_30121c2c8fc4b28bcaf2c264eebb9f5b.png) 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/ ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_d8f5b005cc896dd752376b167410756e.png) 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} ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_74db67b9c219ef113babc9e61559eb9b.png) ::: <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 ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_c13c017ec0a6edcadb907d8b7f100643.png) :::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** ![](https://minio.apps.education.fr/codimd-prod/uploads/upload_3b4fad2f0367638f354e1693ce893ab0.png) 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 :::