withopen("byteme.pyc", "rb") as file: file.seek(16) print(dis.dis(marshal.load(file)))
This challange comes with a compiled python script. There are nice decompilers for this, but they are mostly lacking support for really new python versions. So, not surprising, the decompiler doesn’t want to work.
From the hexdump, we can see that the magic number (first four bytes are 0a0d0df0). We can see that this is corresponding to Python 3.13a1 3568. So lets grab the correct python version.
with open("byteme.pyc", "rb") as file: file.seek(16) print(dis.dis(marshal.load(file)))
Running this (with python 3.13), gives us a somewhat lenghty result. We can reverse this by hand (or we can adapt pycdc to support the new bytecodes). Either way, we find there are three functions that are called and every one of them gives us one part of the flag. Lets start at the top.
# make function for "crackme" 4 22 LOAD_CONST 3 (<code object crackme at 0x558a3158ccd0, file "byteme.py", line 4>) 24 MAKE_FUNCTION 26 STORE_NAME 3 (crackme)
# make function for "solveme" 55 28 LOAD_CONST 4 (<code object solveme at 0x558a315b3210, file "byteme.py", line 55>) 30 MAKE_FUNCTION 32 STORE_NAME 4 (solveme)
# make function for "breakme" 142 34 LOAD_CONST 5 (<code object breakme at 0x558a31538040, file "byteme.py", line 142>) 36 MAKE_FUNCTION 38 STORE_NAME 5 (breakme)
print("Welcome Warrior! You have made it till here") print("This is where best of the best have fallen prey to the fate") print()
print("It is written that only the true Thalor can get The sword of Eldoria") print("Do you have what it takes to be Thalor?") print("Prove your mettle by bringing the sword out of the castle") print()
print("Go on! unlock the castle with XEKLEIDOMA spell") print()
spell = input("> ") print()
if len(spell.strip()) != 12 or md5(spell.strip().encode()).hexdigest() != "9ce86143889d80b01586f8a819d20f0c": print("You are not THE ONE") print("True Thalor is a master of sorcery") print("Ground beneath you opens up and you fall into the depths of hell") exit()
print("The door is opened!") print("You surely mastered sorcery") print()
time.sleep(3)
return spell
So we need to find a input with length of 12 characters and with the matching hashsum. Since we can assume that we need to pass in the flag, we know the first 6 characters already due to the flag format: pearl{. The remaining 6 characters we can bruteforce:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
import hashlib import itertools
def generate_permutations(): characters = 'abcdefghijklmnopqrstuvwxyz0123456789_' permutations = itertools.product(characters, repeat=6) return (''.join(p) for p in permutations)
if __name__ == "__main__": for permutation in generate_permutations(): md5_hash = calculate_md5("pearl{" + permutation) if md5_hash in "9ce86143889d80b01586f8a819d20f0c": print(permutation) exit()
Running this, gives us the magic spell pearl{e4sy_p. The second function is solveme and the reconstructed code looks like the following:
print("As you walk in, you see a spectral figure Elyrian, the Guardian of Souls") print("He speaks to you in a voice that echoes through the chamber") print()
print('"Brave warrior, before you lies the next trial on your path. Answer my riddle, and prove your worthiness to continue your quest."') print('\n"I am a word of ten, with numbers and letters blend,\nUnravel me, and secrets I\'ll send.\nThough cryptic in sight, I hold the code tight,\nUnlock my mystery with wit and might."\n')
print("You have proven your `wit and might`") print("Elyrian, the Guardian of Souls, bows to you") print("You have unlocked the next chamber") print()
time.sleep(4)
return "".join([chr(x) for x in answer])
This time, the user input needs to be 10 characters and we have a whole lot of constraints that need to be fulfilled. Constraints call for SAT, so we use z3 to get the answer.
if solver.check() == sat: model = solver.model() print("Solution found:") for i in range(10): print(f"{chr(model[answer[i]].as_long())}", end="") else: print("No solution found")
Running this gives us the second part of the flag: 34sy_byt3c. One part to go… Moving on to breakme. The reconstructed code looks like this:
sword = sword.split("\n") for line in sword: print(line) time.sleep(0.1)
print() print("There it is! The sword of Eldoria") print("Break it's shackles and show that you are the Thalor") print()
chain = input("> ")
best = [117, 84, 87, 108, 59, 85, 66, 71, 71, 30, 16] mod = [] plier = 69
for i in range(len(chain)): mod.append(ord(chain[i]) ^ plier) plier = ord(chain[i])
if mod == best: print("Oh! True Thalor, you have broken the shackles") print("You are the chosen one") print("I kneel before you") print("Go on! Take the sword and fulfill your destiny") print()
time.sleep(2)
return chain
print("You are not worthy") print("The fate has you in it's grip") print("You will be forgotten in the sands of time") exit()
The input is processed with some xor magic and we check if the result is identical with the values in best. This can be reversed easily: