Stomach Bug
Stomach Bug
Platform: CUSTOM | Category: Steganography | Type: Challenge | Difficulty: Easy | OS: N/A | **Author: XESXOR Techniques: nested_qr_decoding, ordered_hex_reassembly, utf8_to_latin1_byte_recovery
Summary
Walkthrough of the CUSTOM Steganography Stomach Bug. Task: a web endpoint continuously streams printable junk mixed with indexed hex chunks that hide an image. Solution: extract the numbered hex records, rebuild the PNG, decode two QR layers, repair UTF
Description
Source challenge:
Stomach BugURL:
https://stomachbug.umbccd.net
English summary: the endpoint returns an endless attachment named spew.txt. Inside the stream, useful data appears as numbered hex records mixed with distracting printable ASCII lines.
Analysis
Recon observations:
- The server does not return a normal HTML page; it starts downloading
spew.txtand keeps streaming. - The body alternates between sliding printable ASCII text and lines of the form
|000|...through|161|.... - The numbered lines are hex-only payload chunks. Sorting by index and concatenating them reconstructs a valid PNG.
- The recovered image is a 625x625 grayscale QR code.
- Decoding that QR yields PNG bytes that were expanded through UTF-8 text encoding, so they must be converted back with
.decode("utf-8").encode("latin1")before opening the nested image. - The second QR contains a base64 string, which decodes directly to the flag.
Solution
Extraction pipeline:
- Download only a short slice of the endless response.
- Regex-extract all numbered hex chunks.
- Sort them by numeric index and concatenate the hex payload.
- Convert the hex to bytes and save the first PNG.
- Decode the first QR.
- Repair the UTF-8-expanded PNG bytes with
.decode("utf-8").encode("latin1"). - Decode the nested QR.
- Base64-decode the nested QR text to recover the flag.
python read-only • copy allowed#!/usr/bin/env python3 import base64 import io import re import requests from PIL import Image from pyzbar.pyzbar import decode URL = "https://stomachbug.umbccd.net" LINE_RE = re.compile(r"^\|(\d{3})\|([0-9a-fA-F]+)$") def decode_qr(image_bytes: bytes) -> bytes: img = Image.open(io.BytesIO(image_bytes)) result = decode(img) if not result: raise RuntimeError("QR code not found") return result[0].data def fetch_sample(max_lines: int = 2000) -> str: lines = [] with requests.get(URL, stream=True, timeout=15) as r: r.raise_for_status() for i, line in enumerate(r.iter_lines(decode_unicode=True), 1): if line is not None: lines.append(line) if i >= max_lines: break return "\n".join(lines) def main() -> None: sample = fetch_sample() chunks = {} for line in sample.splitlines(): m = LINE_RE.match(line.strip()) if m: idx = int(m.group(1)) chunks[idx] = m.group(2) if not chunks: raise RuntimeError("No numbered hex chunks found") first_png = bytes.fromhex("".join(chunks[i] for i in sorted(chunks))) qr1 = decode_qr(first_png) nested_png = qr1.decode("utf-8").encode("latin1") qr2 = decode_qr(nested_png) flag = base64.b64decode(qr2).decode() print(flag) if __name__ == "__main__": main()
Key Takeaways / Lessons
- Mastering nested_qr_decoding, ordered_hex_reassembly, utf8_to_latin1_byte_recovery was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-04-10 • CUSTOM • Stomach Bug • Difficulty: Easy