Сокровище 2 (Treasure 2)
Сокровище 2 (Treasure 2)
Platform: CUSTOM | Category: Crypto | Type: Challenge | Difficulty: Easy | OS: N/A | **Author: XESXOR Techniques: book_cipher_decryption, page_offset_calculation, pdf_text_extraction
Summary
Walkthrough of the CUSTOM Crypto Сокровище 2 (Treasure 2). Task: Decrypt a message using a book cipher with coordinates (page, line, character) referencing an Eloquent JavaScript PDF. Solution: Extract text from PDF pages with correct offset, count characters
Description
"Что ж, спасибо тебе! После получения огромного наследства у меня достаточное количество свободного времени. Я поделюсь с тобой богатством, но взамен возьму самое ценное у тебя - время. Конечно же, если сможешь взломать мой шифр. Думаю, что тебе понадобиться ещё вот эта книжка..."
Files:
treasure_2.zipcontains:Записка.txt— encrypted messageКнига JavaScript.pdf— book "Eloquent JavaScript" 4th edition
Analysis
1. Extracting the archive
The archive contains files with Russian names in CP866 encoding:
bash read-only • copy allowedunzip -O CP866 treasure_2.zip
2. Note contents
The file Записка.txt contains numbers in "page line character" format:
30 1 25
170 9 1
216 5 18
216 9 1
40 1 1
140 12 1
348 5 7
3 3 16
271 7 42
6 1 3
11 2 4
22 1 4
271 7 42
24 1 26
22 1 4
24 1 29
22 1 4
218 3 1
216 1 16
271 7 42
395 5 1
277 2 4
19 5 16
348 6 1
3. Cipher identification
This is a Book Cipher — a classical cipher where each character of the message is encoded by coordinates in a book:
- First number — page number
- Second number — line number on the page
- Third number — character position in the line
Solution
Key features
- PDF page offset: Book page 1 starts at PDF page 13 (offset = 12)
- Character counting: Character position is counted WITHOUT spaces
- Empty lines: On some pages, empty lines need to be skipped
- Indexing: All numbers are 1-indexed
Solution script
python read-only • copy allowed#!/usr/bin/env python3 """ Book Cipher Decoder for Treasure 2 Uses pdftotext to extract text from Eloquent JavaScript PDF """ import subprocess import sys # Cipher text: (page, line, char_position) cipher = [ (30, 1, 25), (170, 9, 1), (216, 5, 18), (216, 9, 1), (40, 1, 1), (140, 12, 1), (348, 5, 7), (3, 3, 16), (271, 7, 42), (6, 1, 3), (11, 2, 4), (22, 1, 4), (271, 7, 42), (24, 1, 26), (22, 1, 4), (24, 1, 29), (22, 1, 4), (218, 3, 1), (216, 1, 16), (271, 7, 42), (395, 5, 1), (277, 2, 4), (19, 5, 16), (348, 6, 1), ] PDF_FILE = "Книга JavaScript.pdf" PAGE_OFFSET = 12 # Book page 1 = PDF page 13 def get_page_text(pdf_page): """Extract text from specific PDF page using pdftotext""" result = subprocess.run( ['pdftotext', '-layout', '-f', str(pdf_page), '-l', str(pdf_page), PDF_FILE, '-'], capture_output=True, text=True ) return result.stdout def decode_char(book_page, line_num, char_pos): """Decode single character from book coordinates""" pdf_page = book_page + PAGE_OFFSET text = get_page_text(pdf_page) # Split into lines, filter empty for certain pages lines = text.split('\n') # For page 271, skip empty lines if book_page == 271: lines = [l for l in lines if l.strip()] if line_num > len(lines): return '?' line = lines[line_num - 1] # 1-indexed # Remove spaces for character counting line_no_spaces = line.replace(' ', '') if char_pos > len(line_no_spaces): return '?' return line_no_spaces[char_pos - 1] # 1-indexed # Decode the message message = '' for page, line, char in cipher: c = decode_char(page, line, char) message += c print(f"Page {page}, Line {line}, Char {char}: '{c}'") print(f"\nDecoded message: {message}")
Decoding process
| Page | Line | Character | Result | Source |
|---|---|---|---|---|
| 30 | 1 | 25 | C | "Chapter" |
| 170 | 9 | 1 | O | "Often" |
| 216 | 5 | 18 | D | "DOM" |
| 216 | 9 | 1 | E | line start |
| 40 | 1 | 1 | B | line start |
| 140 | 12 | 1 | Y | line start |
| 348 | 5 | 7 | { | character in code |
| 3 | 3 | 16 | u | from text |
| 271 | 7 | 42 | _ | underscore |
| 6 | 1 | 3 | a | from word |
| 11 | 2 | 4 | r | from word |
| 22 | 1 | 4 | e | from word |
| 271 | 7 | 42 | _ | underscore |
| 24 | 1 | 26 | l | from word |
| 22 | 1 | 4 | e | from word |
| 24 | 1 | 29 | g | from word |
| 22 | 1 | 4 | e | from word |
| 218 | 3 | 1 | n | line start |
| 216 | 1 | 16 | d | from word |
| 271 | 7 | 42 | _ | underscore |
| 395 | 5 | 1 | b | line start |
| 277 | 2 | 4 | r | from word |
| 19 | 5 | 16 | 0 | digit zero |
| 348 | 6 | 1 | } | closing brace |
| |
Pitfalls
- Page offset: In PDF, the book may not start from the first page (table of contents, preface)
- Character counting: May count with or without spaces
- Empty lines: May be counted or skipped
- File encoding: Russian filenames in ZIP may be in CP866
- PDF format: Use
pdftotext -layoutto preserve line structure
Alternative Approaches
- Manual search through PDF (for short messages)
- Using PyPDF2/pdfplumber instead of pdftotext
- OCR if PDF contains scans
Key Takeaways / Lessons
- Mastering book_cipher_decryption, page_offset_calculation, pdf_text_extraction was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-01-06 • CUSTOM • Сокровище 2 (Treasure 2) • Difficulty: Easy