parkour encoding
pingctf
Task: Minecraft world save with diamond blocks arranged along X axis encoding binary data. Solution: extract block positions, build binary string from presence/absence, decode as ASCII shifted left by 1 bit (byte >> 1).
$ ls tags/ techniques/
parkour encoding — pingCTF 2026
Description
even the flag is parkour in parkour civilization
The challenge references the YouTube video "Parkour Civilization" by Oliver Age 24 — a Minecraft short film where parkour is the language. The provided file is a Minecraft 1.21.11 world save (~3.7 MB zip) containing region files with .mca anvil format.
Author: tomek7667
Flag format: ping{.*}
Analysis
Initial Recon
Unzipping the world save reveals a standard Minecraft Java Edition structure:
parkour encoding/
├── level.dat # World metadata (NBT)
├── icon.png # World icon
├── region/ # Overworld chunks (.mca files)
│ ├── r.-1.-1.mca
│ ├── r.-1.0.mca
│ ├── r.0.-1.mca
│ ├── r.0.0.mca
│ └── ... (20 files total)
├── DIM-1/ # Nether (empty)
├── DIM1/ # End (empty)
└── data/
└── stopwatches.dat
The level.dat shows:
- Version Name: 1.21.11
- DataVersion: 4671
- WorldGenSettings: flat world (superflat)
Block Extraction
Using the anvil-parser Python library to iterate all chunks and decode the palette-based block_states of each section:
Discovery: The world contains exactly:
- 232
minecraft:diamond_block— all at Y=0, Z=0, with X ranging from 1 to 431 - 1
minecraft:oak_wall_sign— at position (432, 2, 0)
The sign text reads:
congratz!
btw, did you get
the flag along
the way?
This confirms: the flag is encoded in the diamond block arrangement along the X axis.
Encoding Pattern
The diamonds form a parkour path — a single straight line where:
- Diamond present at X → bit
1 - Gap (no diamond) at X → bit
0
Building a binary string from X=1 to X=431 (length 431 bits):
1110000011010010110111001100111011110110110011001101100001101000...
The Core Trick: ASCII Shifted Left by 1
Initial decode as raw 8-bit ASCII produces bytes like:
0xE0,0xD2,0xDC,0xCE,0xF6...
All bytes have MSB set (values > 0x7F) — this is the key indicator!
Observation:
0xE0 >> 1 = 0x70 = 'p'0xD2 >> 1 = 0x69 = 'i'0xDC >> 1 = 0x6E = 'n'0xCE >> 1 = 0x67 = 'g'0xF6 >> 1 = 0x7B = '{'
The encoding is 7-bit ASCII placed in the top 7 bits of each byte (equivalent to ASCII × 2, or left-shifted by 1 bit). The lowest bit is always 0.
Solution
Step 1: Extract All Blocks from Minecraft World
#!/usr/bin/env python3 import anvil import os import math import json def decode_section(section): """Decode palette-based block_states from a chunk section.""" bs = section['block_states'] palette = [tag['Name'].value for tag in bs['palette'].tags] if 'data' not in bs or len(palette) == 1: # Single block type fills entire section return palette, None data = bs['data'].value bits = max(4, math.ceil(math.log2(len(palette)))) mask = (1 << bits) - 1 indices = [] for long_val in data: u = long_val & ((1 << 64) - 1) for i in range(64 // bits): if len(indices) >= 4096: break indices.append((u >> (i * bits)) & mask) if len(indices) >= 4096: break return palette, indices def extract_blocks(world_path): """Extract all non-air, non-bedrock blocks from world.""" region_dir = os.path.join(world_path, 'region') blocks = [] for fname in os.listdir(region_dir): if not fname.endswith('.mca'): continue # Parse region coordinates from filename r.X.Z.mca parts = fname.split('.') rx, rz = int(parts[1]), int(parts[2]) region = anvil.Region.from_file(os.path.join(region_dir, fname)) for cx in range(32): for cz in range(32): try: chunk = region.chunk_data(cx, cz) except: continue if chunk is None or 'sections' not in chunk: continue chunk_x = rx * 32 + cx chunk_z = rz * 32 + cz for section in chunk['sections'].tags: if 'block_states' not in section: continue section_y = section['Y'].value palette, indices = decode_section(section) if indices is None: # Single block type if palette[0] not in ('minecraft:air', 'minecraft:bedrock'): for i in range(4096): lx = i & 0xF ly = (i >> 8) & 0xF lz = (i >> 4) & 0xF x = chunk_x * 16 + lx y = section_y * 16 + ly z = chunk_z * 16 + lz blocks.append([palette[0], x, y, z]) else: for i, idx in enumerate(indices): block = palette[idx] if block in ('minecraft:air', 'minecraft:bedrock'): continue lx = i & 0xF ly = (i >> 8) & 0xF lz = (i >> 4) & 0xF x = chunk_x * 16 + lx y = section_y * 16 + ly z = chunk_z * 16 + lz blocks.append([block, x, y, z]) return blocks # Extract blocks blocks = extract_blocks('parkour encoding') print(f"Total blocks: {len(blocks)}")
Step 2: Decode the Flag
# Filter diamond blocks diamonds = [(x, y, z) for name, x, y, z in blocks if name == 'minecraft:diamond_block'] print(f"Diamond blocks: {len(diamonds)}") # 232 # Get X coordinates diamonds_x = sorted([x for x, y, z in diamonds]) xset = set(diamonds_x) xmin, xmax = min(diamonds_x), max(diamonds_x) print(f"X range: {xmin} to {xmax}") # 1 to 431 # Build binary string: 1 if diamond present, 0 if gap bits = ''.join('1' if x in xset else '0' for x in range(xmin, xmax + 1)) print(f"Bit length: {len(bits)}") # 431 # Pad to multiple of 8 (add trailing 0) bits += '0' # Now 432 bits = 54 bytes # Decode with right-shift by 1 (the key trick!) flag = ''.join(chr(int(bits[i:i+8], 2) >> 1) for i in range(0, len(bits), 8)) print(f"Flag: {flag}")
Output:
ping{fl4g_1s_Wh4t3V3R_Y0u_w4nT_1_h0p3_y0u_d0nt_us3_41}
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar
Similar writeups
- [misc][Pro]SignCraft— hackerlab
- [misc][Pro]МОЯ ЖИЗНЬ (MY LIFE)— hackerlab
- [misc][Pro]Zuma— duckerz
- [reverse][free]bike— b01lersc
- [crypto][Pro]Bazirovanaya baza (Based Base)— duckerz