Files
2026-04-22 10:58:32 +03:00

204 lines
9.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 <sha> in <repo>"
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()