Link to archived challenge

Category Difficulty Solves Author
reverse easy 20 sudoBash418

Description

We’ve found a password verifier made by 0ph10n, one of the t3l0s operatives. Rumor has it, this password controls access to some of their monitoring services, which would be very valuable to our efforts.

Can you figure out what the correct password is?

Players are given a file to download: password_checker.py.

Analysis

The script we’re given is a basic password checker: it asks for the password and tells you if it’s correct or not.

Here’s the entire script, unedited:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#!/usr/bin/env python3

import string

ALPHABET = string.ascii_lowercase + string.digits

def rot18(data: str) -> str:
    s = ''
    for n in data:
        try:
            s += ALPHABET[(ALPHABET.index(n) + 18) % len(ALPHABET)]
        except ValueError:
            s += n
    return s

def check_password(flag: str) -> bool:
    w = ['aluc9l', '7mnnei9vn', 'njwmqwiw']

    for l in flag:
        if l not in ALPHABET + '{_}':
            return False

    if not flag.startswith("clubeh"):
        return False

    if flag.count("{") != 1:
        return False
    elif flag.count("}") != 1:
        return False

    p = flag[7:-1].split(chr(95))

    if len(p) != 7:
        return False

    if not (p[0].endswith("0s") and p[0].startswith("t3l") and len(p[0]) == 5):
        return False
    if not (p[1].endswith("k35") and p[1].startswith("m4") and len(p[1]) == 5):
        return False
    if p[3] != "m05t":
        return False
    if p[2] != "th3":
        return False
    for i in range(4, 7):
        if rot18(p[i]) != w[i-4]:
            return False

    return True

if __name__ == "__main__":
    while True:
        if check_password(input("Flag: ")):
            print("Yup, that's the flag!")
            break
        else:
            print("Sorry, that's not the flag!")

The main loop is pretty boring: it just calls check_password with our input and responds according to the return value.
check_password is the where the interesting logic happens.

Solution

Let’s break this check_password function down, step by step.

19
20
21
for l in flag:
	if l not in ALPHABET + '{_}':  # ALPHABET is "abcdefghijklmnopqrstuvwxyz0123456789"
		return False

First, it checks each character against a whitelist of values.
The flag must contain only lowercase letters, digits, curly brackets, and underscores.

23
24
25
26
27
28
29
if not flag.startswith("clubeh"):
	return False

if flag.count("{") != 1:
	return False
elif flag.count("}") != 1:
	return False

Next, it does a few checks confirming that the flag follows the standard clubeh{} format.

31
p = flag[7:-1].split(chr(95))

chr(95) is _, and flag[7:-1] returns the inner part of the flag (excluding the clubeh{} part).
So p is assigned a list of the “words” in the flag, split by underscores.

33
34
if len(p) != 7:
	return False

This part tells us there must be 7 words in the flag.

36
37
38
39
if not (p[0].endswith("0s") and p[0].startswith("t3l") and len(p[0]) == 5):
	return False
if not (p[1].endswith("k35") and p[1].startswith("m4") and len(p[1]) == 5):
	return False

Here we have a couple of checks on the 1st and 2nd words.
Both words are 5 characters long, so we can just put their prefixes and suffixes together to reconstruct the words.
Word 1 must be t3l0s and word 2 must be m4k35.

40
41
42
43
if p[3] != "m05t":
	return False
if p[2] != "th3":
	return False

Word 3 is th3 and word 4 is m05t.

44
45
46
for i in range(4, 7):
	if rot18(p[i]) != w[i-4]:
		return False

The last 3 words are encoded with a “rot18” cipher: each character is assigned an integer and rotated by 18, then converted back to a character.
Each word must match with the corresponding entry in the w list:

17
w = ['aluc9l', '7mnnei9vn', 'njwmqwiw']

We could work this out by hand, or we could be lazy and copy-paste the rot18 function to use ourselves:

>>> import string
>>> ALPHABET = string.ascii_lowercase + string.digits
>>> def rot18(data: str) -> str:
...     s = ''
...     for n in data:
...         try:
...             s += ALPHABET[(ALPHABET.index(n) + 18) % len(ALPHABET)]
...         except ValueError:
...             s += n
...     return s
... 
>>> rot18('aluc9l')
's3cur3'
>>> rot18('7mnnei9vn')
'p455w0rd5'
>>> rot18('njwmqwiw')
'51e48e0e'

Putting all the pieces together reveals the flag:

>>> "clubeh{" + '_'.join(["t3l0s", "m4k35", "th3", "m05t", "s3cur3", "p455w0rd5", "51e48e0e"]) + "}"
'clubeh{t3l0s_m4k35_th3_m05t_s3cur3_p455w0rd5_51e48e0e}'

Author’s Notes

This challenge was made at 5 AM on the day of the competition as a last-minute addition to our reverse category.
We realized we didn’t really have any beginner-friendly reverse engineering challenges, so I quickly put together a basic “crackme” that only required a basic understanding of Python.

Looking back, I’m really glad Meaghan bugged me to we added this challenge.
It got 20 solves, double that of kAjfehg n which was the next most-solved challenge in the reverse engineering category.