TJCTF 2026: Glitch
Challenge Overview
Glitch presents a single image file, glitch.png, that resembles a corrupted television broadcast — horizontal stripes of vivid color interspersed with snowy noise and visual artifacts. The challenge description, “Do not resist the gilitch.”, is deliberately corrupted with zalgo-style text, reinforcing the “glitch” theme. The word “resist” is the critical hint: it’s a pun on resistor, pointing directly to the electronic component color-coding system as the decoding mechanism.
The solve path involves three conceptual stages. First, you must visually separate the intentional color bands (the signal) from the random pixel noise (the distraction). Second, you must recognize that the colors correspond to the standard resistor color code, where each color maps to a single digit. Third, you must read the bands in pairs from top to bottom, treating each pair as a two-digit decimal number that represents an ASCII character. The resulting plaintext, wrapped in the CTF’s flag format, yields the answer.
What makes this challenge elegant is the layered misdirection. The glitch aesthetic encourages players to reach for image forensics tools — stegsolve, LSB extraction, spectral analysis — when the actual encoding is purely visual and requires nothing more than recognizing a well-known electronics standard. The noise exists solely to obscure the band boundaries and make the pattern harder to spot at a glance.
The word resist in the description is not a philosophical statement about accepting chaos. It is a direct reference to resistors — passive electronic components whose values are encoded using colored bands painted on their surface. Every electronics student learns this code: Black=0, Brown=1, Red=2, through White=9. The challenge is telling you exactly what encoding scheme to use.
Signal Extraction: Separating Bands from Noise
Visual Inspection
Opening glitch.png in any image viewer reveals a chaotic scene: dozens of thin, flickering horizontal lines in random colors, interspersed with blocks of static noise that look like a detuned analog TV channel. The initial instinct is to apply a noise-reduction filter, convert to grayscale, or extract LSB planes — all standard steganography techniques. None of these approaches work because the hidden data is not embedded in pixel values at the bit level; it’s encoded in the colors of the bands themselves.
The key observation is that not all horizontal stripes are created equal. The noise lines are thin (1-3 pixels tall), randomly colored, and inconsistent across the image width. The signal bands, by contrast, are thick (5-15 pixels tall), span the full width of the image, and have a single, consistent color. When you squint or apply a slight Gaussian blur, the thin noise lines blend into the background while the thick signal bands remain clearly visible as distinct horizontal stripes.
Downsampling Technique
A practical way to isolate the signal is to resize the image to a fraction of its original resolution. Averaging adjacent pixels during downsampling smooths out the thin noise lines while preserving the broad color bands:
from PIL import Image
img = Image.open("glitch.png")
# Downsample to ~5% of original size — noise fades, bands remain
small = img.resize((img.width // 20, img.height // 20), Image.LANCZOS)
small.save("glitch_downsampled.png")
In the downsampled image, the signal bands become immediately apparent as a sequence of solid-colored horizontal stripes, ordered from top to bottom. The colors are not random — they repeat in a pattern that corresponds to the resistor color code, with adjacent bands forming pairs.
The noise serves a dual purpose: it makes the band boundaries harder to identify visually, and it discourages automated color-sampling tools that might pick up noise pixels instead of the true band color. The challenge designer is banking on the fact that most CTF players will try bit-level steganography before considering a higher-level visual encoding. Patience and careful observation beat automated tools in this case.
The Resistor Color Code
Standard Color-to-Digit Mapping
The electronic resistor color code is a universal standard in electrical engineering. Each color corresponds to a single decimal digit (0-9). On a physical resistor, the first two colored bands represent the first two significant digits of the resistance value, the third band is the multiplier (power of 10), and the fourth band indicates tolerance. For this challenge, we only need the digit mapping:
| Color | Digit | Swatch |
|---|---|---|
| Black | 0 | |
| Brown | 1 | |
| Red | 2 | |
| Orange | 3 | |
| Yellow | 4 | |
| Green | 5 | |
| Blue | 6 | |
| Violet | 7 | |
| Gray | 8 | |
| White | 9 |
Once you recognize the encoding, the image transforms from an unintelligible mess into a straightforward lookup table. Each horizontal band maps to exactly one digit. Two adjacent bands form a two-digit number. That number is the decimal ASCII code of a character in the hidden message.
Reading Band Pairs
The image organizes its color bands in pairs from top to bottom. The first band in each pair provides the tens digit and the second provides the units digit. For example, if the topmost two bands are Blue and Gray, the two-digit number is 68. Looking up decimal 68 in the ASCII table yields the character 'D'. The second pair might be Green and Brown, giving 51, which is '3' in ASCII. This pattern continues through all fourteen band pairs, producing the complete hidden string.
The glitch noise can shift the apparent color of band edges, making a blue band look slightly purple or a green band appear yellowish at its boundary. Always sample the center of each band, not the edges where noise bleed occurs. Using a color picker tool on the downsampled image (which has already averaged out edge noise) is the most reliable approach.
ASCII Decoding: Band Pairs to Plaintext
Complete Decoding Table
After extracting the color bands from the downsampled image and mapping each to its corresponding digit, we obtain the following sequence of ASCII characters:
| Pair # | Band 1 | Digit 1 | Band 2 | Digit 2 | Decimal | ASCII |
|---|---|---|---|---|---|---|
| 1 | Blue | 6 | Gray | 8 | 68 | D |
| 2 | Green | 5 | Brown | 1 | 51 | 3 |
| 3 | Gray | 8 | Orange | 3 | 83 | S |
| 4 | Yellow | 4 | White | 9 | 49 | 1 |
| 5 | Violet | 7 | Brown | 1 | 71 | G |
| 6 | Violet | 7 | Gray | 8 | 78 | N |
| 7 | Yellow | 4 | Orange | 3 | 43 | + |
| 8 | Gray | 8 | Yellow | 4 | 84 | T |
| 9 | Blue | 6 | White | 9 | 69 | E |
| 10 | Blue | 6 | Violet | 7 | 67 | C |
| 11 | Violet | 7 | Red | 2 | 72 | H |
| 12 | White | 9 | Green | 5 | 95 | _ |
| 13 | Green | 5 | Gray | 8 | 58 | : |
| 14 | Yellow | 4 | Brown | 1 | 41 | ) |
Concatenating all decoded characters produces the hidden plaintext: D3S1GN+TECH_:). Wrapping this in the TJCTF flag format gives the final answer.
tjctf{D3S1GN+TECH_:)}
Exploit Code
#!/usr/bin/env python3
"""
TJCTF 2026 - Glitch
Resistor color code steganography decoder
Author: QA210
"""
# Standard resistor color-to-digit lookup
COLOR_MAP = {
"black": 0, "brown": 1, "red": 2, "orange": 3,
"yellow": 4, "green": 5, "blue": 6, "violet": 7,
"gray": 8, "white": 9,
}
# Color bands extracted from glitch.png (top to bottom, read as pairs)
# Each pair of colors encodes one ASCII character as a two-digit decimal
EXTRACTED_BANDS = [
("blue", "gray"), # 6,8 -> 68 -> D
("green", "brown"), # 5,1 -> 51 -> 3
("gray", "orange"), # 8,3 -> 83 -> S
("yellow", "white"), # 4,9 -> 49 -> 1
("violet", "brown"), # 7,1 -> 71 -> G
("violet", "gray"), # 7,8 -> 78 -> N
("yellow", "orange"), # 4,3 -> 43 -> +
("gray", "yellow"), # 8,4 -> 84 -> T
("blue", "white"), # 6,9 -> 69 -> E
("blue", "violet"), # 6,7 -> 67 -> C
("violet", "red"), # 7,2 -> 72 -> H
("white", "green"), # 9,5 -> 95 -> _
("green", "gray"), # 5,8 -> 58 -> :
("yellow", "brown"), # 4,1 -> 41 -> )
]
def bands_to_ascii(band_pairs):
"""
Decode a list of (color_a, color_b) tuples into a string.
Each tuple maps to two digits via resistor color code,
forming a decimal ASCII value.
"""
result = []
for primary, secondary in band_pairs:
tens = COLOR_MAP[primary]
units = COLOR_MAP[secondary]
char_code = tens * 10 + units
result.append(chr(char_code))
return "".join(result)
def main():
plaintext = bands_to_ascii(EXTRACTED_BANDS)
print(f"Decoded plaintext: {plaintext}")
print(f"Flag: tjctf{{{plaintext}}}")
if __name__ == "__main__":
main()
Running the Exploit
$ python3 glitch_decode.py
Decoded plaintext: D3S1GN+TECH_:)
Flag: tjctf{D3S1GN+TECH_:)}
Automated Pixel Extraction (Advanced)
While the manual approach — downsample, identify bands by eye, look up colors — is sufficient for this challenge, a fully automated solution is also possible. The algorithm works by sampling a vertical column of pixels (avoiding edge noise) and clustering adjacent pixels with similar colors into bands:
#!/usr/bin/env python3
"""
Automated band extraction from glitch.png
Samples the center column, clusters by color proximity, maps to resistor codes.
Author: QA210
"""
from PIL import Image
import math
# Reference colors for resistor code (RGB tuples)
REFERENCE_COLORS = {
"black": (0, 0, 0),
"brown": (139, 69, 19),
"red": (220, 50, 50),
"orange": (240, 140, 50),
"yellow": (240, 200, 50),
"green": (50, 180, 80),
"blue": (50, 120, 220),
"violet": (130, 80, 220),
"gray": (130, 130, 140),
"white": (240, 240, 245),
}
DIGIT_MAP = {name: idx for idx, name in enumerate(REFERENCE_COLORS)}
def color_distance(c1, c2):
"""Euclidean distance between two RGB tuples."""
return math.sqrt(sum((a - b) ** 2 for a, b in zip(c1, c2)))
def nearest_resistor_color(pixel_rgb):
"""Find the closest resistor color name for a given RGB pixel."""
best_name = None
best_dist = float("inf")
for name, ref_rgb in REFERENCE_COLORS.items():
dist = color_distance(pixel_rgb, ref_rgb)
if dist < best_dist:
best_dist = dist
best_name = name
return best_name
def extract_bands(image_path, sample_column_ratio=0.5, min_band_height=4):
"""
Sample a vertical column from the image, group consecutive pixels
into bands based on color similarity, and return band color names.
"""
img = Image.open(image_path).convert("RGB")
col_x = int(img.width * sample_column_ratio)
# Sample every pixel in the chosen column
column_pixels = [img.getpixel((col_x, y)) for y in range(img.height)]
# Group consecutive same-color pixels into bands
bands = []
current_color = nearest_resistor_color(column_pixels[0])
run_length = 1
for y in range(1, len(column_pixels)):
color_name = nearest_resistor_color(column_pixels[y])
if color_name == current_color:
run_length += 1
else:
if run_length >= min_band_height:
bands.append(current_color)
current_color = color_name
run_length = 1
if run_length >= min_band_height:
bands.append(current_color)
return bands
def decode_bands(band_list):
"""Convert a list of band color names to ASCII string."""
if len(band_list) % 2 != 0:
print(f"[!] Odd number of bands ({len(band_list)}), dropping last")
band_list = band_list[:-1]
chars = []
for i in range(0, len(band_list), 2):
tens = DIGIT_MAP[band_list[i]]
units = DIGIT_MAP[band_list[i + 1]]
chars.append(chr(tens * 10 + units))
return "".join(chars)
if __name__ == "__main__":
bands = extract_bands("glitch.png")
print(f"[*] Detected {len(bands)} color bands")
plaintext = decode_bands(bands)
print(f"Decoded: {plaintext}")
print(f"Flag: tjctf{{{plaintext}}}")
The automated approach is more robust against subjective color perception errors and can handle larger images with many more bands. The min_band_height parameter filters out noise lines (which are typically 1-3 pixels tall) while preserving signal bands (which are 5+ pixels tall). The nearest_resistor_color function uses Euclidean distance in RGB space to match each pixel to the closest reference color, accounting for the slight color variations introduced by the glitch noise.
For this challenge, manual extraction is faster — there are only 28 bands (14 pairs), and the color palette is limited to 10 standard colors. However, if the challenge scaled to hundreds of band pairs or used more subtle color variations, the automated approach would become essential. Building both solutions is good practice for CTF competitions where time constraints demand quick adaptation.