Zeecka zeecka

Challenge Accepted !

Quals Sogeti Cyber E-scape 2019 - Prog (493 pts)

Détails du challenge

EventChallengeCategoryPointsSolves
Quals Sogeti Cyber E-scape 2019Challenge Accepted !Programmation49351

Téléchargement : challenge_debug.py - md5: 08522169530a502424d54c8710c6f2b4

Description (originale)

Serez-vous capable de battre la machine ? Celle-ci est programmée pour vous envoyer un challenge chiffré. Votre tâche est de déchiffrer ce challenge et le renvoyer à la machine en moins de 2 secondes.

Une copie du programme vous est fournie.

nc quals.shadow-league.org 5002

Description [EN]

Will you be able to beat the machine? This one is programmed to send you an encrypted challenge. Your task is to decipher this challenge and send it back to the machine in less than 2 seconds.

A copy of the program is provided.

nc quals.shadow-league.org 5002

TL;DR

Il s’agissait d’un bruteforce de hash dû à random.seed(N), où N est dans un intervalle donné.

Méthodologie

Lire le code source

La première chose que j’ai faite a été de regarder le code source python :

import random
import hashlib
import string
import time
from Crypto.Cipher import AES

CHALLENGE = ''.join(random.choice(string.ascii_lowercase + string.ascii_uppercase + string.digits) for _ in range(64))

FLAG = "SCE{DEMO PROGRAM - NOTHING HERE}"

class bcolors:
    OKBLUE = '\033[94m'
    OKGREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    ENDC = '\033[0m'

def pprint(message, level="INFO"):
    if level == "INFO":
        print("[" + bcolors.OKBLUE + "*" + bcolors.ENDC + "] " + message)
    elif level == "WARNING":
        print("[" + bcolors.WARNING + "!" + bcolors.ENDC + "] " + message)
    elif level == "FAIL":
        print("[" + bcolors.FAIL + "X" + bcolors.ENDC + "] " + message)
    elif level == "SUCCESS":
        print("[" + bcolors.OKGREEN + "+" + bcolors.ENDC + "] " + message)

pprint("Random initialization vector", "INFO")
random.seed(random.randint(1,10000))
pprint("Seed generated !", "SUCCESS")

key = 0xffffffff

pprint("Generating secret key", "INFO")
for i in range(10):
   key ^= random.randint(0x00000000, 0xffffffff)
pprint("Secret key generated !", "SUCCESS")


secret = hashlib.sha256(str(key))

# @TODO REMOVE THIS IN PRODUCTION !
pprint("DEBUG - Secret key is %s...%s" % (secret.hexdigest()[:15], secret.hexdigest()[-15:]), "WARNING")

encryption_suite = AES.new(secret.digest(), AES.MODE_CBC, 'LmQHJ6G6QnE5LxbV')
cipher_text = encryption_suite.encrypt(CHALLENGE)

pprint("Encrypted Challenge : " + cipher_text.encode("hex"), "SUCCESS")

current_time = time.time()
USER_CHALLENGE = raw_input("Give me the challenge (2s) > ")

if time.time() - current_time > 2:
    pprint("You were too slow", "FAIL")
else:
    if USER_CHALLENGE == CHALLENGE:
        pprint("Here is your flag : " + FLAG, "SUCCESS")
    else:
        pprint("Incorrect challenge", "FAIL")

Voici une capture de la connexion TCP au challenge :

ncinit.png

On peut voir que le code crée une clé nommée "key" à l’aide de plusieurs appels random et xor. Ensuite, la clé est hashée et renommée "secret" avec sha256 puis partiellement affichée à l’utilisateur (15 premiers caractères et 15 derniers caractères). Enfin, cette clé est utilisée pour chiffrer un challenge secret avec AES et l’IV encryption_suite. Pour résoudre le challenge, l’utilisateur dispose de 2 secondes pour fournir le challenge déchiffré, en connaissant l’algorithme et une partie de la clé.

Identifier les vulnérabilités potentielles

Lors de mon premier regard sur le code, j’ai pensé à une première solution : Étant donné que le secret est le sha256 d’un entier compris entre 0x00000000 et 0xffffffff, il nous suffit de calculer tous les hashes et de choisir le bon lorsqu’on se connecte au challenge. Cette solution aurait été possible mais assez longue.

Lors de mon second regard sur le code, j’ai remarqué le mot-clé ‘seed’ :

random.seed(random.randint(1,10000))

Lorsque la seed est connue, la sortie de random est prévisible et rejouable. Ici, on sait que la seed est un entier compris entre 1 et 10000. On peut donc calculer les 10000 secret (1 par seed) puis choisir le bon lorsqu’on se connecte au challenge !

Voici la génération d’un dictionnaire contenant les 10000 secret avec leur clé partielle comme clé de dictionnaire.

sdico = {}
for i in range(1,10001):  # Generate the 10000 key
    random.seed(i)
    key = 0xffffffff
    for j in range(10):
       key ^= random.randint(0x00000000, 0xffffffff)
    secret = hashlib.sha256(str(key).encode("utf-8"))
    s = secret.hexdigest()[:15]+secret.hexdigest()[-15:]  # get displayed part of the key
    sdico[s] = secret  # add secret in dictionnary with displayed part of the key as dictionnary key

Réponse

Il nous faut maintenant relier le code au challenge. J’ai utilisé pwnlib au lieu de socks. J’ai aussi réalisé que le code tournait en Python2 car Python2 et Python3 ne partagent pas le même PRNG random.

Code final :

# -*- coding:utf-8 -*-

from pwn import *
import random
import hashlib
import string
import time
from Crypto.Cipher import AES

sdico = {}
for i in range(1,10001):  # Generate the 10000 key
    random.seed(i)
    key = 0xffffffff
    for j in range(10):
       key ^= random.randint(0x00000000, 0xffffffff)
    secret = hashlib.sha256(str(key).encode("utf-8"))
    s = secret.hexdigest()[:15]+secret.hexdigest()[-15:]  # get displayed part of the key
    sdico[s] = secret  # add secret in dictionnary with displayed part of the key as dictionnary key

conn = remote("quals.shadow-league.org",5002)  # Connection au serveur distant

conn.recvuntil("key is")
debugcle = conn.recvuntil("\r\n").strip().replace(".","")
print("debugcle = "+str(debugcle))
cle = sdico[debugcle].digest()


conn.recvuntil("Challenge : ")
CHALLENGE = conn.recvuntil("\r\n").strip()
CHALLENGE = CHALLENGE.decode("hex")  # Recup "Encrypted Challenge"


encryption_suite = AES.new(cle, AES.MODE_CBC, "LmQHJ6G6QnE5LxbV")
cipher_text_decode = encryption_suite.decrypt(CHALLENGE)

conn.send(cipher_text_decode+"\r\n")
conn.interactive()

solveseed.png

Flag

SCE{Str0ng_s3eds_are_adv1s3d...}

Zeecka