← Back to Writeups
CUSTOMMediumForensics

Матрешка 2 (Matryoshka 2)

XESXOR9/2/20264 min read
#forensics#custom#medium#nested_archive_extraction#reverse_password_pattern#scripted_extraction

Матрешка 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:

  1. "первая – содержанием" (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) ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍
  2. "последняя – его отсутствием" (last - by its absence)

    • The last archive flag_Z26.zip has no password
    • "Absence of content" = absence of protection

Password Pattern

ArchivePasswordLogic
matryoshka_doll_2.zipflag_Z26Name of the last archive
flag_A1.zipflag_Y25A(0) → Y(24): 24-0=24
flag_B2.zipflag_X24B(1) → X(23): 24-1=23
flag_C3.zipflag_W23C(2) → W(22): 24-2=22
.........
flag_Y25.zipflag_A1Y(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
#!/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

  1. The name "Matryoshka 2" hints at a continuation with increased complexity
  2. Interesting detail: flag_M13.zip uses password flag_M13 (middle of the alphabet, mirrors itself)
  3. The bkcrack tool can help with analysis if the pattern is unknown
  4. Always read the task description carefully — it often contains key hints ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍