WKCTF2024 Challenge Writeups
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.