← Back to Writeups
CUSTOMMediumCrypto

Bookworm

XESXOR9/10/20264 min read
#crypto#custom#medium#book_cipher_decryption#page_line_character_indexing#pdf_text_extraction

Bookworm

Platform: spbctf | Category: Crypto | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-03-09 | Status: Solved Techniques: book_cipher_decryption, page_line_character_indexing, pdf_text_extraction

Summary

Task: Decrypt a book cipher using a PDF book and coordinate file with page-line-character format. Solution: Extract text from PDF with PyMuPDF, skip page numbers and chapter headers, then retrieve characters at specified positions.

Bookworm - Crypto Kids 20 (forkbomb.ru)

Description

"This book can tell you secret if you'll use that file to read it"

Given:

  • Book: "Do Androids Dream of Electric Sheep?" by Philip K. Dick (PDF, 111 pages)
  • Cipher file: bookworm.txt with 17 lines in page - line - character_position format

Contents of bookworm.txt:

71 - 15 - 12
79 - 12 - 49
43 - 8 - 63
89 - 24 - 8
13 - 26 - 27
22 - 6 - 54
34 - 8 - 51
58 - 7 - 50
41 - 15 - 65
11 - 19 - 23
101 - 9 - 85
33 - 11 - 62
94 - 14 - 78
69 - 16 - 12
37 - 13 - 36
89 - 14 - 51
95 - 22 - 37

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

Analysis

This is a classic book cipher in page-line-character format:

  • Each line points to a specific character in the book
  • Format: page - line - character_position
  • 17 coordinates = 17 flag characters

PDF extraction specifics:

  1. PyMuPDF (fitz) extracts text with line breaks
  2. First line of each page is the page number (needs to be skipped)
  3. Pages with chapter beginnings have an additional header line (ONE, TWO, ..., TWENTY)
  4. Paragraph indentation (leading whitespace) needs to be removed before counting character position ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

python
#!/usr/bin/env python3
"""
Book cipher solver for Crypto Kids 20 - Bookworm
Uses PyMuPDF (fitz) for PDF text extraction
"""

import fitz  # PyMuPDF

# Chapter headers that appear on some pages
CHAPTER_HEADERS = [
    "ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT",
    "NINE", "TEN", "ELEVEN", "TWELVE", "THIRTEEN", "FOURTEEN",
    "FIFTEEN", "SIXTEEN", "SEVENTEEN", "EIGHTEEN", "NINETEEN", "TWENTY"
]

def extract_character(pdf_path, page_num, line_num, char_pos):
    """
    Extract a single character from PDF using page-line-character coordinates.
    
    Args:
        pdf_path: Path to PDF file
        page_num: 1-indexed page number
        line_num: 1-indexed line number (after skipping page number header)
        char_pos: 1-indexed character position (after stripping leading whitespace)
    
    Returns:
        The character at the specified position
    """
    doc = fitz.open(pdf_path)
    
    # PDF pages are 0-indexed
    page = doc[page_num - 1]
    text = page.get_text()
    lines = text.split('\n')
    
    # Skip the page number header (first line)
    lines = lines[1:]
    
    # Check if first line is a chapter header and skip it
    if lines and lines[0].strip() in CHAPTER_HEADERS:
        lines = lines[1:]
    
    # Get the target line (1-indexed)
    target_line = lines[line_num - 1]
    
    # Strip leading whitespace before counting character position
    target_line = target_line.lstrip()
    
    # Get character at position (1-indexed)
    char = target_line[char_pos - 1]
    
    doc.close()
    return char
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍


def solve_bookworm(pdf_path, cipher_file):
    """
    Solve the book cipher by extracting characters from PDF.
    """
    # Parse cipher file
    coordinates = []
    with open(cipher_file, 'r') as f:
        for line in f:
            parts = line.strip().split(' - ')
            if len(parts) == 3:
                page = int(parts[0])
                line_num = int(parts[1])
                char_pos = int(parts[2])
                coordinates.append((page, line_num, char_pos))
    
    # Extract characters
    flag = ""
    for page, line_num, char_pos in coordinates:
        char = extract_character(pdf_path, page, line_num, char_pos)
        flag += char
        print(f"Page {page:3d}, Line {line_num:2d}, Char {char_pos:2d} -> '{char}'")
    
    return flag
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍


if __name__ == "__main__":
    pdf_path = "do_androids_dream.pdf"
    cipher_file = "bookworm.txt"
    
    flag = solve_bookworm(pdf_path, cipher_file)
    print(f"\nFlag: {flag}")

Step-by-step execution:

  1. Open the PDF using PyMuPDF
  2. For each coordinate (page, line, char):
    • Extract the page text
    • Skip the first line (page number)
    • If the page starts a chapter - skip the chapter header
    • Remove leading whitespace from the target line
    • Extract the character by position (1-indexed)
  3. Concatenate all 17 characters ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Result: REDACTED (spbctf + sci-fi is good)

Pitfalls

  1. PDF text extraction: Different tools (PyMuPDF, pdftotext, pdfplumber) produce different line breaks
  2. Page headers: Page numbers and chapter headers need to be skipped
  3. Whitespace: Paragraph indentation affects character position counting
  4. Indexing: Important to understand whether 0-indexed or 1-indexed numbering is used ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍