We start by downloading the provided zip file. It contains a
After inspecting the
We have the following checks on the data:
assert data['name'] == "滌漾火珈豆谷欝寵齦棧"
assert data['email'] == "ĄąŁęțÿżÀÁćæçöśű@POD-ASIA.RECRUITMENT.MARS.TECH"
assert data['phone'] == "껪ㇴ㍂舘덃駱縷긭ㇼ蘭㍑糧곀뇂㍅㋗懲궒"
# Language check
# Python is important and should not be listed at the last place.
for lang in data['languages']:
assert len(str(lang)) > 0
assert str(lang).lower() in PROGRAMMING_LANGUAGES
assert data['languages'][3] != "Python"
And some checks on the qr code:
# Our printer's precision is terrible.
# So, the QR code version cannot be too high.
assert dimension <= (7 * 4 + 17)
# Our printer is experiencing issues with ink flow.
# Must use the highest error correction level.
assert bit_matrix[8][0] == 0
assert bit_matrix[8][1] == 0
assert bit_matrix[dimension - 1][8] == 0
assert bit_matrix[dimension - 2][8] == 0
This looks easy at first, but the dimension check is pain, because if we generate a qr code on some data that fits the checks we will receive a code with double the bits of data than what we can fit in the required dimensions.
So we have to get creative. A normal valid json could look like this:
{
"name": "滌漾火珈豆谷欝寵齦棧",
"email": "ĄąŁęțÿżÀÁćæçöśű@POD-ASIA.RECRUITMENT.MARS.TECH",
"phone": "껪ㇴ㍂舘덃駱縷긭ㇼ蘭㍑糧곀뇂㍅㋗懲궒",
"languages": ["Python", "C++", "C", "Java"]
}
One way to reduce the data is to optimize
{
"name": "滌漾火珈豆谷欝寵齦棧",
"email": "ĄąŁęțÿżÀÁćæçöśű@POD-ASIA.RECRUITMENT.MARS.TECH",
"phone": "껪ㇴ㍂舘덃駱縷긭ㇼ蘭㍑糧곀뇂㍅㋗懲궒",
"languages": [3,3,3,3]
}
We can also remove all whitespaces and newlines. This will reduce the size of the qr code even more, but not enough. We need to find a way to reduce the size of the
Fortunately the used qr code
This is still not enough, but we can encode a part of the email as alphanumeric. This will reduce the size of the email field even more:
segs = [
QrSegment.make_bytes('{name:"'.encode('ascii')),
QrSegment.make_eci(25),
QrSegment.make_bytes('滌漾火珈豆谷欝寵齦棧'.encode('utf-16-be')),
QrSegment.make_eci(18),
QrSegment.make_bytes('",email:"ĄąŁęțÿżÀÁćæçöśű@'.encode('iso-8859-16')),
QrSegment.make_alphanumeric('POD-ASIA.RECRUITMENT.MARS.TECH'),
QrSegment.make_bytes('",languages:[3,3,3,3],phone:"'.encode('iso-8859-16')),
QrSegment.make_eci(25),
QrSegment.make_bytes('껪ㇴ㍂舘덃駱縷긭ㇼ蘭㍑糧곀뇂㍅㋗懲궒"}'.encode('utf-16-be')),
]
This took a while to figure out, but we can now generate a qr code that fits the checks. Unfortunately we can only use low error correction. For high error correction we would need to half the data again. This doesn't seem possible.
We can now generate a qr code with the following python script and automatically check the result:
import demjson3 as json3
import json
from app import check_qrcode, check_result, decode
from qrcodegen import QrCode, QrSegment
def to_svg_str(qr: QrCode, border: int) -> str:
"""Returns a string of SVG code for an image depicting the given QR Code, with the given number
of border modules. The string always uses Unix newlines (\n), regardless of the platform."""
if border < 0:
raise ValueError("Border must be non-negative")
parts: list[str] = []
for y in range(qr.get_size()):
for x in range(qr.get_size()):
if qr.get_module(x, y):
parts.append(f"M{x+border},{y+border}h1v1h-1z")
return f"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 {qr.get_size()+border*2} {qr.get_size()+border*2}" stroke="none">
<rect width="100%" height="100%" fill="#FFFFFF"/>
<path d="{" ".join(parts)}" fill="#000000"/>
</svg>
"""
img_filepath = 'qrcode.png'
# data = {
# "name": "滌漾火珈豆谷欝寵齦棧",
# "email": "ĄąŁęțÿżÀÁćæçöśű@POD-ASIA.RECRUITMENT.MARS.TECH",
# "phone": "껪ㇴ㍂舘덃駱縷긭ㇼ蘭㍑糧곀뇂㍅㋗懲궒",
# "languages": ["C", "C", "C", "C"]
# }
small_json = '{"name":"滌漾火珈豆谷欝寵齦棧","email":"ĄąŁęțÿżÀÁćæçöśű@POD-ASIA.RECRUITMENT.MARS.TECH","phone":"껪ㇴ㍂舘덃駱縷긭ㇼ蘭㍑糧곀뇂㍅㋗懲궒","languages":["C", "C", "C", "C"]}'
segs = [
QrSegment.make_bytes('{name:"'.encode('ascii')),
QrSegment.make_eci(25),
QrSegment.make_bytes('滌漾火珈豆谷欝寵齦棧'.encode('utf-16-be')),
QrSegment.make_eci(18),
QrSegment.make_bytes('",email:"ĄąŁęțÿżÀÁćæçöśű@'.encode('iso-8859-16')),
QrSegment.make_alphanumeric('POD-ASIA.RECRUITMENT.MARS.TECH'),
QrSegment.make_bytes('",languages:[3,3,3,3],phone:"'.encode('iso-8859-16')),
QrSegment.make_eci(25),
QrSegment.make_bytes('껪ㇴ㍂舘덃駱縷긭ㇼ蘭㍑糧곀뇂㍅㋗懲궒"}'.encode('utf-16-be')),
]
qr = QrCode.encode_segments(segs, QrCode.Ecc.LOW, maxversion=8)
svg_str = to_svg_str(qr, 2)
with open('qrcode.svg', 'w') as f:
f.write(svg_str)
# svg to png
import cairosvg
cairosvg.svg2png(url='qrcode.svg', write_to=img_filepath, scale=10)
decode(img_filepath)
result_filepath = img_filepath + '.json'
decode_result = json3.decode_file(result_filepath, encoding='utf-8')
if (not decode_result['success']):
raise Exception("Error")
code_matrix = str(decode_result['codeMatrix'])
code_result = str(decode_result['codeResult'])
bit_matrix = [
[
int(pixel) for pixel in line
]
for line in code_matrix.strip('\n').split('\n')
]
dimension = len(bit_matrix)
print(f'dimension: {dimension}')
check_result(code_result)
check_qrcode(code_matrix)
This will pass all checks except the error correction level. Because only 4 squares are checked we can try painting them in and just pray that the error correction is high enough to correct the errors. This worked for us.
We can now upload the code to the server to get the flag. But unfortunately our qr code is too large, so we have to downscale it. This was an easy fix and we get the following working qr code:
We can upload it using this python script:
import base64
import requests
# API endpoint URL
url = "http://8me39h3vpj4te99k.instance.penguin.0ops.sjtu.cn:18080/api/submit"
# Path to the file
file_path = "code2.png"
# Read the file and encode it in base64
with open(file_path, "rb") as file:
encoded_file = base64.b64encode(file.read()).decode("utf-8")
# Prepend the required header
base64_string = f"data:image/png;base64,{encoded_file}"
# Prepare the form data
form_data = {
"file": base64_string
}
# Send the POST request
response = requests.post(url, data=form_data)
# Print the response
print(f"Status Code: {response.status_code}")
print(f"Response Text: {response.text}")
Running it will give us the flag:
Status Code: 200
Response Text: 0ops{16778f17-d41f-46b5-ad2c-aef0d53aafaa}