|
|
@@ -0,0 +1,256 @@
|
|
|
+import time
|
|
|
+import sys
|
|
|
+import re
|
|
|
+import tkinter as tk
|
|
|
+from tkinter import scrolledtext
|
|
|
+import threading
|
|
|
+
|
|
|
+# --- ВАШИ ИСХОДНЫЕ ДАННЫЕ ---
|
|
|
+class Colors:
|
|
|
+ RESET = "\033[0m"
|
|
|
+ RED = "\033[91m"
|
|
|
+ GREEN = "\033[92m"
|
|
|
+ YELLOW = "\033[93m"
|
|
|
+ CYAN = "\033[96m"
|
|
|
+ BOLD = "\033[1m"
|
|
|
+
|
|
|
+def print_slow(text, delay=0.03):
|
|
|
+ """Функция для эффекта печатной машинки"""
|
|
|
+ for char in text:
|
|
|
+ sys.stdout.write(char)
|
|
|
+ sys.stdout.flush()
|
|
|
+ time.sleep(delay)
|
|
|
+ print()
|
|
|
+
|
|
|
+def print_scene_header(scene_num, title):
|
|
|
+ print(f"\n{Colors.YELLOW}{'='*50}")
|
|
|
+ print(f"{Colors.BOLD}СЦЕНА {scene_num}: {title.upper()}{Colors.RESET}")
|
|
|
+ print(f"{Colors.YELLOW}{'='*50}{Colors.RESET}\n")
|
|
|
+
|
|
|
+quiz_data = [
|
|
|
+ {
|
|
|
+ "scene": 1, "title": "Брифинг в рубке",
|
|
|
+ "text": "Главный инженер: «Новичок, наша сеть сейчас под атакой. Чтобы снять первую блокировку с консоли, напомни мне главный постулат современной архитектуры безопасности, на которую мы перешли».",
|
|
|
+ "question": "Что означает концепция Zero Trust (Нулевое доверие)?",
|
|
|
+ "options": ["Доверять всем устройствам внутри корпоративной сети по умолчанию", "Никогда не доверять, всегда проверять любой запрос на доступ", "Полностью заблокировать доступ в интернет всем сотрудникам", "Доверять только администраторам, а остальным запрещать всё"],
|
|
|
+ "correct": 1
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "scene": 2, "title": "Периметр под огнем",
|
|
|
+ "text": "Система разблокирована. Вы видите попытку входа под учеткой директора. Система запросила подтверждающий код из приложения. Нужно классифицировать эти два шага для журнала аудита.",
|
|
|
+ "question": "Ввод логина 'Director' — это идентификация или аутентификация? А ввод кода из приложения?",
|
|
|
+ "options": ["Оба действия — это аутентификация", "Логин — идентификация, код из приложения — аутентификация", "Логин — аутентификация, код из приложения — идентификация", "Оба действия — это идентификация"],
|
|
|
+ "correct": 1
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "scene": 3, "title": "Троянский конь изнутри",
|
|
|
+ "text": f"{Colors.RED}ВНИМАНИЕ: Обнаружена аномалия!{Colors.RESET} Сотрудник бухгалтерии в 03:00 ночи пытается скачать базу клиентов. Это не внешний хакер, угроза исходит изнутри периметра.",
|
|
|
+ "question": "К какому вектору угроз относится эта ситуация и какая система помогла ее обнаружить?",
|
|
|
+ "options": ["Внешний вектор, обнаружен с помощью антивируса", "Физический вектор, обнаружен камерами видеонаблюдения", "Внутренний вектор (инсайдер), обнаружен SIEM-системой", "Программный вектор, обнаружен файрволом"],
|
|
|
+ "correct": 2
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "scene": 4, "title": "Физическая брешь",
|
|
|
+ "text": "Охрана сообщает: «С рабочего стола украден ноутбук аналитика!» На нем хранились отчеты. Вору не осталось ничего, кроме самого железа и жесткого диска.",
|
|
|
+ "question": "Какие средства защиты гарантируют, что данные на украденном диске останутся нечитаемым набором символов?",
|
|
|
+ "options": ["Сложный пароль на Windows и скрытые папки", "Шифрование жесткого диска (FDE) и модуль доверенной загрузки (TPM)", "Установленный браузер Tor и отключенный Wi-Fi", "Регулярная дефрагментация диска и очистка корзины"],
|
|
|
+ "correct": 1
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "scene": 5, "title": "Восстановление системы",
|
|
|
+ "text": "Благодаря вашим верным решениям, скомпрометированный аккаунт заблокирован, а данные в безопасности. Штатные вирусы изолированы. Настало время закрыть инцидент.",
|
|
|
+ "question": "Согласно модели NIST, как называется финальный этап жизненного цикла реагирования на инцидент, на котором данные восстанавливаются из бэкапов?",
|
|
|
+ "options": ["Защита (Protect)", "Обнаружение (Detect)", "Реагирование (Respond)", "Восстановление (Recover)"],
|
|
|
+ "correct": 3
|
|
|
+ }
|
|
|
+]
|
|
|
+
|
|
|
+# --- КЛАСС ГРАФИЧЕСКОГО ИНТЕРФЕЙСА ---
|
|
|
+class HackerApp:
|
|
|
+ def __init__(self, root):
|
|
|
+ self.root = root
|
|
|
+ self.root.title("ОПЕРАЦИЯ: НУЛЕВОЕ ДОВЕРИЕ")
|
|
|
+ self.root.geometry("800x600")
|
|
|
+ self.root.configure(bg="black")
|
|
|
+
|
|
|
+ # Настройка шрифта (Consolas или монопшринный по умолчанию)
|
|
|
+ self.font = ("Consolas", 11)
|
|
|
+
|
|
|
+ # Поле вывода текста (Терминал)
|
|
|
+ self.text_area = scrolledtext.ScrolledText(
|
|
|
+ root,
|
|
|
+ bg="#050505",
|
|
|
+ fg="#00FF41",
|
|
|
+ font=self.font,
|
|
|
+ insertbackground="white",
|
|
|
+ wrap=tk.WORD,
|
|
|
+ state=tk.DISABLED, # Запрещаем ручной ввод в главное окно
|
|
|
+ borderwidth=0,
|
|
|
+ highlightthickness=0
|
|
|
+ )
|
|
|
+ self.text_area.pack(padx=10, pady=(10, 0), fill=tk.BOTH, expand=True)
|
|
|
+
|
|
|
+ # Строка ввода ответов
|
|
|
+ self.input_frame = tk.Frame(root, bg="black")
|
|
|
+ self.input_frame.pack(padx=10, pady=10, fill=tk.X)
|
|
|
+
|
|
|
+ self.input_var = tk.StringVar()
|
|
|
+ self.input_entry = tk.Entry(
|
|
|
+ self.input_frame,
|
|
|
+ textvariable=self.input_var,
|
|
|
+ bg="#0A0A0A",
|
|
|
+ fg="#00FFFF",
|
|
|
+ font=self.font,
|
|
|
+ insertbackground="#00FFFF",
|
|
|
+ borderwidth=1,
|
|
|
+ relief="solid"
|
|
|
+ )
|
|
|
+ self.input_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, ipady=5)
|
|
|
+ self.input_entry.bind("<Return>", self.on_enter)
|
|
|
+
|
|
|
+ # Механизм замены стандартного ввода/вывода
|
|
|
+ self.input_ready = threading.Event()
|
|
|
+ self.user_input = ""
|
|
|
+
|
|
|
+ # Перенаправляем стандартный вывод (print) в наше окно
|
|
|
+ sys.stdout = self
|
|
|
+
|
|
|
+ # Функция для записи в текстовое поле (с поддержкой потоков)
|
|
|
+ def write(self, text):
|
|
|
+ # Очищаем ANSI-коды, так как Tkinter их не понимает,
|
|
|
+ # но заменяем их на теги цветов Tkinter
|
|
|
+ clean_text = re.sub(r'\x1B\[[0-?]*[ -/]*[@-~]', '', text)
|
|
|
+
|
|
|
+ def _write():
|
|
|
+ self.text_area.config(state=tk.NORMAL)
|
|
|
+
|
|
|
+ # Простая эмуляция цветов (зеленный по умолчанию)
|
|
|
+ if Colors.RED in text:
|
|
|
+ self.text_area.insert(tk.END, clean_text, "red")
|
|
|
+ elif Colors.YELLOW in text:
|
|
|
+ self.text_area.insert(tk.END, clean_text, "yellow")
|
|
|
+ elif Colors.CYAN in text:
|
|
|
+ self.text_area.insert(tk.END, clean_text, "cyan")
|
|
|
+ elif Colors.BOLD in text:
|
|
|
+ self.text_area.insert(tk.END, clean_text, "bold")
|
|
|
+ else:
|
|
|
+ self.text_area.insert(tk.END, clean_text)
|
|
|
+
|
|
|
+ self.text_area.see(tk.END)
|
|
|
+ self.text_area.config(state=tk.DISABLED)
|
|
|
+
|
|
|
+ # Используем root.after для безопасности потоков в Tkinter
|
|
|
+ self.root.after(0, _write)
|
|
|
+
|
|
|
+ def flush(self):
|
|
|
+ pass # Заглушка для sys.stdout
|
|
|
+
|
|
|
+ # Обработка нажатия Enter в строке ввода
|
|
|
+ def on_enter(self, event):
|
|
|
+ self.user_input = self.input_var.get()
|
|
|
+ self.input_entry.delete(0, tk.END)
|
|
|
+ self.input_entry.config(state=tk.DISABLED) # Блокируем ввод пока идет процесс
|
|
|
+ self.input_ready.set() # Сигнализируем основному потоку, что ответ получен
|
|
|
+
|
|
|
+ # Замена стандартной функции input()
|
|
|
+ def gui_input(self, prompt):
|
|
|
+ self.write(prompt)
|
|
|
+
|
|
|
+ self.input_ready.clear()
|
|
|
+
|
|
|
+ # Разблокируем поле ввода в главном потоке интерфейса
|
|
|
+ def unlock():
|
|
|
+ self.input_entry.config(state=tk.NORMAL)
|
|
|
+ self.input_entry.focus_set()
|
|
|
+ self.root.after(0, unlock)
|
|
|
+
|
|
|
+ # Ждем, пока пользователь не нажмет Enter
|
|
|
+ self.input_ready.wait()
|
|
|
+
|
|
|
+ # Выводим то, что ввел пользователь (эффект эха)
|
|
|
+ self.write(self.user_input + "\n")
|
|
|
+ return self.user_input
|
|
|
+
|
|
|
+
|
|
|
+# --- ИЗМЕНЕННАЯ ФУНКЦИЯ MAIN ---
|
|
|
+def main(app):
|
|
|
+ print(f"{Colors.CYAN}{Colors.BOLD}")
|
|
|
+ print("╔══════════════════════════════════════════════╗")
|
|
|
+ print("║ ОПЕРАЦИЯ: НУЛЕВОЕ ДОВЕРИЕ ║")
|
|
|
+ print("║ СИМУЛЯЦИЯ ИБ-АНАЛИТИКА ║")
|
|
|
+ print("╚══════════════════════════════════════════════╝{Colors.RESET}\n")
|
|
|
+
|
|
|
+ print_slow("Инициализация терминала безопасности...")
|
|
|
+ time.sleep(1)
|
|
|
+ print_slow("Загрузка базы данных лекций по НСД...")
|
|
|
+ time.sleep(1)
|
|
|
+ print_slow(f"{Colors.YELLOW}Статус: ТРЕВОГА. Требуется подтверждение допуска.{Colors.RESET}")
|
|
|
+ time.sleep(1)
|
|
|
+
|
|
|
+ score = 0
|
|
|
+
|
|
|
+ for level in quiz_data:
|
|
|
+ print_scene_header(level["scene"], level["title"])
|
|
|
+ print_slow(level["text"])
|
|
|
+ time.sleep(0.5)
|
|
|
+
|
|
|
+ print(f"\n{Colors.BOLD}>> ВАШ ОТВЕТ:{Colors.RESET}")
|
|
|
+ for i, option in enumerate(level["options"]):
|
|
|
+ print(f" [{i + 1}] {option}")
|
|
|
+
|
|
|
+ # Заменяем стандартный input() на наш графический
|
|
|
+ while True:
|
|
|
+ try:
|
|
|
+ # ВАЖНО: тут используем app.gui_input вместо обычного input
|
|
|
+ user_answer = int(app.gui_input(f"\n{Colors.CYAN}Введите номер варианта (1-{len(level['options'])}: ")) - 1
|
|
|
+ if 0 <= user_answer < len(level["options"]):
|
|
|
+ break
|
|
|
+ else:
|
|
|
+ print(f"{Colors.RED}Ошибка: Выбран несуществующий вариант. Попробуйте снова.{Colors.RESET}")
|
|
|
+ except ValueError:
|
|
|
+ print(f"{Colors.RED}Ошибка: Введите пожалуйста цифру.{Colors.RESET}")
|
|
|
+
|
|
|
+ print("\n" + "-"*40)
|
|
|
+ if user_answer == level["correct"]:
|
|
|
+ print(f"{Colors.GREEN}[ УСПЕХ ] Верный ответ! Угроза нейтрализована.{Colors.RESET}")
|
|
|
+ score += 1
|
|
|
+ else:
|
|
|
+ print(f"{Colors.RED}[ ПРОВАЛ ] Неверный ответ! Системе нанесен ущерб.{Colors.RESET}")
|
|
|
+ correct_text = level["options"][level["correct"]]
|
|
|
+ print(f"{Colors.YELLOW}Подсказка системы: Правильный ответ -> {correct_text}{Colors.RESET}")
|
|
|
+ print("-"*40 + "\n")
|
|
|
+
|
|
|
+ time.sleep(1.5)
|
|
|
+
|
|
|
+ # Финал игры
|
|
|
+ print(f"{Colors.YELLOW}{'='*50}{Colors.RESET}")
|
|
|
+ print(f"{Colors.BOLD}СИМУЛЯЦИЯ ЗАВЕРШЕНА{Colors.RESET}")
|
|
|
+ print(f"{Colors.YELLOW}{'='*50}{Colors.RESET}\n")
|
|
|
+
|
|
|
+ print_slow(f"Итоговый результат: {score} из {len(quiz_data)} верных решений.")
|
|
|
+
|
|
|
+ if score == len(quiz_data):
|
|
|
+ print_slow(f"\n{Colors.GREEN}{Colors.BOLD}РАНГ: ЭКСПЕРТ. Вы полностью остановили утечку данных и доказали, что уместно применять меры защиты заблаговременно. Добро пожаловать в отдел ИБ!{Colors.RESET}")
|
|
|
+ elif score >= 3:
|
|
|
+ print_slow(f"\n{Colors.YELLOW}{Colors.BOLD}РАНГ: УЧЕНИК. Атака частично отражена, но компания понесла некоторые потери. Вам стоит перечитать лекцию про внутренние угрозы и криптографию.{Colors.RESET}")
|
|
|
+ else:
|
|
|
+ print_slow(f"\n{Colors.RED}{Colors.BOLD}РАНГ: ПРОВАЛ. Сервера скомпрометированы, данные утекли в сеть. Уступить инициативу — значит допустить потерю данных. Симуляция провалена.{Colors.RESET}")
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ # Создаем графическое окно
|
|
|
+ root = tk.Tk()
|
|
|
+ app = HackerApp(root)
|
|
|
+
|
|
|
+ # Настраиваем цветовые теги для текста в Tkinter
|
|
|
+ app.text_area.tag_config("red", foreground="#FF4444")
|
|
|
+ app.text_area.tag_config("yellow", foreground="#FFFF00")
|
|
|
+ app.text_area.tag_config("cyan", foreground="#00FFFF")
|
|
|
+ app.text_area.tag_config("bold", font=("Consolas", 11, "bold"), foreground="#FFFFFF")
|
|
|
+
|
|
|
+ # Запускаем логику игры в отдельном потоке, чтобы не зависало окно
|
|
|
+ game_thread = threading.Thread(target=main, args=(app,), daemon=True)
|
|
|
+ game_thread.start()
|
|
|
+
|
|
|
+ # Запускаем графический цикл
|
|
|
+ root.mainloop()
|