← Back to Writeups
CUSTOMMediumMisc

Змейка (Snake)

XESXOR9/2/20265 min read
#misc#custom#medium#ascii_table_parsing#expression_evaluation#graph_traversal#hash_cracking

Змейка (Snake)

Platform: duckerz | Category: Misc | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-01-09 | Status: Solved Techniques: ascii_table_parsing, expression_evaluation, graph_traversal, hash_cracking

Summary

Task: ASCII grid with snake path containing numbers and operators, solve 100 puzzles, decode SHA256 hashes. Solution: Parse ASCII table, traverse snake path using graph traversal, evaluate expression with proper operator precedence, brute-force 2-character SHA256 hashes.

Змейка (Snake) - duckerz CTF

Description

Я люблю математику. И играть в змейку. (I love math. And playing snake.)

Connection: nc tasks.duckerz.ru 30002

Analysis

When connecting to the service, an ASCII grid is displayed with a "snake" - a path of numbers and mathematical operators (+, -, *, /). The task requires solving 100 such puzzles.

Grid example:

+-----+---+-----+-----+-----+-----+-----+-----+-----+
| 839 | - | 868 | +   | 782 | +   | 710 | +   | 803 |
+-----+---+-----+-----+-----+-----+-----+-----+-----+
|     |   |     |     |     |     |     |     | *   |
+-----+---+-----+-----+-----+-----+-----+-----+-----+
|     |   |     |     |     |     |     |     | 358 |
+-----+---+-----+-----+-----+-----+-----+-----+-----+

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

Key observations:

  1. Snake - a connected path through grid cells
  2. The path contains alternating numbers and operators
  3. Need to find the start and end of the snake (cells with one neighbor)
  4. IMPORTANT: Calculation must use standard operator precedence (* and / before + and -)

Solution

Step 1: Parsing the ASCII table

python
def parse_grid(data):
    """Parse ASCII table into 2D array of cell values"""
    lines = data.strip().split('\n')
    grid = []
    for line in lines:
        if line.startswith('+') or not line.strip():
            continue
        if line.startswith('|'):
            cells = line.split('|')[1:-1]
            row = [cell.strip() for cell in cells]
            grid.append(row)
    return grid

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

Step 2: Finding the snake path

python
def find_snake_path(grid):
    """Find connected path through non-empty cells"""
    if not grid:
        return []
    
    rows, cols = len(grid), len(grid[0]) if grid else 0
    
    # Collect all non-empty cells
    non_empty = {}
    for r in range(rows):
        for c in range(len(grid[r])):
            if grid[r][c]:
                non_empty[(r, c)] = grid[r][c]
    
    def get_neighbors(r, c):
        """Get adjacent non-empty cells"""
        neighbors = []
        for dr, dc in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
            nr, nc = r + dr, c + dc
            if (nr, nc) in non_empty:
                neighbors.append((nr, nc))
        return neighbors
    
    # Find start point (first non-empty cell in first row)
    start = None
    for c in range(cols):
        if (0, c) in non_empty:
            start = (0, c)
            break
    
    # Traverse the snake path
    path = []
    visited = set()
    current = start
    
    while current:
        path.append(non_empty[current])
        visited.add(current)
        
        # Find next unvisited neighbor
        next_cell = None
        for neighbor in get_neighbors(*current):
            if neighbor not in visited:
                next_cell = neighbor
                break
        current = next_cell
    
    return path

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

Step 3: Evaluating the expression

CRITICALLY IMPORTANT: Use standard operator precedence!

python
def calculate_expression(path):
    """Calculate expression with proper operator precedence"""
    expr = ''.join(path)
    return eval(expr)  # eval() respects operator precedence

Step 4: Decoding the flag

After solving 100 puzzles, the server outputs 16 SHA256 hashes:

5d544acfc7fb56cda14aedb9c796836947a7cd98070bc4248884425e498b0e0a
8d5579affb518db0185bafeb983ab99b73664d3d9d3a7c1defef61749474611a
7a475a8b0a074fa8bd3bf54dc2a172de8c70f8ad78980010b9d8e9bc5a5b978f
... (16 total)

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

Each hash is SHA256 of a 2-character string. Brute-force:

python
import hashlib
import string

def crack_hash(target_hash):
    """Brute-force 2-character SHA256 hash"""
    charset = string.printable
    for c1 in charset:
        for c2 in charset:
            candidate = c1 + c2
            if hashlib.sha256(candidate.encode()).hexdigest() == target_hash:
                return candidate
    return None

# Results:
# DU + CK + ER + Z{ + ea + s4 + _c + 4l + 3_ + 13 + _m + 1n + 3_ + b3 + s9 + !}

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

Full solution script

python
#!/usr/bin/env python3
"""
Snake CTF Challenge Solver
duckerz CTF - Task 25
"""

import socket
import re
import hashlib
import string

def parse_grid(data):
    """Parse ASCII table into 2D array"""
    lines = data.strip().split('\n')
    grid = []
    for line in lines:
        if line.startswith('+') or not line.strip():
            continue
        if line.startswith('|'):
            cells = line.split('|')[1:-1]
            row = [cell.strip() for cell in cells]
            grid.append(row)
    return grid
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

def find_snake_path(grid):
    """Find connected path through non-empty cells"""
    if not grid:
        return []
    
    rows, cols = len(grid), len(grid[0]) if grid else 0
    non_empty = {}
    
    for r in range(rows):
        for c in range(len(grid[r])):
            if grid[r][c]:
                non_empty[(r, c)] = grid[r][c]
    
    def get_neighbors(r, c):
        neighbors = []
        for dr, dc in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
            nr, nc = r + dr, c + dc
            if (nr, nc) in non_empty:
                neighbors.append((nr, nc))
        return neighbors
    
    # Find start
    start = next((0, c) for c in range(cols) if (0, c) in non_empty)
    
    path, visited, current = [], set(), start
    while current:
        path.append(non_empty[current])
        visited.add(current)
        current = next((n for n in get_neighbors(*current) if n not in visited), None)
    
    return path
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

def calculate_expression(path):
    """Calculate with proper operator precedence"""
    expr = ''.join(path)
    return eval(expr)

def crack_2char_hash(target_hash):
    """Brute-force 2-character SHA256"""
    charset = string.printable
    for c1 in charset:
        for c2 in charset:
            candidate = c1 + c2
            if hashlib.sha256(candidate.encode()).hexdigest() == target_hash:
                return candidate
    return "??"

def solve():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(("tasks.duckerz.ru", 30002))
    sock.settimeout(10)
    
    for round_num in range(100):
        # Receive grid
        data = b""
        while b"Answer:" not in data:
            data += sock.recv(4096)
        
        grid_text = data.decode('utf-8', errors='ignore')
        
        # Parse and solve
        grid = parse_grid(grid_text)
        path = find_snake_path(grid)
        answer = calculate_expression(path)
        
        print(f"Round {round_num + 1}: {answer}")
        
        # Send answer
        sock.send(f"{answer}\n".encode())
    
    # Receive hashes
    final_data = sock.recv(8192).decode()
    hashes = re.findall(r'[a-f0-9]{64}', final_data)
    
    # Crack hashes
    flag = ""
    for h in hashes:
        flag += crack_2char_hash(h)
    
    print(f"Flag: {flag}")
    sock.close()
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

if __name__ == "__main__":
    solve()

Lessons

  1. Operator precedence: Don't calculate left to right! Use eval() or a proper parser
  2. Graph traversal: The snake is a graph where you need to find a path between two endpoints
  3. Short string hashes: 2-character hashes are easily brute-forced (~10000 combinations)
  4. Automation: 100 rounds = mandatory script

References