Aller au contenu

[LeHack] - Singularity

·3 mins· 0 · 0 ·
CTF Crypto LeHack
JustinType
Auteur
JustinType
Auditeur - Pentester chez Wavestone
Sommaire
LeHack2025 - Cet article fait partie d'une série.
Partie 3: Cet article

Description #

intro.png

Le script Python suivant nous est donné, celui-ci permet de retrouver le flag :

import base64

def banner():
    BANNER = """CgogICAgICAgICAgICAgICAgICAuYW5kQUhIQWJubi4KICAgICAgICAgICAgICAgLmFBSEhIQUFV
    VUFBSEhIQW4uCiAgICAgICAgICAgICAgZEhQXn4iICAgICAgICAifl5USGIuCiAgICAgICAgLiAg
    IC5BSEYgICAgICAgICAgICAgICAgWUhBLiAgIC4KICAgICAgICB8ICAuQUhIYi4gICAgICAgICAg
    ICAgIC5kSEhBLiAgfAogICAgICAgIHwgIEhIQVVBQUhBYm4gICAgICBhZEFIQUFVQUhBICB8CiAg
    ICAgICAgSSAgSEZ+Il9fX19fICAgICAgICBfX19fIF1ISEggIEkgICAgCiAgICAgICBISEkgSEFQ
    SyIifl5ZVUhiICBkQUhISEhISEhISEggSUhICiAgICAgICBISEkgSEhIRCZndDsgLmFuZEhIICBI
    SFVVUF5+WUhISEggSUhIICAgICAgIFdlbGNvbWUgYmFjayB0byB0aGUgc2luZ3VsYXJpdHkgIQog
    ICAgICAgWVVJIF1ISFAgICAgICJ+WSAgUH4iICAgICBUSEhbIElVUAogICAgICAgICIgIGBISyAg
    ICAgICAgICAgICAgICAgICBdSEgnICAiICAgICAgICAgICAgICAgQ2FuIHlvdSBmaW5kIG15IHBh
    c3N3b3JkID8KICAgICAgICAgICAgVEhBbi4gIC5kLmFBQW4uYi4gIC5kSEhQCiAgICAgICAgICAg
    IF1ISEhIQUFVUCIgfn4gIllVQUFISEhIWwogICAgICAgICAgICBgSEhQXn4iICAuYW5ubi4gICJ+
    XllISCcKICAgICAgICAgICAgIFlIYiAgICB+IiAiIiAifiAgICBkSEYKICAgICAgICAgICAgICAi
    WUFiLi5hYmRISGJuZGJuZEFQIgogICAgICAgICAgICAgICBUSEhBQWIuICAuYWRBSEhGCiAgICAg
    ICAgICAgICAgICAiVUhISEhISEhISEhVIgogICAgICAgICAgICAgICAgICBdSEhVVUhISEhISFsK
    ICAgICAgICAgICAgICAgIC5hZEhIYiAiSEhISEhibi4KICAgICAgICAgLi5hbmRBQUhISEhISGIu
    QUhISEhISEhBQWJubi4uCiAgICAubmRBQUhISEhISFVVSEhISEhISEhISFVQXn4ifl5ZVUhISEFB
    Ym4uCiAgICAgICJ+XllVSEhQIiAgICJ+XllVSEhVUCIgICAgICAgICJeWVVQXiIKICAgICAgICAg
    ICAiIiAgICAgICAgICJ+fiIK"""
    print(base64.b64decode(BANNER).decode())

def check_password(password):
    singularity = lambda t: (ord(t[1])+t[0])^0x42 == bytes.fromhex(
      base64.b64decode('MDExMjMyN2IzNDdiMzgxODI1MjA3OGMyMjkxMTNmYzYzYzM3MzMzZDNiMTljMDE1MWQ=').decode()
    )[t[0]]
    return all(map(singularity, enumerate(password)))

def main():
    banner()
    password = input("Enter the password> ")
    if check_password(password):
        print("You can validate with leHACK{{{}}}".format(password))
    else:
        print("Error: Wrong password")

if __name__ == '__main__':
    main()

Analyse #

  • La première partie du script affiche un ASCII art (bien que très joli, celui-ci est inutile pour la résolution du challenge)
  • Ensuite le script nous demande d’entrer un mot de passe qui sera donné à la fonction check_password
    • Si le mot de passe n’est celui attendu, le script renvoi le message ‘Error: Wrong password
    • Si le mot de passe est valide, le script renvoi le message ‘You can validate with leHACK{}

Notre but est donc de retrouver le mot de passe vérifié par la fonction check_password car celui-ci est le flag !

Analysons plus en profondeur la fonction check_password :

  • C’est une fonction anonyme (représenté par le mot lambda) prenant un tuple :
    • t[0] → l’index du caractère dans le mot de passe
    • t[1] → le caractère à cette position
  • Cette fonction réalise une opération mathématique pour chaque caractère du mot de passe renseigné :
    • Elle récupère le code ASCII du caractère (ord(t[1]))
    • Additionne sa position (+ t[0])
    • Et XOR le résultat avec 0x42 (= 66 en décimal)
  • Enfin cette fonction va comparer le résultat obtenu avec une chaîne obfusquée
    • Cette chaîne est encodé en base64, une fois décodée elle donne un résultat en hexadécimal, pour enfin être transformée en liste d’octets
    • La liste d’octets donne :
    [1, 18, 50, 123, 52, 123, 56, 24, 37, 32, 120, 194, 41, 17, 63, 198, 60, 55, 51, 61, 59, 25, 192, 21, 29]
    

Résolution #

Le mot de passe attendu se trouve donc dans cette chaîne, afin de le retrouver on peut prendre la logique de la fonction :

(ord(char) + i) ^ 0x42 == valeur_attendue[i]
char est le caractère à la position i dans le mot de passe, valeur_attendue est la liste d’octets

Et l’inverser :

ord(char) = (valeur_attendue[i] ^ 0x42) - i

Avec un script Python qui effectue cette action pour tous les octets de la liste et qui affiche le résultat finale, on obtient :

import base64

# Étape 1 : Décoder la chaîne pour obtenir la liste des octets
encoded = 'MDExMjMyN2IzNDdiMzgxODI1MjA3OGMyMjkxMTNmYzYzYzM3MzMzZDNiMTljMDE1MWQ='
hex_string = base64.b64decode(encoded).decode()
target_bytes = bytes.fromhex(hex_string)

# Étape 2 : Inverser la formule pour retrouver le mot de passe
# On a : (ord(c) + i) ^ 0x42 == target_bytes[i]
# Qui devient : ord(c) = (target_bytes[i] ^ 0x42) - i

password_chars = []
for i, val in enumerate(target_bytes):
    c = (val ^ 0x42) - i
    password_chars.append(chr(c))

password = ''.join(password_chars)

# Etape 3 : afficher le mot de passe
print(password)

On obtient le résultat : COn6r4tS_Y0u_Found_leFl@G

🚩 Flag : leHACK{COn6r4tS_Y0u_Found_leFl@G}

LeHack2025 - Cet article fait partie d'une série.
Partie 3: Cet article