fixed format

This commit is contained in:
2026-01-14 13:26:49 +01:00
parent ee95f4df4f
commit fcaa6b3543
8 changed files with 16 additions and 85 deletions

View File

@@ -1,69 +0,0 @@
import argparse
from typing import List
import qrcode
from PIL import Image
from pathlib import Path
import random
NUM_PIECES = 3
TILE = 78
def generate_qrcode(data: str) -> Image:
qr = qrcode.QRCode(version=25, box_size=2, error_correction=qrcode.ERROR_CORRECT_L, border=0)
qr.add_data(data)
return qr.make_image()
def cut_pieces(image: Image) -> List[Image]:
pieces = []
for x, y in [(x, y) for x in range(NUM_PIECES) for y in range(NUM_PIECES)]:
pieces.append(image.crop((x * TILE, y * TILE, x * TILE + TILE, y * TILE + TILE)))
return pieces
def scramble_qr_code(payloadList: List[Image], seed: str) -> Image:
height_piece, width_piece = payloadList[0].size
random.seed(seed)
scrambled_width = 3
random.shuffle(payloadList)
new_qr_code = Image.new("RGB", (width_piece * scrambled_width, height_piece * NUM_PIECES))
for x, y in [(x, y) for x in range(scrambled_width) for y in range(NUM_PIECES)]:
piece = payloadList[x + scrambled_width * y]
new_qr_code.paste(piece, (x * width_piece, y * height_piece))
return new_qr_code
def generate_scrambled_qrcode(seed: str, output_dir: Path, name: str):
with open("./secret_message.txt", "r") as secret:
message = secret.read()
qr_code = generate_qrcode(message + seed)
qr_pieces = cut_pieces(qr_code)
scrambled_qr_code = scramble_qr_code(qr_pieces, seed)
output_dir.mkdir(parents=True, exist_ok=True)
scrambled_qr_code.save(output_dir / name)
def main():
parser = argparse.ArgumentParser(description="Generates a scrambled QR code with optional decoy pieces.")
parser.add_argument("seed", type=str, help="The password to scramble the QR code.")
parser.add_argument("output_dir", type=Path, help="Directory where the output will be stored")
parser.add_argument("-o", "--output", type=str, default="secret_message.png", help="The name of the output image file.")
args = parser.parse_args()
generate_scrambled_qrcode(
seed=args.seed,
output_dir=args.output_dir,
name=args.output
)
if __name__ == "__main__":
main()

View File

@@ -16,3 +16,8 @@ Hint:
content: "h4tum{sm4ll_key_spac3s_are_deadly}" content: "h4tum{sm4ll_key_spac3s_are_deadly}"
dynamic: true dynamic: true
files: dynamic # dynamic requires a gen_files script files: dynamic # dynamic requires a gen_files script
dependencies:
type: debian
system: ["python3", "libzbar0"]
python: ["pillow", "qrcode", "pyzbar", "numpy", "tqdm"]
check: "python3 solve.py"

View File

@@ -9,7 +9,7 @@ Consider a toy "QR-encryption" scheme where a message is encoded into
a QR code and then randomized by permuting its tiles with a secret seed. a QR code and then randomized by permuting its tiles with a secret seed.
(a) Under ideal assumptions, explain why this scheme could satisfy (a) Under ideal assumptions, explain why this scheme could satisfy
Kerckhoffs principle. Kerckhoffslibzbar0 principle.
(b) Now assume an attacker can test permutations against the QR format (b) Now assume an attacker can test permutations against the QR format
(find patterns, error correction, alignment markers). Why does this (find patterns, error correction, alignment markers). Why does this
reduce the effective security of the scheme? reduce the effective security of the scheme?

View File

@@ -40,8 +40,8 @@ def scramble_qr_code(payloadList, decoyList, seed):
return new_qr_code return new_qr_code
def generate_scrambled_qrcode(seed, output_dir, decoy, name): def generate_scrambled_qrcode(seed, output_dir, res_dir, decoy, name):
with open("res/secret_message.txt", "r") as secret: with open(res_dir / "secret_message.txt", "r") as secret:
message = secret.read() message = secret.read()
qr_code = generate_qrcode(message + seed) qr_code = generate_qrcode(message + seed)
@@ -49,7 +49,7 @@ def generate_scrambled_qrcode(seed, output_dir, decoy, name):
qr_pieces = cut_pieces(qr_code) qr_pieces = cut_pieces(qr_code)
decoy_pieces = None decoy_pieces = None
if decoy: if decoy:
with open("res/decoy.txt", "r") as decoy_msg: with open(res_dir / "decoy.txt", "r") as decoy_msg:
decoy = decoy_msg.read() decoy = decoy_msg.read()
decoy_qr_code = generate_qrcode(decoy) decoy_qr_code = generate_qrcode(decoy)
@@ -65,6 +65,7 @@ def main():
parser = argparse.ArgumentParser(description="Generates a scrambled QR code with optional decoy pieces.") parser = argparse.ArgumentParser(description="Generates a scrambled QR code with optional decoy pieces.")
parser.add_argument("seed", type=str, help="The password to scramble the QR code.") parser.add_argument("seed", type=str, help="The password to scramble the QR code.")
parser.add_argument("output_dir", type=Path, help="Directory where the output will be stored") parser.add_argument("output_dir", type=Path, help="Directory where the output will be stored")
parser.add_argument("res_dir", type=Path, help="Directory where needed resources are stored")
parser.add_argument("--without-decoy", action="store_false", help="Exclude decoy pieces in the scrambled QR code.") parser.add_argument("--without-decoy", action="store_false", help="Exclude decoy pieces in the scrambled QR code.")
parser.add_argument("-o", "--output", type=str, default="secret_message.png", help="The name of the output image file.") parser.add_argument("-o", "--output", type=str, default="secret_message.png", help="The name of the output image file.")
@@ -73,6 +74,7 @@ def main():
generate_scrambled_qrcode( generate_scrambled_qrcode(
seed=args.seed, seed=args.seed,
output_dir=args.output_dir, output_dir=args.output_dir,
res_dir=args.res_dir,
decoy=args.without_decoy, decoy=args.without_decoy,
name=args.output name=args.output
) )

View File

@@ -12,14 +12,14 @@ test_flag = f"h4tum{{healthy_healthy_healthchecks_{random_bytes}}}"
generate_scrambled_qrcode( generate_scrambled_qrcode(
seed=test_flag, seed=test_flag,
output_dir=Path("tmp/health_check"), output_dir=Path("/tmp/health_check"),
decoy=True, decoy=True,
name=f"{random_bytes}.png" name=f"{random_bytes}.png"
) )
output = main(f"tmp/health_check/{random_bytes}.png", False) output = main(f"/tmp/health_check/{random_bytes}.png", False)
if output and test_flag in output: if output and test_flag in output:
print("Healthy!") print(output)
else: else:
print("Oh oh somethings wrong :()") print("Oh oh somethings wrong :()")

View File

@@ -1,4 +0,0 @@
Wow you solved the challenge!
Meet us at our booth we really want to get in touch with you :D
Show us the flag:

View File

@@ -1,4 +1,3 @@
from logging import disable
from PIL import Image from PIL import Image
from pyzbar.pyzbar import decode from pyzbar.pyzbar import decode
import itertools import itertools
@@ -104,7 +103,7 @@ def solve_combo(combo):
return None return None
def main(img_path="student_fair/secret_message.png", verbose=True): def main(img_path="files/secret_message.png", verbose=True):
img = Image.open(img_path) img = Image.open(img_path)
global new_image global new_image
new_image = Image.new("RGB", (img.height, img.height)) new_image = Image.new("RGB", (img.height, img.height))
@@ -129,10 +128,8 @@ def main(img_path="student_fair/secret_message.png", verbose=True):
result = None result = None
MAX_WORKER = 6
# Use `multiprocessing.Pool` with the number of available cores # Use `multiprocessing.Pool` with the number of available cores
with Pool(MAX_WORKER) as pool: with Pool() as pool:
# `imap_unordered` returns results as they are completed. # `imap_unordered` returns results as they are completed.
# This is a lazy iterator, so it doesn't create all combos at once. # This is a lazy iterator, so it doesn't create all combos at once.
# `tqdm` can still be used to show progress. # `tqdm` can still be used to show progress.