Browse CTFs New CTF Sign in

QR Code Forensics: Error Correction Analysis and Partially Damaged Payload Reconstruction

log_analysis_siem Difficulty 1–5 30 min certifiable

Theory

Why This Matters

QR codes (Quick Response codes) are 2D matrix barcodes standardised as ISO/IEC 18004. They appear in CTF challenges as image files containing encoded flags, and they appear in real-world security contexts as attack vectors: malicious QR codes in phishing campaigns redirect victims to credential-harvesting sites, and QR codes in physical penetration tests have been used to deliver malware. Analysts need to know both how to decode standard QR codes programmatically and how to recover data from damaged or deliberately obstructed codes. The built-in error correction in QR codes — a major design feature — is also the feature that CTF authors exploit to create "damaged QR" challenges where part of the code is obscured or corrupted.

Core Concept

A QR code is a square grid of black and white modules (pixels). Its structure includes: three finder patterns (7×7 nested squares in three corners, used for orientation detection), timing patterns (alternating black/white stripes connecting the finder patterns), alignment patterns (smaller nested squares in larger versions), format information strips (encoding error correction level and mask pattern), and the data region (the remaining modules encoding the actual payload).

QR codes support four error correction levels: L (low, ~7% damage recovery), M (medium, ~15%), Q (quartile, ~25%), and H (high, ~30%). Higher correction levels sacrifice capacity for resilience. Version 1 (21×21 modules) through Version 40 (177×177 modules) cover increasing data capacities. The data modules encode bytes using Reed-Solomon error correction codes, which means up to the stated percentage of damaged modules can be reconstructed algorithmically — making partial QR codes readable if the damage is within tolerance.

Technical Deep-Dive

# Reading QR codes from image files
from PIL import Image
from pyzbar.pyzbar import decode as zbar_decode
import zxingcpp   # alternative, often more robust

def read_qr_pyzbar(path: str) -> list:
    img = Image.open(path)
    results = zbar_decode(img)
    return [r.data.decode("utf-8", errors="replace") for r in results]

def read_qr_zxing(path: str) -> list:
    results = zxingcpp.read_barcodes(Image.open(path))
    return [r.text for r in results if r.valid]

# Try both — one may succeed where the other fails on damaged codes
for reader in (read_qr_pyzbar, read_qr_zxing):
    try:
        print(reader("challenge_qr.png"))
        break
    except Exception as e:
        print(f"Reader failed: {e}")
# Generating a QR code for verification / reconstruction
import qrcode

def generate_qr(data: str, error_level=qrcode.constants.ERROR_CORRECT_H) -> None:
    qr = qrcode.QRCode(error_correction=error_level, box_size=10, border=4)
    qr.add_data(data)
    qr.make(fit=True)
    img = qr.make_image(fill_color="black", back_color="white")
    img.save("reconstructed.png")
    print(f"Version: {qr.version}, Error correction: H")

# Detect format information to identify error correction level
# Format bits are in modules adjacent to the finder patterns
# Bits 13-12: 00=M, 01=L, 10=H, 11=Q (after masking with 101010000010010)
# Command-line QR decode
zbarimg --raw challenge_qr.png
# or
python3 -c "
from PIL import Image
from pyzbar.pyzbar import decode
print(decode(Image.open('challenge_qr.png')))
"

# For damaged QR: open in image editor, restore finder patterns
# (three corner squares are mandatory — if any is missing, reconstruction fails)
# Then retry decoding after manual repair

Analytical Methodology

  1. Attempt automated decode first. Use pyzbar and zxingcpp on the raw image. Try multiple orientations (rotate 90°, 180°, 270°) if initial decode fails — some challenges provide a rotated QR image. Check whether the image needs contrast adjustment (histogram equalisation).
  2. Assess structural integrity. Visually inspect the three finder patterns (three-corner nested squares) and the timing patterns. If any finder pattern is missing or obscured, automated decoders will fail and manual reconstruction is needed.
  3. Identify the error correction level. Read the format information strip (adjacent to the finder patterns). This tells you the maximum damage the code can sustain. If less than the maximum is damaged, the code can still be decoded once the finder patterns are intact.
  4. Reconstruct damaged regions in an image editor. For challenges where part of the QR is deliberately obscured, reconstruct finder patterns first (they are always the same pattern: dark 7×7 with white 5×5 with dark 3×3). Then attempt decode — error correction will handle remaining data damage.
  5. Generate a reference QR. If you have a hypothesis about the encoded data (e.g., you know the flag prefix), generate a QR code containing that string at the same version and error correction level, and compare the data modules with the challenge image to validate.
  6. Check for layered QR steganography. Some challenges overlay two QR images with XOR or alpha blending. If the image looks like noise or a visual puzzle, try XOR-combining it with a blank white QR background or splitting colour channels.

Common Analytical Errors

  • Using only one decoder library. pyzbar and zxingcpp use different decoding algorithms. A damaged code that fails in one often succeeds in the other. Always try both.
  • Ignoring image preprocessing. Low-contrast, blurry, or skewed QR images must be preprocessed (threshold, sharpen, deskew) before decoding. Passing a raw photo directly to a decoder will fail for anything other than a clean digital QR.
  • Assuming data is text. QR codes can encode binary data, URLs, vCards, or raw bytes. If decoded output is not printable text, treat it as binary and check for embedded file headers (PNG, PDF, ZIP magic bytes).
  • Missing rotated or mirrored QR codes. A QR code image that has been flipped horizontally is still valid (QR spec includes mirrored reading). Some decoders do not handle mirrored codes. Try flipping the image if standard orientations all fail.
  • Confusing Micro QR with standard QR. Micro QR codes have only one finder pattern (top-left corner) and no alignment patterns. They will not decode with standard QR readers — use a Micro QR-capable library.
  • Giving up after finder pattern damage. Even with a damaged finder pattern, manual reconstruction using the known fixed pattern (1110111 / 1000001 / 1011101 / ...) in an image editor takes only a few minutes and often restores decodability.

NICE Framework Alignment

Code Knowledge/Skill/Task Statement How This Card Develops It
K0018 Knowledge of encryption algorithms used to protect data during transmission Grounds understanding of Reed-Solomon error correction as a non-cryptographic information integrity mechanism
K0019 Knowledge of cryptography and key management concepts Distinguishes error-correction codes from cryptographic integrity mechanisms (hash functions, MACs)
K0305 Knowledge of encryption standards and various encryption algorithms Positions QR/ISO 18004 within the landscape of data encoding and correction standards
S0138 Skill in using defensive coding practices Encourages multi-library fallback patterns and defensive image preprocessing in decoder implementations
T0212 Perform penetration testing as required to evaluate information security Develops QR analysis skills applicable to physical-layer penetration testing and QR phishing campaign analysis

Further Reading

  • ISO/IEC 18004:2015 — Information Technology: Automatic Identification and Data Capture Techniques: QR Code Bar Code Symbology Specification — ISO
  • QR Code Demystified — Thonky.com QR Code Tutorial (comprehensive structural reference)
  • Malicious QR Codes in Physical Penetration Testing — SANS Reading Room

Challenge Lab

Reinforce your learning with a hands-on generated challenge based on this card's competency.