Стул №5: ДСП (Chair #5: Chipboard)
Стул №5: ДСП (Chair #5: Chipboard)
Platform: bug-makers | Category: Misc | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-05-27 | Status: Solved Techniques: jsfuck_evaluation, octal_escape_decoding, pixel_art_character_recognition, width_based_glyph_classification
Summary
Task: PNG image of a chair made from pixel-art JSFuck code characters (!+). Solution: custom OCR via glyph width/shape classification extracts 6984 chars of JSFuck, which evaluates to the flag.
Стул №5: ДСП — Bug Makers CTF
Description
Что будет, если стружку смешать с клеем? Правильно! ДСП! Из него в этот раз сделан стул. Материал не самый лучший - при неосторожном обращении могут отваливаться куски. Флаг внутри!
English summary: What happens if you mix wood shavings with glue? Chipboard (ДСП)! This time the chair is made of it. The material isn't the best — pieces can fall off with careless handling. The flag is inside!
Given: a PNG image (stul__5.png, 1860×2400, 8-bit RGB grayscale) showing a chair shape composed entirely of text characters. Goal: extract the flag hidden within.
Analysis
Semantic Clues
"ДСП" (chipboard = shavings + glue) is a metaphor for JSFuck — code "shavings" ([]()!+) mixed together to form functional JavaScript. "Pieces can fall off" hints at the fragile nature of the pixel-art encoding and the difficulty of accurate character extraction.
Image Structure
The image uses a dark background (RGB 30,30,30) with text rendered in various grayscale values (200, 198, 137, 89, 62, etc.). The characters form the silhouette of a chair. Visual inspection reveals the characters are exclusively from the set []()!+ — the six characters used in JSFuck, an esoteric JavaScript encoding.
The image contains 115 text lines, each 18-19 pixels tall. Each character is rendered as a distinct pixel-art glyph with characteristic dimensions:
| Character | Width (px) | Visual Shape |
|---|---|---|
! | 3-4 | Vertical bar |
[ | 6 | Left bracket — .####> top/bottom, .@ left side |
] | 6 | Right bracket — >####. top/bottom, !#. right side |
( | 7 | Diagonal curve going right-to-left |
) | 7 | Diagonal curve going left-to-right |
+ | 9-11 | Cross shape with horizontal bar |
| |
Why Standard OCR Failed
Tesseract OCR produced garbled output because:
- The pixel-art font is non-standard (no training data)
- Characters are densely packed with minimal spacing
- The limited 6-character alphabet confuses standard language models
Solution
Step 1: Custom Character Extraction
Built a Python script using PIL/numpy to perform width-and-shape-based OCR:
-
Line detection: Scanned rows to find 115 text lines by identifying horizontal bands where non-background pixels exist
-
Character segmentation: For each line, found vertical gaps (columns where all pixels match background) to isolate individual character bounding boxes
-
Character classification by width and internal shape:
- Width ≤ 4 →
!(exclamation mark — narrow vertical bar) - Width = 6 →
[or](distinguished by which side has content: left-heavy =[, right-heavy =]) - Width ≥ 9 →
+(cross shape — widest character) - Width 7-8 →
(or)(distinguished by curve direction: middle content position relative to top/bottom edges)
- Width ≤ 4 →
-
Extracted 6984 characters total, all confirmed to be from the set
[]()!+
Step 2: Parenthesis Balancing Fix
The extracted code had one OCR error: an extra ( at position 382 where a ) was misidentified as (. After removing it to balance parentheses (277 opening, 277 closing), the code became valid JavaScript.
Step 3: JSFuck Evaluation
Evaluated the JSFuck code in Node.js:
javascript read-only • copy allowedconst fs = require('fs'); const code = fs.readFileSync('jsfuck_fixed.txt', 'utf8'); // JSFuck may call alert(), redirect to console global.alert = function(msg) { console.log('ALERT:', msg); }; const result = eval(code); console.log('Result:', result);
The JSFuck code constructs a string containing octal-escaped JavaScript:
\143\157ns\157le.\154\157g('\102\165g\103\124\106{\112\065\106uck_\061s_we\061\061knoWn_\060bfusc@t\061on_meth\060d}')
Which decodes to:
javascript read-only • copy allowedconsole.log('BugCTF{REDACTED}')