Quick Response
Quick Response
Platform: tamuctf | Category: Steganography | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-03-21 | Status: Solved Techniques: finder_pattern_analysis, module_grid_extraction, qr_mask_pattern_removal, xor_deobfuscation
Summary
Task: QR code image that cannot be decoded by standard readers due to applied XOR mask. Solution: Identified checkerboard XOR pattern (QR Mask Pattern 0) by comparing corrupted finder patterns with expected structure, applied inverse XOR to restore valid QR code.
Quick Response — TAMUctf
Description
If you're bored, check this one out!
Given: A PNG image (quick-response.png) that visually resembles a QR code but cannot be decoded by any standard QR reader. The goal is to recover the hidden flag by understanding and reversing the applied transformation.
Analysis
Initial reconnaissance revealed:
- PNG image: 928×928 pixels, 2-bit colormap (palette mode), non-interlaced
- Exactly 2 colors: RGB(20,22,27) dark and RGB(224,226,234) light
- Dimensions: 928 / 32 = 29 modules → QR Version 3 (29×29 grid)
- Each QR module is represented by a 32×32 pixel block
Key observation: The three corner finder patterns (normally 7×7 black-white-black squares) were NOT standard QR finder patterns. All three corners had the same non-standard 7×7 pattern.
Comparing the actual corner pattern with the expected QR finder pattern using XOR revealed a perfect checkerboard pattern: (row + col) % 2. This is QR Mask Pattern 0, one of the 8 standard QR data masking patterns.
The flag hint "timing bits" refers to the alternating black-white timing patterns in QR codes that follow this same checkerboard pattern. The entire QR code was XORed with this mask, corrupting the finder patterns and making it unreadable.
Solution
Step 1: Extract the module grid
Read the PNG and extract a 29×29 binary grid by sampling each 32×32 pixel block:
python read-only • copy allowedfrom PIL import Image import numpy as np from pyzbar.pyzbar import decode img = Image.open('quick-response.png') arr = np.array(img) # Extract 29x29 module grid (each module is 32x32 pixels) modules = np.zeros((29, 29), dtype=int) for r in range(29): for c in range(29): block = arr[r*32:(r+1)*32, c*32:(c+1)*32] modules[r, c] = int(np.mean(block) > 0.5)
Step 2: Undo the checkerboard XOR mask
Apply the inverse transformation (XOR is self-inverse):
python read-only • copy allowed# Undo checkerboard XOR mask (QR Mask Pattern 0) for r in range(29): for c in range(29): modules[r, c] ^= (r + c) % 2
Step 3: Render and decode
Create a clean QR code image and decode it:
python read-only • copy allowed# Render clean QR code scale = 10 qr_img = np.ones((29 * scale, 29 * scale), dtype=np.uint8) * 255 for r in range(29): for c in range(29): if modules[r, c] == 0: qr_img[r*scale:(r+1)*scale, c*scale:(c+1)*scale] = 0 out = Image.fromarray(qr_img) out.save('qr_fixed.png') results = decode(out) print(results[0].data.decode()) # gigem{REDACTED}