#!/usr/bin/env python3 import base64 import csv import re import os from pathlib import Path PUBLIC_DIR = Path(__file__).resolve().parent.parent / "public" def extract_part1_br34d(): """ Часть 1: 'br34d' Цепочка: resume.pdf -> ID сотрудника 'NT-3893' business_card.png -> ИНН 7707083893 (последние 4 цифры = 3893, подтверждает связь с NordTech) commits.log -> тикет NT-3893 ссылается на 'module-br34d' """ print("[Шаг 1] Извлечение части 1: br34d") employee_id = "NT-3893" print(f" [1a] resume.pdf — ID сотрудника: {employee_id}") print(" [1b] business_card.png — ИНН: 7707083893 (последние 4 цифры = 3893, совпадает)") commits = (PUBLIC_DIR / "commits.log").read_text() # Ищем коммит вида: feat(NT-3893): integrate module-<название> pattern = rf"feat\({employee_id}\): integrate (module-\w+)" match = re.search(pattern, commits) if match: module_name = match.group(1) part1 = module_name.replace("module-", "") # Убираем префикс, оставляем только кодовое слово print(f" [1c] commits.log: '{match.group(0)}'") print(f" Часть 1: '{part1}'") return part1 return None def extract_part2_crumbs(): """ Часть 2: 'crumbs' (метод второго раунда) Цепочка: profile_photo.jpg GPS -> 55.7616N, 37.6385E postal_codes.csv -> находим почтовый индекс 101000, соответствующий этим координатам столбец sector_code = '6372756d6273' -> hex в ASCII = 'crumbs' """ print("\n[Шаг 2] Извлечение части 2: crumbs (через поиск по почтовому индексу)") # Шаг 2a: Извлекаем GPS-координаты из EXIF-данных фотографии import piexif img_path = str(PUBLIC_DIR / "profile_photo.jpg") exif = piexif.load(img_path) def parse_gps_coord(coord_data, ref): """Конвертируем GPS из формата DMS (градусы/минуты/секунды) в десятичные градусы.""" degrees = coord_data[0][0] / coord_data[0][1] minutes = coord_data[1][0] / coord_data[1][1] seconds = coord_data[2][0] / coord_data[2][1] result = degrees + minutes / 60 + seconds / 3600 if ref in [b'S', b'W']: # Южная широта и западная долгота — отрицательные result = -result return result gps = exif.get("GPS", {}) lat = parse_gps_coord(gps[piexif.GPSIFD.GPSLatitude], gps[piexif.GPSIFD.GPSLatitudeRef]) lon = parse_gps_coord(gps[piexif.GPSIFD.GPSLongitude], gps[piexif.GPSIFD.GPSLongitudeRef]) print(f" [2a] GPS: {lat:.4f}N, {lon:.4f}E (район Чистопрудного бульвара)") # Шаг 2b: Ищем ближайшую точку в таблице почтовых индексов по манхэттенскому расстоянию csv_path = PUBLIC_DIR / "postal_codes.csv" best_match = None best_dist = float('inf') with open(csv_path) as f: reader = csv.DictReader(f) for row in reader: rlat = float(row['latitude']) rlon = float(row['longitude']) # Манхэттенское расстояние — достаточно для грубого геопоиска dist = abs(rlat - lat) + abs(rlon - lon) if dist < best_dist: best_dist = dist best_match = row print(f" [2b] Ближайший индекс: {best_match['postal_code']} ({best_match['district']}, dist={best_dist:.4f})") print(f" sector_code: {best_match['sector_code']}") # Шаг 2c: Декодируем hex-строку sector_code в ASCII — это и есть часть флага hex_str = best_match['sector_code'] decoded = bytes.fromhex(hex_str).decode('ascii') print(f" [2c] hex -> ASCII: '{decoded}'") return decoded def extract_part3_l34d(): """ Часть 3: 'l34d' Цепочка: commits.log -> ссылка на коммит SHA 'a7c3e91' во внешнем репозитории repo_snapshot.txt -> коммит a7c3e91 содержит base64-строку base64-декодирование -> 'module-l34d-integration-v2.1.0' """ print("\n[Шаг 3] Извлечение части 3: l34d") commits = (PUBLIC_DIR / "commits.log").read_text() # Ищем отсылку к внешнему репозиторию вида: "See commit in " ref_match = re.search(r"See commit\s+(\w+)\s+in\s+([\w\-\.\/]+)", commits, re.DOTALL) if not ref_match: return None sha_prefix = ref_match.group(1) # Короткий SHA (7 символов) repo = ref_match.group(2) print(f" [3a] commits.log ссылается на {sha_prefix} в {repo}") snapshot = (PUBLIC_DIR / "repo_snapshot.txt").read_text() # Находим весь блок коммита — от нашего SHA до следующего коммита commit_pattern = rf"commit {sha_prefix}\w*\n.*?(?=\ncommit [a-f0-9]{{40}}|\Z)" commit_match = re.search(commit_pattern, snapshot, re.DOTALL) if not commit_match: return None commit_block = commit_match.group(0) # Ищем все потенциальные base64-строки длиной от 20 символов b64_pattern = r'[A-Za-z0-9+/]{20,}={0,2}' b64_matches = re.findall(b64_pattern, commit_block) for b64_str in b64_matches: try: decoded = base64.b64decode(b64_str).decode("utf-8", errors="ignore") if "module-" in decoded: # Из строки вида "module-l34d-integration-v2.1.0" берём только кодовое слово mod_match = re.search(r"module-(\w+)", decoded) if mod_match: part3 = mod_match.group(1).split("-")[0] # Только первый сегмент до дефиса print(f" [3b] base64: {b64_str}") print(f" decoded: {decoded}") print(f" Часть 3: '{part3}'") return part3 except Exception: continue return None def extract_part4_h0m3(): """ Часть 4: 'h0m3' (метод второго раунда) Цепочка: browser_render.png -> LSB-стеганография в красном канале Извлекаем LSB из первых пикселей -> 'h0m3' """ print("\n[Шаг 4] Извлечение части 4: h0m3 (через LSB-стеганографию)") from PIL import Image import numpy as np img = Image.open(str(PUBLIC_DIR / "browser_render.png")) pixels = np.array(img) # Разворачиваем красный канал (индекс 0) в одномерный массив flat_r = pixels[:, :, 0].flatten() # Читаем байты: каждые 8 последовательных LSB пикселей = 1 символ result = bytearray() for byte_idx in range(100): # Ограничение на 100 байт во избежание зависания byte_val = 0 for bit_idx in range(8): pixel_idx = byte_idx * 8 + bit_idx byte_val = (byte_val << 1) | (flat_r[pixel_idx] & 1) if byte_val == 0: break # Нулевой байт — признак конца скрытых данных result.append(byte_val) decoded = result.decode('utf-8', errors='replace') print(f" [4a] LSB из красного канала (сырые байты): {result}") print(f" [4b] Декодировано: '{decoded}'") # Данные могут начинаться с маркера трассировки вида [N/4] — удаляем его import re m = re.match(r'\[\d/\d\](.*)', decoded) if m: decoded = m.group(1) print(f" [4c] После удаления маркера трассировки: '{decoded}'") return decoded def main(): print("=" * 60) print("OSINT-задание «Пропавший коллега» — Решение (R2)") print("=" * 60) print() part1 = extract_part1_br34d() part2 = extract_part2_crumbs() part3 = extract_part3_l34d() part4 = extract_part4_h0m3() print("\n" + "=" * 60) print("Сборка флага") print("=" * 60) print(f" Часть 1 (commits.log через NT-3893): {part1}") print(f" Часть 2 (sector_code почтового индекса): {part2}") print(f" Часть 3 (base64 из внешнего репозитория): {part3}") print(f" Часть 4 (LSB-стего в browser_render.png): {part4}") if __name__ == "__main__": main()