Points: 30
Tags: picoCTF 2021, Reverse Engineering
Author: SYREAL
Description:
keygenme-trial.py
Hints:
(None)
Challenge link: https://play.picoctf.org/practice/challenge/121
Let's start by looking at the beginning of the script
#============================================================================#
#============================ARCANE CALCULATOR===============================#
#============================================================================#
import hashlib
from cryptography.fernet import Fernet
import base64
# GLOBALS --v
arcane_loop_trial = True
jump_into_full = False
full_version_code = ""
username_trial = "GOUGH"
bUsername_trial = b"GOUGH"
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
<---snip--->
There we have what seems to be most parts of the flag, apart from a unknown dynamic part.
Further down we have a menu function called menu_trial
def menu_trial():
print("___Arcane Calculator___\n\n\
Menu:\n\
(a) Estimate Astral Projection Mana Burn\n\
(b) [LOCKED] Estimate Astral Slingshot Approach Vector\n\
(c) Enter License Key\n\
(d) Exit Arcane Calculator")
choice = input("What would you like to do, "+ username_trial +" (a/b/c/d)? ")
if not validate_choice(choice):
print("\n\nInvalid choice!\n\n")
return
if choice == "a":
estimate_burn()
elif choice == "b":
locked_estimate_vector()
elif choice == "c":
enter_license()
elif choice == "d":
global arcane_loop_trial
arcane_loop_trial = False
print("Bye!")
else:
print("That choice is not valid. Please enter a single, valid \
lowercase letter choice (a/b/c/d).")
Let's check the enter_license
function next
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")
It calls the check_key
function so let's investigate that
def check_key(key, username_trial):
global key_full_template_trial
if len(key) != len(key_full_template_trial):
return False
else:
# Check static base key part --v
i = 0
for c in key_part_static1_trial:
if key[i] != c:
return False
i += 1
# TODO : test performance on toolbox container
# Check dynamic part --v
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
if key[i] != hashlib.sha256(username_trial).hexdigest()[3]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[6]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[2]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[7]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[1]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[8]:
return False
return True
Ah, here we have the dynamic part. It checks a number of hash digits, in some strange order, of the trial user name.
Let's make a small script to compile the complete flag
#!/usr/bin/python
import hashlib
# Static parts
key_part_static1_trial = "picoCTF{1n_7h3_|<3y_of_"
key_part_static2_trial = "}"
# Dynamic part - same as in the check_key function
bUsername_trial = b"GOUGH"
hash = hashlib.sha256(bUsername_trial).hexdigest()
dynamic = hash[4] + hash[5] + hash[3] + hash[6] + hash[2] + hash[7] + hash[1] + hash[8]
flag = "picoCTF{1n_7h3_|<3y_of_" + dynamic + "}"
print(flag)
Then, make sure the script is executable and run it to get the flag
┌──(kali㉿kali)-[/mnt/…/picoCTF/picoCTF_2021/Reverse_Engineering/keygenme-py]
└─$ chmod +x solve.py
┌──(kali㉿kali)-[/mnt/…/picoCTF/picoCTF_2021/Reverse_Engineering/keygenme-py]
└─$ ./solve.py
picoCTF{<REDACTED>}