Матрешка 2 (Matryoshka 2)
Матрешка 2 (Matryoshka 2)
Platform: hackerlab | Category: Forensics | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-01-06 | Status: Solved Techniques: nested_archive_extraction, reverse_password_pattern, scripted_extraction
Summary
Task: Nested password-protected ZIP archives with cryptic hint about first and last matryoshkas. Solution: Reverse password pattern where password_index = 24 - archive_index, with the last archive having no password.
Матрешка 2 (Matryoshka 2) — HackerLab
Description
"Самые неожиданные сюрпризы таят первая и последняя матрёшки: первая – содержанием, последняя – его отсутствием"
(Translation: "The most unexpected surprises are hidden in the first and last matryoshkas: the first - by its content, the last - by its absence")
File: matryoshka_doll_2.zip
Analysis
This is a continuation of the "Matryoshka" challenge with a more complex password pattern. In the first version, the password was simply the name of the previous archive. Here a mirror/reverse pattern is used.
Archive Structure
matryoshka_doll_2.zip (password: flag_Z26)
└── flag_A1.zip (password: flag_Y25)
└── flag_B2.zip (password: flag_X24)
└── flag_C3.zip (password: flag_W23)
└── ... (26 archives)
└── flag_Y25.zip (password: flag_A1)
└── flag_Z26.zip (NO PASSWORD!)
└── flag.txt
Decoding the Hint
The task description contains a key hint:
-
"первая – содержанием" (first - by its content)
- The first matryoshka contains the password for the last one
- The outer archive opens with password
flag_Z26(name of the last archive)
-
"последняя – его отсутствием" (last - by its absence)
- The last archive
flag_Z26.ziphas no password - "Absence of content" = absence of protection
- The last archive
Password Pattern
| Archive | Password | Logic |
|---|---|---|
| matryoshka_doll_2.zip | flag_Z26 | Name of the last archive |
| flag_A1.zip | flag_Y25 | A(0) → Y(24): 24-0=24 |
| flag_B2.zip | flag_X24 | B(1) → X(23): 24-1=23 |
| flag_C3.zip | flag_W23 | C(2) → W(22): 24-2=22 |
| ... | ... | ... |
| flag_Y25.zip | flag_A1 | Y(24) → A(0): 24-24=0 |
| flag_Z26.zip | (none) | No password |
| |
Formula: password_index = 24 - archive_index
Where archive_index = position of the letter in the alphabet (A=0, B=1, ..., Y=24)
Solution
Script for Automatic Extraction
python read-only • copy allowed#!/usr/bin/env python3 """ Matryoshka 2 - Nested ZIP extractor with reverse password pattern """ import zipfile import os import re def get_password_for_archive(archive_name): """ Get password based on archive name using reverse/mirror pattern. Pattern: flag_A1 -> flag_Y25, flag_B2 -> flag_X24, etc. Formula: password_letter_index = 24 - archive_letter_index """ match = re.match(r'flag_([A-Z])(\d+)\.zip', archive_name) if not match: return None letter = match.group(1) archive_index = ord(letter) - ord('A') # A=0, B=1, ..., Y=24, Z=25 password_index = 24 - archive_index # Y=24, X=23, ..., A=0 # Z (index 25) has no password if password_index < 0: return None password_letter = chr(ord('A') + password_index) password_num = password_index + 1 return f'flag_{password_letter}{password_num}' def extract_matreshka(): """Extract all nested archives following the reverse password pattern.""" # Step 1: Extract outer archive with password flag_Z26 # (first matryoshka's "content" is the password for the last one) print("[*] Extracting outer archive with password: flag_Z26") with zipfile.ZipFile('matryoshka_doll_2.zip', 'r') as zf: zf.extractall(pwd=b'flag_Z26') current_zip = "flag_A1.zip" count = 0 while os.path.exists(current_zip): count += 1 with zipfile.ZipFile(current_zip, 'r') as zf: for info in zf.infolist(): # Check if archive is encrypted is_encrypted = info.flag_bits & 0x1 if is_encrypted: password = get_password_for_archive(current_zip) if password: zf.extractall(pwd=password.encode()) print(f"[+] {current_zip} -> password: {password}") else: print(f"[-] Could not determine password for {current_zip}") return else: # Last archive (flag_Z26.zip) has no password zf.extractall() print(f"[+] {current_zip} (no password - last matryoshka!)") extracted = info.filename # Check if we found the flag if not extracted.endswith('.zip'): print(f"\n[*] Found non-zip file: {extracted}") with open(extracted, 'r') as f: flag = f.read().strip() print(f"[FLAG] {flag}") print(f"\n[*] Total archives extracted: {count}") return current_zip = extracted break # Only one file per archive if __name__ == "__main__": extract_matreshka()
Script Output
[*] Extracting outer archive with password: flag_Z26
[+] flag_A1.zip -> password: flag_Y25
[+] flag_B2.zip -> password: flag_X24
[+] flag_C3.zip -> password: flag_W23
[+] flag_D4.zip -> password: flag_V22
[+] flag_E5.zip -> password: flag_U21
[+] flag_F6.zip -> password: flag_T20
[+] flag_G7.zip -> password: flag_S19
[+] flag_H8.zip -> password: flag_R18
[+] flag_I9.zip -> password: flag_Q17
[+] flag_J10.zip -> password: flag_P16
[+] flag_K11.zip -> password: flag_O15
[+] flag_L12.zip -> password: flag_N14
[+] flag_M13.zip -> password: flag_M13
[+] flag_N14.zip -> password: flag_L12
[+] flag_O15.zip -> password: flag_K11
[+] flag_P16.zip -> password: flag_J10
[+] flag_Q17.zip -> password: flag_I9
[+] flag_R18.zip -> password: flag_H8
[+] flag_S19.zip -> password: flag_G7
[+] flag_T20.zip -> password: flag_F6
[+] flag_U21.zip -> password: flag_E5
[+] flag_V22.zip -> password: flag_D4
[+] flag_W23.zip -> password: flag_C3
[+] flag_X24.zip -> password: flag_B2
[+] flag_Y25.zip -> password: flag_A1
[+] flag_Z26.zip (no password - last matryoshka!)
[*] Found non-zip file: flag.txt
[FLAG] CODEBY{REDACTED}
[*] Total archives extracted: 26
Related Challenges
- Матрешка (Matryoshka) — HackerLab, simpler version
- Pattern: password = name of previous archive
- Flag:
CODEBY{M47RY0SHK4_D0LL_1S_D3F3473D}
Notes
- The name "Matryoshka 2" hints at a continuation with increased complexity
- Interesting detail:
flag_M13.zipuses passwordflag_M13(middle of the alphabet, mirrors itself) - The
bkcracktool can help with analysis if the pattern is unknown - Always read the task description carefully — it often contains key hints