Незваный гоуст
Незваный гоуст
Platform: Avitoctf | Category: Steganography | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-07-22 | Status: Solved Techniques: cell_centroid_extraction, gaussian_smoothing, local_background_subtraction
Summary
Task: A static Ghost Font PNG hides text in a regular field of jittered dots rather than conventional pixel bit planes. Solution: Measure each 8x8 cell's vertical ink centroid, smooth the feature grid, and subtract the local background.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
avitoctf| ID:20260722_avitoctf_nezvanyy_goust - Tags: png, image_steganography, dot_grid, ghost_font
- Indicators: regular 8x8 dot-cell lattice, message encoded by coherent dot motion, static frame resembles random noise
- Source:
20260722_avitoctf_nezvanyy_goust.md
Foothold
Vulnerability / Misconfiguration
- Cell_centroid_extraction
- Gaussian_smoothing
- Local_background_subtraction
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- cell_centroid_extraction
- gaussian_smoothing
- local_background_subtraction
- Tags: png, image_steganography, dot_grid, ghost_font
Original Writeup
<details><summary>Click to expand original content</summary>Description
Пчёлы с Полянки 38 поставили на всех каналах сотовой связи СОРМ: ИИ читает переписку и смотрит картинки, докладывая о каждом упоминании мёда. Медоед-разведчик нашёл свежий сервис Ghost Font https://www.mixfont.com/ghost-font, который обещает делать текст нечитаемым для ИИ, и набрал им важное послание.
Правда, разбираться в инструкции он не стал: вместо видео прислал в штаб один скриншот и сразу отключил связь. До вылазки осталось полчаса, а на картинке только шум.
Разберите послание. Текст на картинке — флаг.
https://avitoctf.ru/files/ghost_screenshot.png Флаг в этом задании не в формате avito{...}, а фраза «WRITTEN IN GHOST FONT» добавляется сервисом на каждую картинку, это не секретное послание.
The supplied artifact is a single screenshot of a Ghost Font animation. The goal is to recover the hidden text from the apparent dot noise; the visible service attribution is an explicit decoy.
Analysis
ghost_screenshot.png is a 1918×997 RGBA PNG. Container and channel checks found no useful metadata, appended payload, varying alpha, or conventional sequential LSB stream. The image therefore had to be analyzed as the rendered output of Ghost Font rather than as an ordinary PNG bit-plane challenge.
Ghost Font forms glyphs through coherent dot movement while background dots move independently. Although motion is unavailable in one screenshot, each dot's instantaneous displacement remains measurable. The white canvas begins near (97, 40) and is 1712 pixels wide, exactly 214 × 8 pixels. Thresholding dark pixels and dividing the canvas into 8×8 cells produces a 119×214 grid with approximately one dot per cell.
For every cell, compute the centroid of its dark pixels. Dot area, covariance, shape, and horizontal centroid are dominated by background jitter, but the vertical centroid forms a weak spatially coherent signal. A small Gaussian blur suppresses independent noise and reveals two text lines. Subtracting a much broader blur removes gradual page-wide drift and improves contrast:
signal = GaussianBlur(vertical_centroid, 1.2)
- GaussianBlur(vertical_centroid, 7.0)
The result contains leetspeak-like glyphs that can be mistaken for letters. Since the task requires the text exactly as rendered, preserve the confirmed transcription rather than normalizing ambiguous characters.
Solution
- Crop the visible Ghost Font canvas at
(97, 40, 1809, 997). - Convert it to grayscale and classify pixels below intensity 100 as ink.
- Trim the crop to complete 8×8 cells and reshape it into a dot-cell grid.
- Compute each cell's vertical dark-pixel centroid.
- Smooth the centroid map with
sigma ≈ 1.2–1.4. - Optionally subtract a
sigma = 7.0background estimate. - Crop the two glyph rows, enlarge with nearest-neighbor interpolation, and read the text without character normalization.
The following concise solver reproduces the high-pass proof image:
#!/usr/bin/env python3
from pathlib import Path
import cv2
import numpy as np
from PIL import Image
root = Path(__file__).resolve().parent
rgba = np.asarray(Image.open(root / "ghost_screenshot.png").convert("RGBA"))
# Ghost Font canvas: 214 complete columns of 8x8 dot cells.
x0, y0, x1, y1 = 97, 40, 1809, 997
gray = cv2.cvtColor(rgba[y0:y1, x0:x1, :3], cv2.COLOR_RGB2GRAY)
ink = (gray < 100).astype(np.uint8)
h = ink.shape[0] // 8 * 8
w = ink.shape[1] // 8 * 8
cells = ink[:h, :w].reshape(h // 8, 8, w // 8, 8).transpose(0, 2, 1, 3)
yy = np.mgrid[:8, :8][0]
area = cells.sum(axis=(2, 3)).astype(np.float32)
cy = (cells * yy).sum(axis=(2, 3)) / np.maximum(area, 1)
fine = cv2.GaussianBlur(cy.astype(np.float32), (0, 0), 1.2)
coarse = cv2.GaussianBlur(cy.astype(np.float32), (0, 0), 7.0)
decoded = (fine - coarse)[24:89, 12:207]
lo, hi = np.percentile(decoded, (3, 99))
decoded = np.clip((decoded - lo) * 255 / (hi - lo), 0, 255).astype(np.uint8)
decoded = cv2.resize(decoded, None, fx=8, fy=8, interpolation=cv2.INTER_NEAREST)
Image.fromarray(decoded).save(root / "decoded_phrase_highpass.png")
Evidence and Artifacts
ghost_screenshot.png— original 1918×997 RGBA challenge artifact.analyze_ghost.py— complete feature extraction and proof-image generator.decoded_phrase.png— vertically smoothed centroid map.decoded_phrase_highpass.png— local-background-subtracted map; some glyphs remain visually ambiguous, so exact transcription must not be inferred through normalization.notes.md— reconnaissance, failed families, and extraction findings.
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR