Bejeweled
Bejeweled
Platform: Sekai2026 | Category: Coding | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-06-28 | Status: Solved Techniques: adaptive_waiting, ansi_terminal_emulation, greedy_move_search, grid_parsing, sgr_mouse_click_injection
Summary
Task: an interactive TCP Bejeweled terminal game using ANSI drawing and SGR mouse input. Solution: emulate the terminal screen, parse the 12x7 grid, greedily choose legal match-3 swaps, and click cells with adaptive waits.
Recon
Port scan
bash read-only • copy allowednmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
sekai2026| ID:20260628_sekai2026_bejeweled - Tags: automation, tcp_service, terminal_ui, ansi_escape, sgr_mouse, match3
- Indicators: ANSI alternate-screen terminal UI, minimum terminal size 80x24, SGR mouse mode sequences, 12x7 match-3 grid, 45 second game timer
- Source:
20260628_sekai2026_bejeweled.md
Foothold
Vulnerability / Misconfiguration
- adaptive_waiting
- ansi_terminal_emulation
- greedy_move_search
- grid_parsing
- sgr_mouse_click_injection
bash read-only • copy allowed<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
bash read-only • copy allowedsudo -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.
bash read-only • copy allowed<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- adaptive_waiting
- ansi_terminal_emulation
- greedy_move_search
- grid_parsing
- sgr_mouse_click_injection
- Tags: automation, tcp_service, terminal_ui, ansi_escape, sgr_mouse, match3
Original Writeup
<details><summary>Click to expand original content</summary>Description
The stage is yours. Clear the board before the final note fades. socat -,raw,echo=0 tcp:bejeweled.chals.sekai.team:1337
English summary: the service launches a terminal Bejeweled-style game over TCP. The goal is to automate valid mouse input quickly enough to clear the game before the timer expires.
Analysis
Connecting to the service shows an ANSI alternate-screen terminal UI, not a line-oriented protocol. The initial screen requires at least an 80x24 terminal and displays a [ Start ] button. The application enables mouse reporting and accepts SGR mouse events, so the start button can be pressed by sending:
text read-only • copy allowedESC[<0;40;13M ESC[<0;40;13m
After starting, the game board is a 12x7 match-3 grid. The visible gem symbols are:
text read-only • copy allowed♣ ♢ ♡ ◼ ▲ ● ♠
The grid is drawn at stable terminal coordinates. For zero-based column c in [0..6] and row r in [0..11], the cell centers are:
python read-only • copy allowedx = 19 + 6*c # terminal column y = 2 + 2*r # terminal row
This made it possible to ignore most of the UI and maintain a small virtual terminal screen from ANSI cursor-position writes. The solver parses CSI row;col H updates, ignores styling and mouse-mode escape sequences, and reads gem glyphs near each cell center.
Solution
The automation loop is:
- Connect to
bejeweled.chals.sekai.team:1337. - Parse incoming ANSI output into a virtual screen buffer.
- Send the SGR mouse down/up sequence for the
[ Start ]button. - Extract the 12x7 board from fixed cell coordinates.
- Enumerate every adjacent horizontal/vertical swap.
- For each swap, check whether it creates any horizontal or vertical run of 3+ identical gems.
- Pick the best greedy move, preferring more cleared cells and matches lower on the board.
- Send two SGR mouse clicks at the selected cell centers.
- Wait only until the score or parsed grid changes, then immediately compute the next move.
The timing optimization was important. A first version slept a fixed long delay after every swap and only reached score 9855, level 4 before the 45-second timeout. The successful version waits adaptively for a score or board change, which avoids wasting time and reaches level 8 within 43 seconds.
Core snippets from tasks/sekai2026/Bejeweled/solve.py:
python read-only • copy allowedGEMS = "♣♢♡◼▲●♠" R, C = 12, 7 CELL_X = [19 + 6 * i for i in range(C)] CELL_Y = [2 + 2 * i for i in range(R)] def click(s, r, c): x, y = CELL_X[c], CELL_Y[r] s.sendall(f"\x1b[<0;{x};{y}M\x1b[<0;{x};{y}m".encode()) def matches(g): m = set() for r in range(R): c = 0 while c < C: c2 = c + 1 while c2 < C and g[r][c2] == g[r][c]: c2 += 1 if c2 - c >= 3: m.update((r, k) for k in range(c, c2)) c = c2 for c in range(C): r = 0 while r < R: r2 = r + 1 while r2 < R and g[r2][c] == g[r][c]: r2 += 1 if r2 - r >= 3: m.update((k, c) for k in range(r, r2)) r = r2 return m def best_move(g): best = None for r in range(R): for c in range(C): for dr, dc in [(1, 0), (0, 1)]: rr, cc = r + dr, c + dc if rr >= R or cc >= C or g[r][c] == g[rr][cc]: continue h = [row[:] for row in g] h[r][c], h[rr][cc] = h[rr][cc], h[r][c] mm = matches(h) if mm: score = len(mm) * 100 + sum(x for x, y in mm) if not best or score > best[0]: best = (score, (r, c, rr, cc), len(mm)) return None if best is None else best[1:]
The adaptive wait after each pair of clicks looks like this:
python read-only • copy allowedold_score = vals.get("score", -1) old_grid = [row[:] for row in g] click(s, r, c) time.sleep(0.005) click(s, rr, cc) deadline = time.time() + 0.42 while time.time() < deadline: recv_some(s, term, 0.035) vals_now = term.score_level_time() gg = term.grid() if vals_now.get("score", old_score) != old_score or (gg and gg != old_grid): break
The successful run printed:
</details>text read-only • copy allowed*** GAME CLEAR! *** You reached level 8! Final score: 17610 Time: 43s SEKAI{REDACTED}
signed by XESXOR