If we take a peek under the hood, there’s a lot more code here than in yesterday’s challenge. There’s code to generate the program menu, do the arcane calculations, deal with license keys, and write out the full version of the program if the key is found to be correct.
Up near the top of the file, we see a few interesting pieces that stand out:
username_trial = "FRASER"
bUsername_trial = b"FRASER"
key_part_static1_trial = "picoCTF{1n_7h3_|<3y_of_"
key_part_dynamic1_trial = "xxxxxxxx"
key_part_static2_trial = "}"
key_full_template_trial = key_part_static1_trial + key_part_dynamic1_trial + key_part_static2_trial
Almost a whole flag right there, we now just need to figure out what the xxxxxs are.
The money seems to be at:
def enter_license():
user_key = input("\nEnter your license key: ")
user_key = user_key.strip()
global bUsername_trial
if check_key(user_key, bUsername_trial):
decrypt_full_version(user_key)
else:
print("\nKey is NOT VALID. Check your data entry.\n\n")
So if check_key(user_key, bUsername_trial)
returns true
, we’re in business.
Let’s jump over there.
def check_key(key, username_trial):
global key_full_template_trial
if len(key) != len(key_full_template_trial):
return False
First check: is the key we entered equal in length to key_full_template_trial from up above?
If we recall, key_full_template_trial
just smooshed together all of those flaggy-looking pieces, so right now it’s:
picoCTF{1n_7h3_|<3y_of_xxxxxxxx}
That’s 32 characters. Which means our key needs to be exactly that long.
Ah, but wait, the next check:
# Check static base key part --v
i = 0
for c in key_part_static1_trial:
if key[i] != c:
return False
i += 1
… indicates that not just ANY 32 character string will do; it has to start with exactly the same characters as there are in key_part_static1_trial
, which MEANS the first few characters are picoCTF{1n_7h3_|<3y_of_
That’s a great start!
OK what’s next? A bunch of lines like this:
if key[i] != hashlib.sha256(username_trial).hexdigest()[4]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[5]:
return False
else:
i += 1
This is moving one character at a time through the next part of the key (those Xs), and comparing its value. To what? We recall from above that username_trial is “FRASER”. This code takes that name, runs it through a SHA-256 hash function to turn it into a hexadecimal string, then finds the character in the Nth position. (And actually, N + 1 since indexes in Python, like many other languages, start counting from zero.)
If you run “FRASER” through a tool like SHA256 Online, you’ll see it results in the following string:
92d7ac3c9a0cf9d527a5906540d6c59c80bf8d7ad5bb1885f5f79b5b24a6d387
(Note: SHA-256 hashing will always result in the same output for any given input. This is why it’s extremely important to “salt” your hashes so that they cannot be easily reverse-engineered, e.g. when used for things like one-way encrypting passwords.)
Reading through the remainder of the check_key()
function, you can see that it wants the character in the hash string in the 5th position (“a”), then the 6th (“c”), then the 4th (“7”), … and so on.
Once you’ve figured that out, replace “xxxxx” in key_full_template_trial
with what you derived, and you have both your flag and your license key!