Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

WKCTF2024 Challenge Writeups

Tech Jul 20 5

Misc Challenges

Signin

The competition was scheduled to start at 10 AM, but upon checking at 9:40 AM, we discovered the start time had been changed to 9 AM. A small oversight on our part.

The challenge was immediately recognizable as a twin-hex cipher. After decoding using an online tool (https://www.calcresult.com/misc/cyphers/twin-hex.html), we obtained a PNG image containing a QR code that directed us to follow an official WeChat account.

Python Bytecode Magic Numbers

This challenge initially seemed puzzling. After researching the first two bytes, we identified them as Python magic numbers. We found a reference table at simryang.tistory.com that contained all the magic numbers.

Using this reference, we developed a script to map each magic number to its corresponding Python version. Interestingly, all entries indicated Python 3.x, suggesting the version numbers were encoded sequentially. We extracted the minor version numbers and concatenated them, but the result wasn't the string "tupper". Converting the number to bytes using n2s yielded a ZIP file instead.

# Python bytecode magic number to version mapping
bytecode_magic = {
    0x02099900: "python 1.0 byte-compiled",
    0x03099900: "python 1.1/1.2 byte-compiled",
    0x892e0d0a: "python 1.3 byte-compiled",
    0x04170d0a: "python 1.4 byte-compiled",
    0x994e0d0a: "python 1.5 byte-compiled",
    0xfcc40d0a: "python 1.6 byte-compiled",
    0xfdc40d0a: "python 1.6 byte-compiled",
    0x87c60d0a: "python 2.0 byte-compiled",
    0x88c60d0a: "python 2.0 byte-compiled",
    0x2aeb0d0a: "python 2.1 byte-compiled",
    0x2beb0d0a: "python 2.1 byte-compiled",
    0x2ded0d0a: "python 2.2 byte-compiled",
    0x2eed0d0a: "python 2.2 byte-compiled",
    0x3bf20d0a: "python 2.3 byte-compiled",
    0x3cf20d0a: "python 2.3 byte-compiled",
    0x45f20d0a: "python 2.3 byte-compiled",
    0x59f20d0a: "python 2.4 byte-compiled",
    0x63f20d0a: "python 2.4 byte-compiled",
    0x6df20d0a: "python 2.4 byte-compiled",
    0x6ef20d0a: "python 2.4 byte-compiled",
    0x77f20d0a: "python 2.5 byte-compiled",
    0x81f20d0a: "python 2.5 byte-compiled",
    0x8bf20d0a: "python 2.5 byte-compiled",
    0x8cf20d0a: "python 2.5 byte-compiled",
    0x95f20d0a: "python 2.5 byte-compiled",
    0x9ff20d0a: "python 2.5 byte-compiled",
    0xa9f20d0a: "python 2.5 byte-compiled",
    0xb3f20d0a: "python 2.5 byte-compiled",
    0xb4f20d0a: "python 2.5 byte-compiled",
    0xc7f20d0a: "python 2.6 byte-compiled",
    0xd1f20d0a: "python 2.6 byte-compiled",
    0xd2f20d0a: "python 2.6 byte-compiled",
    0xdbf20d0a: "python 2.7 byte-compiled",
    0xe5f20d0a: "python 2.7 byte-compiled",
    0xeff20d0a: "python 2.7 byte-compiled",
    0xf9f20d0a: "python 2.7 byte-compiled",
    0x03f30d0a: "python 2.7 byte-compiled",
    0x04f30d0a: "python 2.7 byte-compiled",
    0xb80b0d0a: "python 3.0 byte-compiled",
    0xc20b0d0a: "python 3.0 byte-compiled",
    0xcc0b0d0a: "python 3.0 byte-compiled",
    0xd60b0d0a: "python 3.0 byte-compiled",
    0xe00b0d0a: "python 3.0 byte-compiled",
    0xea0b0d0a: "python 3.0 byte-compiled",
    0xf40b0d0a: "python 3.0 byte-compiled",
    0xf50b0d0a: "python 3.0 byte-compiled",
    0xff0b0d0a: "python 3.0 byte-compiled",
    0x090c0d0a: "python 3.0 byte-compiled",
    0x130c0d0a: "python 3.0 byte-compiled",
    0x1d0c0d0a: "python 3.0 byte-compiled",
    0x1f0c0d0a: "python 3.0 byte-compiled",
    0x270c0d0a: "python 3.0 byte-compiled",
    0x3b0c0d0a: "python 3.0 byte-compiled",
    0x450c0d0a: "python 3.1 byte-compiled",
    0x4f0c0d0a: "python 3.1 byte-compiled",
    0x580c0d0a: "python 3.2 byte-compiled",
    0x620c0d0a: "python 3.2 byte-compiled",
    0x6c0c0d0a: "python 3.2 byte-compiled",
    0x760c0d0a: "python 3.3 byte-compiled",
    0x800c0d0a: "python 3.3 byte-compiled",
    0x8a0c0d0a: "python 3.3 byte-compiled",
    0x940c0d0a: "python 3.3 byte-compiled",
    0x9e0c0d0a: "python 3.3 byte-compiled",
    0xb20c0d0a: "python 3.4 byte-compiled",
    0xbc0c0d0a: "python 3.4 byte-compiled",
    0xc60c0d0a: "python 3.4 byte-compiled",
    0xd00c0d0a: "python 3.4 byte-compiled",
    0xda0c0d0a: "python 3.4 byte-compiled",
    0xe40c0d0a: "python 3.4 byte-compiled",
    0xee0c0d0a: "python 3.4 byte-compiled",
    0xf80c0d0a: "python 3.5.1- byte-compiled",
    0x020d0d0a: "python 3.5.1- byte-compiled",
    0x0c0d0d0a: "python 3.5.1- byte-compiled",
    0x160d0d0a: "python 3.5.1- byte-compiled",
    0x170d0d0a: "python 3.5.2+ byte-compiled",
    0x200d0d0a: "python 3.6 byte-compiled",
    0x210d0d0a: "python 3.6 byte-compiled",
    0x2a0d0d0a: "python 3.6 byte-compiled",
    0x2b0d0d0a: "python 3.6 byte-compiled",
    0x2c0d0d0a: "python 3.6 byte-compiled",
    0x2d0d0d0a: "python 3.6 byte-compiled",
    0x2f0d0d0a: "python 3.6 byte-compiled",
    0x300d0d0a: "python 3.6 byte-compiled",
    0x310d0d0a: "python 3.6 byte-compiled",
    0x320d0d0a: "python 3.6 byte-compiled",
    0x330d0d0a: "python 3.6 byte-compiled",
    0x3e0d0d0a: "python 3.7 byte-compiled",
    0x3f0d0d0a: "python 3.7 byte-compiled",
    0x400d0d0a: "python 3.7 byte-compiled",
    0x410d0d0a: "python 3.7 byte-compiled",
    0x420d0d0a: "python 3.7 byte-compiled",
    0x480d0d0a: "python 3.8 byte-compiled",
    0x490d0d0a: "python 3.8 byte-compiled",
    0x520d0d0a: "python 3.8 byte-compiled",
    0x530d0d0a: "python 3.8 byte-compiled",
    0x540d0d0a: "python 3.8 byte-compiled",
    0x550d0d0a: "python 3.8 byte-compiled",
    0x5c0d0d0a: "python 3.9 byte-compiled",
    0x5d0d0d0a: "python 3.9 byte-compiled",
    0x5e0d0d0a: "python 3.9 byte-compiled",
    0x5f0d0d0a: "python 3.9 byte-compiled",
    0x600d0d0a: "python 3.9 byte-compiled",
    0x610d0d0a: "python 3.9 byte-compiled",
}


def parse_bytecode_versions(input_file):
    """Parse magic numbers and extract version information"""
    with open(input_file, 'r') as f:
        lines = f.readlines()

    version_string = ''
    for line in lines:
        hex_val = int(line.strip(), 16)
        if hex_val in bytecode_magic:
            version_desc = bytecode_magic[hex_val]
            minor_version = version_desc.split(' ')[1].split('.')[1]
            version_string += minor_version
        else:
            print(f"Unknown magic number: {hex_val:0x}")

    return version_string


# Process the magic.txt file
input_path = 'magic.txt'
extracted_version = parse_bytecode_versions(input_path)

print(f"Extracted version string: {extracted_version}")

# Convert to bytes and save as ZIP
import libnum
numeric_value = int(extracted_version)
binary_data = libnum.n2s(numeric_value)

# Write the ZIP file
output_zip = open('flags.zip', 'wb')
output_zip.write(binary_data)
output_zip.close()

The extracted ZIP file contained secret.txt, wich was password-protected. Upon inspection, we found it used fake encryption. By modifying the header byte from 0x07 to 0x00 and the trailer byte from 0x09 to 0x00, we bypassed the protection.

Inside we found a Flask session cookie and a hint indicating the password was weak:

Data:eyJhbGdvcml0aG0iOiJkZXMiLCJmbGFnIjoiVW5BbVBWWTRhdCt2bkJqellPNytUZEZSMmZEYnhScytqQzdsMWt2b2hUMFp4clBDOEJUTWJBPT0ifQ.ZkyTwA.KPlAnhfBH8qMClLyoP6yboafHyw

Using rockyou.txt wordlist, we identified the password as "12312312". Decrypting the DES-encoded session revealed the flag:

WKCTF{N0w_u_Know_th3_fl4sk_Sess1on}

Social Network Analysis

This challenge was initially misleading. While encryption was applied, all data blocks appeared identical, which is characteristic of AES-ECB mode. We treated the entire ciphertext as RGB image data.

This approach yielded a username: M3moryyy. After extensive searching across 12 Chinese and 6 international platforms, no suspicious accounts were found.

Reviewing other teams' writeups revealed the intended solution: the target had a Weibo account, and the flag was hidden in their following list. Unfortunately, we couldn't access Weibo without an account, which cost us this challenge.

Web Challenges

During the competition, multiple teams uploaded files to the servers without proper access controls. Both ThinkPHP instances were compromised by other participants, causing significant disruption as we waited for server recovery.

Qiandao (Daily Check-in)

The flag resided in the root directory, with the source code hinting at a file inclusion vulnerability using the ?file parameter.

http://110.40.35.73:15560/?file=file:///flag

Note: The actual flag string appears to be WFCTF:

WFCTF{Do_You_1ike_WkCTF_have_FUunN}

Easy ThinkPHP

The ThinkPHP challenge was sabotaged during the competition and effectively became identical to the qiandao challenge. The intended solution likely involved either an RCE or deserialization vulnerability in ThinkPHP 6.0.12. One referenced exploit (CSDN article m0_71518346) allowed file uploads but we couldn't execute the uploaded files. Unfortunately, the target server was taken offline while we were actively working on it.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.