← Back to Writeups
CUSTOMMediumMisc

Стул №5: ДСП (Chair #5: Chipboard)

XESXOR9/2/20263 min read
#misc#custom#medium#jsfuck_evaluation#octal_escape_decoding#pixel_art_character_recognitio#width_based_glyph_classificati

Стул №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:

CharacterWidth (px)Visual Shape
!3-4Vertical bar
[6Left bracket — .####> top/bottom, .@ left side
]6Right bracket — >####. top/bottom, !#. right side
(7Diagonal curve going right-to-left
)7Diagonal curve going left-to-right
+9-11Cross 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:

  1. Line detection: Scanned rows to find 115 text lines by identifying horizontal bands where non-background pixels exist

  2. Character segmentation: For each line, found vertical gaps (columns where all pixels match background) to isolate individual character bounding boxes

  3. 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) ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍
  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
const 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
console.log('BugCTF{REDACTED}')

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍