worked on the chall
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,3 +1,3 @@
|
||||
.venv
|
||||
__pycache__
|
||||
.venv/
|
||||
__pycache__/
|
||||
*.png
|
||||
|
||||
@@ -11,3 +11,12 @@ The mosaic system encodes emails as QR-Codes and then applies the ultra secure p
|
||||
We found a QR-Code that was not disposed properly in a waste bin.
|
||||
Can you decipher the email?
|
||||
Hint: The mosaic algorithm adds decoy fake data to the QR-Code. It is mega secure after all!
|
||||
|
||||
### How to deploy
|
||||
|
||||
Install all python dependencies:
|
||||
`pip install -r requirements.txt`
|
||||
|
||||
Install zbar:
|
||||
`sudo apt-get install libzbar0` https://pypi.org/project/pyzbar/
|
||||
|
||||
|
||||
69
create_qrcode.py
Normal file
69
create_qrcode.py
Normal file
@@ -0,0 +1,69 @@
|
||||
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()
|
||||
18
description.yml
Normal file
18
description.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
mosaic: # replace with actual short name
|
||||
name: "mega obfuscated secure advanced information communication"
|
||||
author: "cato447" # will be displayed on CTFd
|
||||
# Estimated difficulty
|
||||
difficulty: easy
|
||||
description: "The TAs creating the endterm exam have suffered in the past from leaked drafts.
|
||||
To protect themselves against further leaks they invented the *mega obfuscated secure advanced information communication* (mosaic) system.
|
||||
The mosaic system encodes emails as QR-Codes and then applies the ultra secure protection algorithm.
|
||||
We found a QR-Code that was not disposed properly in a waste bin.
|
||||
Can you decipher the email?
|
||||
Hint:
|
||||
1. The mosaic algorithm adds decoy fake data to the QR-Code. It is mega secure after all!
|
||||
2. Save yourself from the headache of searching for QR-Code libraries: https://github.com/NaturalHistoryMuseum/pyzbar"
|
||||
category: misc
|
||||
flag:
|
||||
content: "h4tum{sm4ll_key_spac3s_are_deadly}"
|
||||
dynamic: true
|
||||
files: dynamic # dynamic requires a gen_files script
|
||||
82
gen_files.py
Normal file
82
gen_files.py
Normal file
@@ -0,0 +1,82 @@
|
||||
import argparse
|
||||
import qrcode
|
||||
from PIL import Image
|
||||
from pathlib import Path
|
||||
import random
|
||||
|
||||
NUM_PIECES = 3
|
||||
TILE = 78
|
||||
|
||||
def generate_qrcode(data: str):
|
||||
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):
|
||||
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, decoyList, seed):
|
||||
height_piece, width_piece = payloadList[0].size
|
||||
random.seed(seed)
|
||||
scrambled_width = 3
|
||||
if decoyList is not None:
|
||||
payloadList += random.sample(decoyList, NUM_PIECES)
|
||||
assert len(payloadList) == 12
|
||||
scrambled_width = 4
|
||||
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, output_dir, decoy, name):
|
||||
with open("res/secret_message.txt", "r") as secret:
|
||||
message = secret.read()
|
||||
|
||||
qr_code = generate_qrcode(message + seed)
|
||||
|
||||
qr_pieces = cut_pieces(qr_code)
|
||||
decoy_pieces = None
|
||||
if decoy:
|
||||
with open("res/decoy.txt", "r") as decoy_msg:
|
||||
decoy = decoy_msg.read()
|
||||
|
||||
decoy_qr_code = generate_qrcode(decoy)
|
||||
decoy_pieces = [piece for id, piece in enumerate(cut_pieces(decoy_qr_code)) if id not in [0, 2, 6]]
|
||||
|
||||
scrambled_qr_code = scramble_qr_code(qr_pieces, decoy_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("--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.")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
generate_scrambled_qrcode(
|
||||
seed=args.seed,
|
||||
output_dir=args.output_dir,
|
||||
decoy=args.without_decoy,
|
||||
name=args.output
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,74 +0,0 @@
|
||||
import qrcode
|
||||
from PIL import Image
|
||||
import random
|
||||
import sys
|
||||
|
||||
from templates import payload, decoy_payload
|
||||
|
||||
NUM_PIECES = 3
|
||||
|
||||
def generate_qrcode(data : str):
|
||||
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):
|
||||
# Get the size of the image
|
||||
width, height = image.size
|
||||
assert width == height
|
||||
|
||||
# Calculate the size of each grid cell
|
||||
cell_width = width // NUM_PIECES
|
||||
cell_height = height // NUM_PIECES
|
||||
|
||||
pieces = []
|
||||
|
||||
# Split the original image into 9 pieces
|
||||
for i in range(NUM_PIECES**2):
|
||||
# Calculate the coordinates of the current grid cell
|
||||
x1 = (i % NUM_PIECES) * cell_width
|
||||
y1 = (i // NUM_PIECES) * cell_height
|
||||
x2 = x1 + cell_width
|
||||
y2 = y1 + cell_height
|
||||
|
||||
# Crop the corresponding part of the original image
|
||||
cropped_piece = image.crop((x1, y1, x2, y2))
|
||||
pieces.append(cropped_piece)
|
||||
|
||||
return pieces
|
||||
|
||||
|
||||
def scramble_qr_code(payloadList, decoyList, password):
|
||||
height_piece, width_piece = payloadList[0].size
|
||||
|
||||
new_qr_code = Image.new("RGB", (width_piece * (NUM_PIECES+1), height_piece * NUM_PIECES))
|
||||
random.seed(password)
|
||||
payloadList += random.sample(decoyList, NUM_PIECES)
|
||||
assert len(payloadList) == 12
|
||||
random.shuffle(payloadList)
|
||||
|
||||
for x,y in [(x,y) for x in range(NUM_PIECES+1) for y in range(NUM_PIECES)]:
|
||||
piece = payloadList[x + (NUM_PIECES+1) * y]
|
||||
new_qr_code.paste(piece, (x * width_piece, y * height_piece))
|
||||
return new_qr_code
|
||||
|
||||
|
||||
def generate_scrambled_qrcode(passphrase):
|
||||
qr_code = generate_qrcode(payload)
|
||||
decoy_qr_code = generate_qrcode(decoy_payload)
|
||||
qr_pieces = cut_pieces(qr_code)
|
||||
decoy_pieces = [piece for id, piece in enumerate(cut_pieces(decoy_qr_code)) if id not in [0,2,6]]
|
||||
|
||||
scrambled_qr_code = scramble_qr_code(qr_pieces, decoy_pieces, passphrase)
|
||||
scrambled_qr_code.save("scrambled_test_qr_code.png")
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("missing password")
|
||||
sys.exit()
|
||||
generate_scrambled_qrcode(sys.argv[1])
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
25
healthcheck.py
Normal file
25
healthcheck.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from solve import main
|
||||
from gen_files import generate_scrambled_qrcode
|
||||
import random
|
||||
from pathlib import Path
|
||||
import string
|
||||
|
||||
visible_chars = string.ascii_letters + string.digits
|
||||
random_bytes = "".join(random.choice(visible_chars) for _ in range(10))
|
||||
|
||||
test_flag = f"h4tum{{healthy_healthy_healthchecks_{random_bytes}}}"
|
||||
|
||||
|
||||
generate_scrambled_qrcode(
|
||||
seed=test_flag,
|
||||
output_dir=Path("tmp/health_check"),
|
||||
decoy=True,
|
||||
name=f"{random_bytes}.png"
|
||||
)
|
||||
|
||||
output = main(f"tmp/health_check/{random_bytes}.png", False)
|
||||
|
||||
if output and test_flag in output:
|
||||
print("Healthy!")
|
||||
else:
|
||||
print("Oh oh somethings wrong :()")
|
||||
@@ -1,51 +0,0 @@
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
from numpy.lib.stride_tricks import sliding_window_view
|
||||
|
||||
def load_bw(path):
|
||||
# Black=1, White=0
|
||||
im = Image.open(path).convert("L")
|
||||
bw = (np.array(im) < 128).astype(np.uint8)
|
||||
return bw
|
||||
|
||||
def finder_template(box_size=2):
|
||||
# 7x7 finder: outer black, inner white (5x5), center black (3x3)
|
||||
F = np.array([
|
||||
[1,1,1,1,1,1,1],
|
||||
[1,0,0,0,0,0,1],
|
||||
[1,0,1,1,1,0,1],
|
||||
[1,0,1,1,1,0,1],
|
||||
[1,0,1,1,1,0,1],
|
||||
[1,0,0,0,0,0,1],
|
||||
[1,1,1,1,1,1,1],
|
||||
], dtype=np.uint8)
|
||||
# upscale to pixels
|
||||
return np.kron(F, np.ones((box_size, box_size), dtype=np.uint8))
|
||||
|
||||
def count_finders(img_path, box_size=2, max_mismatch=0):
|
||||
bw = load_bw(img_path)
|
||||
T = finder_template(box_size=box_size)
|
||||
h, w = T.shape
|
||||
# slide 14x14 window over the image
|
||||
win = sliding_window_view(bw, (h, w))
|
||||
# Hamming distance to template per window
|
||||
mismatches = (win ^ T).sum(axis=(-2, -1))
|
||||
hits = mismatches <= max_mismatch
|
||||
# Optional: suppress overlapping duplicates by non-maximum suppression on exact matches
|
||||
ys, xs = np.where(hits)
|
||||
# Convert to center coordinates
|
||||
centers = [(int(y + h/2), int(x + w/2)) for y, x in zip(ys, xs)]
|
||||
|
||||
# Greedy dedup within ~half a finder width
|
||||
deduped = []
|
||||
r = max(2, h//2)
|
||||
for cy, cx in centers:
|
||||
if all((abs(cy - y) > r) or (abs(cx - x) > r) for y, x in deduped):
|
||||
deduped.append((cy, cx))
|
||||
|
||||
return len(deduped), deduped
|
||||
|
||||
if __name__ == "__main__":
|
||||
n, centers = count_finders("./scrambled_test_qr_code.png", box_size=2, max_mismatch=0)
|
||||
print("Finder count:", n, "centers:", centers, "Exactly three?", n == 3)
|
||||
|
||||
25
res/decoy.txt
Normal file
25
res/decoy.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
From: ops-team@sec.in.tum.de
|
||||
To: exam_archive@sec.in.tum.de
|
||||
Subject: [Draft Item — Secure Systems]
|
||||
|
||||
Restricted Draft — Not Finalized
|
||||
|
||||
Question:
|
||||
Suppose a QR code is divided into equal parts and randomly
|
||||
reordered before printing. Security is claimed because the
|
||||
order cannot be recovered without the exact permutation.
|
||||
|
||||
(a) Explain why relying on this hidden ordering ensures
|
||||
long-term secrecy.
|
||||
(b) Would the presence of alignment markers or error
|
||||
correction change this conclusion? Justify briefly.
|
||||
|
||||
Solution (Internal Notes Only):
|
||||
(a) The scheme is secure since without the original order,
|
||||
the data is mathematically unrecoverable; no structure
|
||||
leaks to the adversary.
|
||||
(b) Alignment markers and redundancy do not help attackers,
|
||||
because the scrambling completely destroys any visible
|
||||
pattern, leaving only random noise.
|
||||
|
||||
Verification Token: h4tum{fake_fake_fake_flag]
|
||||
24
res/mosaic_secret_message
Normal file
24
res/mosaic_secret_message
Normal file
@@ -0,0 +1,24 @@
|
||||
From: hochspezialisiert@sec.in.tum.de
|
||||
To: itsec_examdraft@sec.in.tum.de
|
||||
Subject: [Draft Question — Cryptography]
|
||||
|
||||
Confidential (ONLY FOR AUTHORIZED PERSONNEL)
|
||||
|
||||
Question:
|
||||
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) Under ideal assumptions, explain why this scheme could satisfy
|
||||
Kerckhoffs’ principle.
|
||||
(b) Now assume an attacker can test permutations against the QR format
|
||||
(find patterns, error correction, alignment markers). Why does this
|
||||
reduce the effective security of the scheme?
|
||||
|
||||
Solution (DO NOT DISTRIBUTE):
|
||||
(a) If the algorithm is known and the seed remains secret, only the
|
||||
seed determines security → aligns with Kerckhoffs’ principle.
|
||||
(b) QR codes have strong structural redundancy (finder patterns,
|
||||
error correction). This gives the attacker an oracle to prune wrong
|
||||
seeds quickly → brute force feasible.
|
||||
|
||||
Verification Token:
|
||||
4
res/secret_message.txt
Normal file
4
res/secret_message.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Wow you solved the challenge!
|
||||
Meet us at our booth we really want to get in touch with you :D
|
||||
|
||||
Show us the flag:
|
||||
90
solve.py
90
solve.py
@@ -1,13 +1,13 @@
|
||||
from logging import disable
|
||||
from PIL import Image
|
||||
from pyzbar.pyzbar import decode
|
||||
import itertools
|
||||
from math import perm
|
||||
from tqdm import tqdm
|
||||
import numpy as np
|
||||
from multiprocessing import Pool
|
||||
from math import perm
|
||||
import sys
|
||||
|
||||
NUM_PIECES_WIDTH = 4
|
||||
NUM_PIECES_HEIGHT = 3
|
||||
BOX_SIZE = 2
|
||||
TILE = 78
|
||||
# 7x7 finder, upscaled to 14x14 (black=1, white=0)
|
||||
@@ -40,23 +40,17 @@ def cut_pieces(image):
|
||||
# Get the size of the image
|
||||
width, height = image.size
|
||||
|
||||
# Calculate the size of each grid cell
|
||||
cell_width = width // NUM_PIECES_WIDTH
|
||||
cell_height = height // NUM_PIECES_HEIGHT
|
||||
if width == height:
|
||||
NUM_PIECES_WIDTH = 3
|
||||
else:
|
||||
NUM_PIECES_WIDTH = 4
|
||||
|
||||
NUM_PIECES_HEIGHT = 3
|
||||
|
||||
pieces = []
|
||||
|
||||
# Split the original image into 12 pieces
|
||||
for i in range(NUM_PIECES_WIDTH * NUM_PIECES_HEIGHT):
|
||||
# Calculate the coordinates of the current grid cell
|
||||
x1 = (i % NUM_PIECES_WIDTH) * cell_width
|
||||
y1 = (i // NUM_PIECES_WIDTH) * cell_height
|
||||
x2 = x1 + cell_width
|
||||
y2 = y1 + cell_height
|
||||
|
||||
# Crop the corresponding part of the original image
|
||||
cropped_piece = image.crop((x1, y1, x2, y2))
|
||||
pieces.append(cropped_piece)
|
||||
for x, y in [(x, y) for x in range(NUM_PIECES_WIDTH) for y in range(NUM_PIECES_HEIGHT)]:
|
||||
pieces.append(image.crop((x * TILE, y * TILE, x * TILE + TILE, y * TILE + TILE)))
|
||||
|
||||
return pieces
|
||||
|
||||
@@ -72,7 +66,6 @@ def finder_corner_exact(tile_bw):
|
||||
'TL': tile_bw[0:H, 0:W ],
|
||||
'TR': tile_bw[0:H, TILE-W:TILE ],
|
||||
'BL': tile_bw[TILE-H:TILE, 0:W ],
|
||||
'BR': tile_bw[TILE-H:TILE, TILE-W:TILE ],
|
||||
}
|
||||
for corner, crop in crops.items():
|
||||
if np.array_equal(crop, TEMPLATE):
|
||||
@@ -90,11 +83,33 @@ def three_tiles_with_finders(tiles):
|
||||
break
|
||||
return hits
|
||||
|
||||
def solve_combo(combo):
|
||||
"""
|
||||
Function to be executed by each process in the pool.
|
||||
"""
|
||||
a, b, c, d, e, f = combo
|
||||
# Assuming `new_image` is a base image you can copy or an in-memory image object.
|
||||
# You may need to pass `new_image` to the function or have it loaded by each process.
|
||||
local_image = new_image.copy() # Make a copy to avoid race conditions
|
||||
local_image.paste(a, (78, 0))
|
||||
local_image.paste(b, (0, 78))
|
||||
local_image.paste(c, (78, 78))
|
||||
local_image.paste(d, (156, 78))
|
||||
local_image.paste(e, (78, 156))
|
||||
local_image.paste(f, (156, 156))
|
||||
|
||||
if __name__ == "__main__":
|
||||
img = Image.open("./scrambled_test_qr_code.png")
|
||||
output = decodeQRCode(local_image)
|
||||
if output:
|
||||
return output
|
||||
return None
|
||||
|
||||
|
||||
def main(img_path="student_fair/secret_message.png", verbose=True):
|
||||
img = Image.open(img_path)
|
||||
global new_image
|
||||
new_image = Image.new("RGB", (img.height, img.height))
|
||||
|
||||
|
||||
pieces = cut_pieces(img)
|
||||
corners = three_tiles_with_finders(pieces)
|
||||
|
||||
@@ -110,22 +125,27 @@ if __name__ == "__main__":
|
||||
|
||||
|
||||
pieces = [piece for piece in pieces if piece not in [tile for tile, _ in corners]]
|
||||
assert len(pieces) == 9
|
||||
new_image.show()
|
||||
possible_combos = itertools.permutations(pieces, 6)
|
||||
num_possible_combos = perm(len(pieces), 6)
|
||||
print(f"Testing {num_possible_combos} combinations")
|
||||
|
||||
for a,b,c,d,e,f in tqdm(possible_combos, total=num_possible_combos):
|
||||
new_image.paste(a, (78, 0))
|
||||
new_image.paste(b, (0, 78))
|
||||
new_image.paste(c, (78, 78))
|
||||
new_image.paste(d, (156, 78))
|
||||
new_image.paste(e, (78, 156))
|
||||
new_image.paste(f, (156, 156))
|
||||
output = decodeQRCode(new_image)
|
||||
if output:
|
||||
print(output)
|
||||
result = None
|
||||
|
||||
print(decodeQRCode(img))
|
||||
MAX_WORKER = 6
|
||||
|
||||
# Use `multiprocessing.Pool` with the number of available cores
|
||||
with Pool(MAX_WORKER) as pool:
|
||||
# `imap_unordered` returns results as they are completed.
|
||||
# This is a lazy iterator, so it doesn't create all combos at once.
|
||||
# `tqdm` can still be used to show progress.
|
||||
for result in tqdm(pool.imap_unordered(solve_combo, possible_combos), total=perm(len(pieces), 6), disable=(not verbose)):
|
||||
if result:
|
||||
if verbose:
|
||||
print(f"QR Code decoded: {result}")
|
||||
pool.terminate() # Stop all worker processes
|
||||
break
|
||||
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
55
templates.py
55
templates.py
@@ -1,55 +0,0 @@
|
||||
flag = "h4tum{small_key_spaces_are_deadly}"
|
||||
|
||||
payload = f"""
|
||||
From: hochspezialisiert@sec.in.tum.de
|
||||
To: itsec_examdraft@sec.in.tum.de
|
||||
Subject: [Draft Question — Cryptography]
|
||||
|
||||
Confidential (ONLY FOR AUTHORIZED PERSONNEL)
|
||||
|
||||
Question:
|
||||
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) Under ideal assumptions, explain why this scheme could satisfy
|
||||
Kerckhoffs’ principle.
|
||||
(b) Now assume an attacker can test permutations against the QR format
|
||||
(find patterns, error correction, alignment markers). Why does this
|
||||
reduce the effective security of the scheme?
|
||||
|
||||
Solution (DO NOT DISTRIBUTE):
|
||||
(a) If the algorithm is known and the seed remains secret, only the
|
||||
seed determines security → aligns with Kerckhoffs’ principle.
|
||||
(b) QR codes have strong structural redundancy (finder patterns,
|
||||
error correction). This gives the attacker an oracle to prune wrong
|
||||
seeds quickly → brute force feasible.
|
||||
|
||||
Verification Token: {flag}"""
|
||||
|
||||
decoy_payload = """
|
||||
From: ops-team@sec.in.tum.de
|
||||
To: exam_archive@sec.in.tum.de
|
||||
Subject: [Draft Item — Secure Systems]
|
||||
|
||||
Restricted Draft — Not Finalized
|
||||
|
||||
Question:
|
||||
Suppose a QR code is divided into equal parts and randomly
|
||||
reordered before printing. Security is claimed because the
|
||||
order cannot be recovered without the exact permutation.
|
||||
|
||||
(a) Explain why relying on this hidden ordering ensures
|
||||
long-term secrecy.
|
||||
(b) Would the presence of alignment markers or error
|
||||
correction change this conclusion? Justify briefly.
|
||||
|
||||
Solution (Internal Notes Only):
|
||||
(a) The scheme is secure since without the original order,
|
||||
the data is mathematically unrecoverable; no structure
|
||||
leaks to the adversary.
|
||||
(b) Alignment markers and redundancy do not help attackers,
|
||||
because the scrambling completely destroys any visible
|
||||
pattern, leaving only random noise.
|
||||
|
||||
Verification Token: h4tum{fake_fake_fake_flag]"""
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
from locate_finders import count_finders
|
||||
from generate_qrcode import generate_scrambled_qrcode
|
||||
import random
|
||||
from PIL import Image
|
||||
|
||||
for i in range(1000):
|
||||
print(f"\rTesting {i}", end="")
|
||||
generate_scrambled_qrcode(random.randbytes(32))
|
||||
finders, _ = count_finders("./scrambled_test_qr_code.png")
|
||||
if finders != 3:
|
||||
print("found anomaly")
|
||||
img = Image.open("./scrambled_test_qr_code.png")
|
||||
img.show()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user