Skip to main content

[LeHack] - Singularity

·3 mins· 0 · 0 ·
CTF Crypto LeHack
JustinType
Author
JustinType
Auditor - Pentester @ Wavestone
Table of Contents
LeHack2025 - This article is part of a series.
Part 3: This Article

Description #

intro.png

We are given the following Python script, which retrieves the :

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()

Analysis #

  • The first part of the script displays ASCII art (although very pretty, it’s useless for solving the challenge).
  • Next, the script asks us to enter a password which will be given to the check_password function.
    • If the password is not the one expected, the script returns the message ‘Error: Wrong password’.
    • If the password is valid, the script returns the message ‘You can validate with leHACK{}’.

Our goal is to find the password verified by the check_password function, as this is the flag!

Let’s take a closer look at the check_password function:

  • It’s an anonymous function (represented by the word lambda) taking a tuple :
    • t[0] → the index of the character in the password
    • t[1] → the character at this position
  • This function performs a mathematical operation for each character in the password:
    • It retrieves the character’s ASCII code (ord(t[1]))
    • Adds its position (+ t[0])
    • And XOR the result with 0x42 (= 66 in decimal)
  • Finally, this function compares the result with an obfuscated string
    • This string is encoded in base64, decoded to give a result in hexadecimal, and then transformed into a byte list.
    • The byte list gives :
    [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]
    

Solving #

The expected password is in this string. To find it, we can use the logic of the function :

(ord(char) + i) ^ 0x42 == valeur_attendue[i]
char is the character at position i in the password, expected_value is the byte list

And reverse it:

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

With a Python script that performs this action for all the bytes in the list and displays the final result, we obtain :

import base64

# Step 1: Decode the string to obtain the byte list
encoded = 'MDExMjMyN2IzNDdiMzgxODI1MjA3OGMyMjkxMTNmYzYzYzM3MzMzZDNiMTljMDE1MWQ='
hex_string = base64.b64decode(encoded).decode()
target_bytes = bytes.fromhex(hex_string)

# Step 2: Invert the formula to find the password
# We have : (ord(c) + i) ^ 0x42 == target_bytes[i]
# Which becomes: 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)

# Step 3: Display password
print(password)

The result is : COn6r4tS_Y0u_Found_leFl@G

🚩 Flag : leHACK{COn6r4tS_Y0u_Found_leFl@G}

LeHack2025 - This article is part of a series.
Part 3: This Article