EN RU

Полное исследование и модернизация RDPmon для Windows 10/11

Оглавление
  1. 1. Введение
  2. 2. Руководство для обычных пользователей
  3. 3. Руководство для разработчиков
  4. 4. Ссылки по теме

1. Введение

1.1 Обзор системы

RDPmon представляет собой открытое решение для мониторинга RDP-подключений, разработанное компанией Cameyo для защиты от атак методом перебора (brute-force). Система предназначена для мониторинга и анализа попыток подключения к серверам Windows через протокол удалённого рабочего стола (Remote Desktop Protocol).[^1][^2][^3]

Основные возможности RDPmon:

  • Мониторинг всех RDP-подключений в реальном времени
  • Обнаружение атак методом перебора паролей
  • Отслеживание активных и завершённых сессий пользователей
  • Анализ запущенных процессов в RDP-сессиях
  • Цветовая индикация уровня угрозы (зелёный, жёлтый, красный)
  • Полная конфиденциальность данных (всё остаётся локально)

Проблемы, решаемые RDPmon:
Согласно исследованиям Cameyo, облачные машины Windows с открытым портом 3389 подвергаются более чем 150,000 попыток входа в неделю. RDPmon адресует критическую потребность в мониторинге RDP-безопасности для облачных инфраструктур.[^2][^4][^1]

1.2 Архитектура решения

RDPmon построен на основе многоуровневой архитектуры, включающей следующие компоненты:

Архитектура RDPmon - диаграмма компонентов системы

Архитектура RDPmon — диаграмма компонентов системы

Основные компоненты:

  1. Windows Service (RdpMon.exe) — центральная служба, обеспечивающая непрерывный мониторинг
  2. GUI Application — пользовательский интерфейс для просмотра и управления данными
  3. Мониторинговые модули:
    • ConnectMon — мониторинг RDP-подключений
    • SessionMon — отслеживание пользовательских сессий
    • ProcessMon — мониторинг запущенных процессов
  4. База данных LiteDB — локальное хранение всех данных мониторинга
  5. Интеграция с Windows API:
    • Windows Event Log (события безопасности)
    • Windows Terminal Services API (информация о сессиях)

Принципы работы:

  • Служба работает в фоновом режиме с правами SYSTEM
  • GUI подключается к службе для отображения данных в реальном времени
  • Все данные остаются локально в файле LiteDB
  • Система использует Windows ETW для получения событий безопасности

1.3 Новые возможности

В рамках модернизации RDPmon были спроектированы следующие расширения функциональности:

Контекстное меню для записей:

  • Блокировать IP через Windows Firewall
  • Копировать IP-адрес в буфер обмена
  • Отправить информацию об атаке в AbuseIPDB[^5][^6][^7][^8]

Новые вкладки интерфейса:

  • Settings — централизованные настройки системы
  • Reports — генерация и просмотр отчётов

Расширенные настройки безопасности:

  • Белый список IP-адресов (исключения)
  • Чёрный список IP-адресов (автоматическая и ручная блокировка)
  • Настройка количества неудачных попыток для блокировки
  • Интеграция с AbuseIPDB API для проверки репутации IP
  • Автоматическая блокировка на основе Confidence of Abuse %

2. Руководство для обычных пользователей

2.1 Установка и первоначальная настройка

2.1.1 Системные требования

Операционная система:

  • Windows 10 версии 1903 и выше
  • Windows 11 (все версии)
  • Windows Server 2019/2022

Программные требования:

  • .NET Framework 4.6.1 или выше
  • Права администратора для установки службы
  • Доступ к интернету для интеграции с AbuseIPDB (опционально)

Системные ресурсы:

  • ОЗУ: минимум 100 МБ, рекомендуется 256 МБ
  • Дисковое пространство: 50 МБ + место для базы данных (растёт по мере накопления данных)
  • CPU: минимальное влияние на производительность системы

2.1.2 Процесс установки

  1. Загрузка RdpMon.exe с официального репозитория GitHub
  2. Первый запуск — система предложит установить службу автоматически
  3. Подтверждение установки службы “RDP Monitor”
  4. Автоматический запуск службы после установки
  5. Запуск GUI для начала работы с интерфейсом

Важные особенности:

  • При первом запуске требуются права администратора для установки службы
  • Служба устанавливается автоматически и не требует дополнительной настройки
  • Удаление службы: rdpmon.exe -uninst

2.1.3 Первый запуск и автоматическая установка службы

При первом запуске RdpMon.exe выполняется автоматическая проверка установки службы. Если служба не обнаружена, появляется диалог подтверждения установки. После согласия пользователя:

  1. Система запрашивает повышение привилегий (UAC)
  2. Устанавливается служба Windows “RdpMon”
  3. Служба автоматически запускается
  4. Открывается главное окно приложения
  5. Начинается сбор данных о RDP-подключениях

2.2 Работа с основным интерфейсом

2.2.1 Вкладка Connections (Подключения)

Основная вкладка для мониторинга RDP-подключений отображает агрегированную информацию по IP-адресам:

Столбцы таблицы:

  • IP Address — адрес источника подключений
  • Fail Count — количество неудачных попыток входа
  • Success Count — количество успешных подключений
  • First Time — время первого обнаруженного подключения
  • Last Time — время последнего зафиксированного события
  • Duration — продолжительность активности (для текущих атак показывается “ongoing”)
  • Logins — список пользователей, с которыми пытались подключиться

Цветовая индикация:

  • 🟢 Зелёный — легитимные подключения (есть успешные входы, мало неудачных попыток)
  • 🟡 Жёлтый — подозрительная активность (несколько неудачных попыток)
  • 🔴 Красный — атаки методом перебора (10+ неудачных попыток подряд)

Фильтры отображения:

  • Показать атаки (красные записи)
  • Показать легитимные подключения (зелёные записи)
  • Показать неопределённые (жёлтые записи)

2.2.2 Вкладка Sessions (Сессии)

Детальная информация о пользовательских сессиях:

Информация о сессиях:

  • Session ID — уникальный идентификатор WTS сессии
  • User — имя пользователя
  • State — статус сессии (Active, Disconnected, Ended)
  • Started — время начала сессии
  • Ended — время завершения (для активных показывается “ongoing”)
  • Address — IP-адрес клиента

Дополнительная функциональность:

  • Просмотр процессов, запущенных в выбранной сессии
  • Возможность теневого подключения к активным сессиям (Shadow)
  • Фильтрация системных процессов

2.2.3 Новая вкладка Settings (Настройки)

Централизованная панель настроек системы безопасности:

Основные настройки:

  • Белый список IP — доверенные адреса, исключённые из блокировки
  • Чёрный список IP — заблокированные адреса
  • Failures для блокировки — количество неудачных попыток для автоматической блокировки
  • Failures для AbuseIPDB — порог для отправки сообщения о злоумышленнике

Настройки AbuseIPDB:

  • API Key — ключ для доступа к AbuseIPDB API
  • Проверять IP после X failures — автоматическая проверка репутации
  • Confidence of Abuse % — процент уверенности для классификации как вредоносный

2.2.4 Новая вкладка Reports (Отчёты)

Система генерации отчётов для анализа безопасности:

Типы отчётов:

  • Сводка по атакам — статистика за выбранный период
  • Топ атакующих IP — наиболее активные злоумышленники
  • Географическое распределение — анализ по странам (при интеграции с GeoIP)
  • Временные паттерны — активность атак по времени

Настройки экспорта:

  • Формат: CSV, PDF, HTML
  • Период: за день, неделю, месяц, произвольный
  • Автоматическая отправка по email (в перспективе)

2.3 Использование контекстного меню

2.3.1 Блокировка IP-адресов

Щелчок правой кнопкой мыши по записи в таблице подключений открывает контекстное меню с опциями:

“Блокировать IP”:

  1. Создаётся правило в Windows Firewall
  2. Блокируется весь трафик с данного IP на порт 3389 (TCP/UDP)
  3. IP добавляется в чёрный список системы
  4. Записывается событие в лог системы

Особенности блокировки:

  • Правила создаются для входящих подключений
  • Действуют на всех сетевых профилях (Domain, Private, Public)
  • Автоматическое именование правил: “RdpMon.BlackList TCP/UDP”

2.3.2 Копирование IP-адресов

“Копировать IP” — быстрое копирование IP-адреса в буфер обмена для:

  • Дальнейшего анализа в внешних системах
  • Добавления в конфигурации сетевого оборудования
  • Передачи коллегам для расследования инцидента

2.3.3 Отправка сообщений в AbuseIPDB

“Отправить информацию о атаке в AbuseIPDB”:

Автоматическая отправка отчёта о вредоносной активности включает:

  • IP-адрес злоумышленника
  • Категории атаки (по классификации AbuseIPDB)[^5]
  • Комментарий с деталями атаки (количество попыток, период)
  • Автоматическую классификацию как “Brute Force” (категория 18)

Требования:

  • Настроенный API ключ AbuseIPDB
  • Подключение к интернету
  • Соблюдение лимитов API (1000 запросов/день для бесплатного аккаунта)

2.4 Настройка параметров безопасности

2.4.1 Управление белым списком IP

Белый список защищает доверенные IP-адреса от автоматической блокировки:

Случаи использования:

  • Офисные IP-адреса администраторов
  • VPN-серверы компании
  • Адреса систем мониторинга
  • Партнёрские организации

Управление списком:

  • Добавление через интерфейс Settings
  • Импорт из файла (формат: один IP на строку)
  • Поддержка диапазонов IP (CIDR нотация)
  • Автоматическое исключение localhost (127.0.0.1)

2.4.2 Управление чёрным списком IP

Чёрный список содержит заблокированные IP-адреса:

Источники пополнения:

  • Ручная блокировка через контекстное меню
  • Автоматическая блокировка при превышении лимита попыток
  • Импорт внешних списков угроз
  • Синхронизация с AbuseIPDB

Функции управления:

  • Просмотр всех заблокированных IP
  • Снятие блокировки (разблокировка)
  • Просмотр истории блокировок
  • Экспорт списка для резервного копирования

2.4.3 Настройка количества попыток для блокировки

Настройка “Failures для блокировки”:

  • По умолчанию: 10 неудачных попыток
  • Диапазон: от 3 до 100 попыток
  • Учитывается скользящее окно времени (последние 24 часа)

Настройка “Failures для отправки в AbuseIPDB”:

  • По умолчанию: 20 неудачных попыток
  • Предотвращает спам-отчёты в AbuseIPDB
  • Должна быть больше или равна настройке блокировки

2.4.4 Конфигурация AbuseIPDB API

Получение API ключа:

  1. Регистрация на abuseipdb.com
  2. Верификация домена (для увеличения лимитов)
  3. Создание API ключа v2 в панели управления

Настройки интеграции:

  • API Key — вставка полученного ключа
  • Test Connection — проверка доступности API
  • Daily Limit Monitoring — отслеживание использованных запросов

2.4.5 Настройка автоматической проверки

“Проверять IP в AbuseIPDB после X Failures”:

  • Автоматический запрос к AbuseIPDB при достижении порога
  • Получение Confidence of Abuse % для IP
  • Принятие решения о блокировке на основе репутации

“Confidence of Abuse % для блокировки”:

  • По умолчанию: 75% (высокая уверенность в том, что IP вредоносный)
  • Диапазон: от 25% до 100%
  • Рекомендуется: 75-90% для баланса между безопасностью и ложными срабатываниями

2.5 Мониторинг и анализ

2.5.1 Интерпретация цветовой маркировки

🟢 Зелёные записи (легитимные):

  • Есть успешные подключения
  • Менее 100 неудачных попыток
  • Обычно администраторы или авторизованные пользователи

🟡 Жёлтые записи (подозрительные):

  • Нет успешных подключений
  • Менее 10 неудачных попыток
  • Могут быть случайными сканированиями или ошибками пользователей

🔴 Красные записи (атаки):

  • 10 или более неудачных попыток подряд
  • Нет успешных подключений
  • Высокая вероятность brute-force атаки

Специальные индикаторы:

  • “ongoing” — атака продолжается в данный момент (последняя активность менее 2 минут назад)
  • Мигающие записи — новая активность в реальном времени

2.5.2 Анализ статистики подключений

Общая статистика (строка состояния):

  • Количество легитимных пользователей
  • Количество подозрительных адресов
  • Общее количество попыток входа
  • Предупреждение об отключённом NLA (Network Level Authentication)

Паттерны атак:

  • Распределённые атаки — множество IP с небольшим количеством попыток каждый
  • Концентрированные атаки — один IP с большим количеством попыток
  • Продолжительные атаки — длительные по времени кампании

2.5.3 Работа с отчётами

Генерация отчётов:

  1. Переход на вкладку Reports
  2. Выбор типа отчёта и периода
  3. Настройка параметров фильтрации
  4. Генерация и просмотр результатов

Анализ трендов:

  • Динамика количества атак по времени
  • Сезонность атакующей активности
  • Корреляция с внешними событиями (публикация уязвимостей, праздники)

Экспорт данных:

  • Сохранение в различных форматах для дальнейшего анализа
  • Интеграция с системами Business Intelligence
  • Предоставление отчётов руководству и службе безопасности

3. Руководство для разработчиков

3.1 Архитектура системы

3.1.1 Обзор компонентов

RDPmon построен на основе многоуровневой архитектуры, которая обеспечивает разделение ответственности и гибкость системы:[^9][^10]

Уровень представления (Presentation Layer):

  • MainForm.cs — главная форма приложения
  • SettingsForm.cs — форма настроек (новая)
  • ReportsForm.cs — форма отчётов (новая)
  • ShadowForm.cs — форма теневого подключения

Уровень бизнес-логики (Business Layer):

  • ConnectMon.cs — мониторинг подключений
  • SessionMon.cs — отслеживание сессий
  • ProcessMon.cs — мониторинг процессов
  • Новые модули расширения функциональности

Уровень служб (Service Layer):

  • Service.cs — Windows Service реализация
  • Program.cs — точка входа и управление службой

Уровень доступа к данным (Data Access Layer):

  • LiteDB — встроенная NoSQL база данных[^11][^12]
  • Модели данных: Addr, Session, Process

3.1.2 Паттерны проектирования

Service Pattern: Windows Service обеспечивает непрерывную работу мониторинга в фоновом режиме, независимо от пользовательских сессий.[^10]

Observer Pattern: Система событий для уведомления GUI о изменениях в данных:

EventWaitHandle waitEvent = new EventWaitHandle(false, EventResetMode.AutoReset, @"Global\RdpMonRefresh");

Repository Pattern: Инкапсуляция логики доступа к данным LiteDB:

public class Addrs
{
    private LiteDatabase db;
    private LiteCollection<Addr> addrTable;
    
    public void Aggregate(LiteCollection<Addr> addrTable, string ip, DateTime utcTime, bool success, string userName)
    {
        // Логика агрегации данных
    }
}

Factory Pattern: Создание мониторинговых компонентов:

var connectionsAggregator = new ConnectMon(true, true);
var sessionMon = new SessionMon();
var processMon = new ProcessMon();

3.1.3 Взаимодействие между компонентами

Схема взаимодействия:

  1. Windows Service запускается автоматически при загрузке системы
  2. ConnectMon периодически агрегирует события из Windows Event Log
  3. SessionMon отслеживает изменения сессий через WTS API
  4. Данные сохраняются в LiteDB для персистентности
  5. GUI подключается к службе через глобальные события
  6. Новые модули интегрируются через dependency injection

3.2 Структура исходного кода

3.2.1 Основные классы и модули

ConnectMon.cs — Мониторинг подключений:

public class ConnectMon
{
    public const int FailureEvtId = 4625;  // Неудачный вход
    public const int SuccessEvtId = 4648;  // Успешный вход
    
    public Addrs Aggregate(DateTime fromUtc)
    {
        // Запрос событий из Event Log
        // Агрегация по IP-адресам
        // Обновление базы данных
    }
}

SessionMon.cs — Мониторинг сессий:

public class SessionMon
{
    public void OnSessionChange(SessionChangeDescription sessionChange)
    {
        switch (sessionChange.Reason)
        {
            case SessionChangeReason.SessionLogon:
                // Обработка входа в сессию
                break;
            case SessionChangeReason.SessionLogoff:
                // Обработка выхода из сессии
                break;
        }
    }
}

Addr.cs — Модель IP-адреса:

public class Addr
{
    public string AddrId { get; set; }
    public int FailCount { get; set; }
    public int SuccessCount { get; set; }
    public DateTime First { get; set; }
    public DateTime Last { get; set; }
    public HashSet<string> UserNames { get; set; }
    
    public bool IsAttack(int offset = 0) => (FailCount >= 10 + offset);
    public bool IsLegit() => (SuccessCount > 0 && FailCount < 100);
}

3.2.2 Работа с базой данных LiteDB

Подключение к базе данных:

using (var db = new LiteDatabase("Filename=" + Utils.MyPath("RdpMon.db") + ";utc=true"))
{
    var addrTable = db.GetCollection<Addr>("Addr");
    var sessionTable = db.GetCollection<Session>("Session");
    var processTable = db.GetCollection<Process>("Process");
}

Операции CRUD:

// Create/Update
addrTable.Upsert(addr);

// Read
var addresses = addrTable.FindAll();
var specificAddr = addrTable.FindById(ip);

// Query with conditions
var attackingIPs = addrTable.Find(x => x.FailCount >= 10);

// Delete
addrTable.Delete(ip);

Индексирование для производительности:

addrTable.EnsureIndex(x => x.Last);  // Для сортировки по времени
addrTable.EnsureIndex(x => x.FailCount);  // Для фильтрации атак

3.2.3 Windows Service реализация

Service.cs структура:

partial class Service : ServiceBase
{
    Thread mainThread = null;
    bool Stopping = false;
    EventWaitHandle waitEvent;
    
    protected override void OnStart(string[] args)
    {
        mainThread = new Thread(MainThread);
        mainThread.Start();
    }
    
    protected override void OnStop()
    {
        Stopping = true;
        waitEvent.Set();
    }
    
    void MainThread()
    {
        var connectionsAggregator = new ConnectMon(true, true);
        while (!Stopping)
        {
            connectionsAggregator.Aggregate(DateTime.MinValue);
            waitEvent.WaitOne(3 * 60 * 1000); // 3 минуты в обычном режиме
        }
    }
}

3.2.4 WinForms интерфейс

MainForm.cs — основные принципы:

public partial class MainForm : Form
{
    private void RefreshLV(bool initialLoad)
    {
        if (initialLoad || tabs.SelectedIndex == 0)
            RefreshConnectionsLV(initialLoad);
        if (initialLoad || tabs.SelectedIndex == 1)
            RefreshSessionsLV(initialLoad, WTS.ListSessions());
    }
    
    private void OnLvColumnClick(object sender, ColumnClickEventArgs e)
    {
        // Реализация сортировки столбцов
        var sorter = connectsSorter;
        if (e.Column == sorter.SortColumn)
            sorter.Order = (sorter.Order == SortOrder.Ascending) ? 
                SortOrder.Descending : SortOrder.Ascending;
    }
}

3.3 Новые модули — детальная реализация

3.3.1 Модуль блокировки IP (IPBlocker)

Назначение: Автоматическая и ручная блокировка IP-адресов через Windows Firewall API.

Основные функции:

  • Создание правил блокировки для TCP/UDP порта 3389
  • Управление существующими правилами
  • Интеграция с чёрным списком
  • Логирование операций блокировки

3.3.2 Модуль AbuseIPDB (AbuseReporter)

Назначение: Интеграция с внешним сервисом AbuseIPDB для проверки репутации IP и отправки сообщений о злоумышленниках.

API эндпоинты:[^5][^7]

  • /api/v2/check — проверка репутации IP
  • /api/v2/report — сообщение о вредоносной активности
  • /api/v2/blacklist — получение списка известных злоумышленников

Обработка ответов:

public class AbuseIPDBResponse
{
    public int abuseConfidencePercentage { get; set; }
    public string countryCode { get; set; }
    public bool isWhitelisted { get; set; }
    public DateTime lastReportedAt { get; set; }
}

3.3.3 Модуль управления списками (ListManager)

Назначение: Централизованное управление белыми и чёрными списками IP-адресов.

Функциональность:

  • CRUD операции со списками
  • Валидация IP-адресов и CIDR диапазонов
  • Импорт/экспорт списков
  • Синхронизация с внешними источниками угроз

3.3.4 Модуль настроек (SettingsManager)

Назначение: Управление конфигурацией системы с персистентным хранением настроек.

Настройки:

public class RdpMonSettings
{
    public int FailuresForBlocking { get; set; } = 10;
    public int FailuresForAbuseReport { get; set; } = 20;
    public string AbuseIPDBApiKey { get; set; }
    public int AbuseConfidenceThreshold { get; set; } = 75;
    public bool EnableAutoBlocking { get; set; } = true;
    public bool EnableAbuseIPDBCheck { get; set; } = false;
}

3.3.5 Модуль отчётов (ReportsManager)

Назначение: Генерация различных типов отчётов для анализа безопасности.

Типы отчётов:

  • Сводка атак за период
  • Топ атакующих IP-адресов
  • Временные паттерны активности
  • Географическое распределение (при наличии GeoIP данных)

3.3.6 Контекстное меню (ContextMenuManager)

Назначение: Предоставление быстрого доступа к основным действиям через контекстное меню.

Действия:

  • Блокировка IP
  • Копирование IP в буфер обмена
  • Отправка сообщения в AbuseIPDB
  • Добавление в белый/чёрный список

3.4 API и интерфейсы

3.4.1 Windows Event Log API

Чтение событий безопасности:

var query = "*[" +
    "(System/EventID=" + SuccessEvtId.ToString() + " or " + "System/EventID=" + FailureEvtId.ToString() + ")" +
    " and " +
    "System[TimeCreated[@SystemTime>'" + fromUtc.ToString("yyyy-MM-dd") + "T" + fromUtc.ToString("HH:mm:ss") + ".000000000Z" + "']]" +
    "]";

var eventsQuery = new EventLogQuery("Security", PathType.LogName, query);
var logReader = new EventLogReader(eventsQuery);

for (var evt = logReader.ReadEvent(); evt != null; evt = logReader.ReadEvent())
{
    ProcessSecurityEvent(evt);
}

События для мониторинга:

  • 4625 — Неудачная попытка входа
  • 4648 — Успешный вход (explicit credentials)
  • 21 — Terminal Services session logon (из TS-LocalSessionManager log)

3.4.2 Windows Terminal Services API

Получение информации о сессиях:

[DllImport("wtsapi32.dll")]
static extern int WTSEnumerateSessions(
    IntPtr pServer,
    int iReserved,
    int iVersion,
    ref IntPtr pSessionInfo,
    ref int iCount);

[DllImport("Wtsapi32.dll")]
public static extern bool WTSQuerySessionInformation(
    IntPtr pServer,
    Int32 iSessionID,
    WTS_INFO_CLASS oInfoClass,
    out IntPtr pBuffer,
    out uint iBytesReturned);

Типы информации WTS:

  • WTSUserName — имя пользователя сессии
  • WTSSessionId — идентификатор сессии
  • WTSConnectState — состояние подключения
  • WTSClientAddress — IP-адрес клиента
  • WTSLogonTime — время входа в сессию

3.4.3 Windows Firewall API

Управление правилами фаервола:

Type typeFWPolicy2 = Type.GetTypeFromCLSID(new Guid("{E2B3C97F-6AE1-41AC-817A-F6F92166D7DD}"));
Type typeFWRule = Type.GetTypeFromCLSID(new Guid("{2C5BC43E-3369-4C33-AB0C-BE9469677AF4}"));
INetFwPolicy2 fwPolicy2 = (INetFwPolicy2)Activator.CreateInstance(typeFWPolicy2);

// Создание правила блокировки
var newRule = (INetFwRule)Activator.CreateInstance(typeFWRule);
newRule.Name = "RdpMon.BlackList TCP";
newRule.Protocol = (int)NET_FW_IP_PROTOCOL_.NET_FW_IP_PROTOCOL_TCP;
newRule.LocalPorts = "3389";
newRule.Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN;
newRule.Action = NET_FW_ACTION_.NET_FW_ACTION_BLOCK;
newRule.RemoteAddresses = ipAddress + "/255.255.255.255";
fwPolicy2.Rules.Add(newRule);

3.4.4 AbuseIPDB REST API

HTTP клиент для AbuseIPDB:

public async Task<AbuseIPDBCheckResponse> CheckIPAsync(string ipAddress)
{
    using (var client = new HttpClient())
    {
        client.DefaultRequestHeaders.Add("Key", apiKey);
        client.DefaultRequestHeaders.Add("Accept", "application/json");
        
        var response = await client.GetAsync($"https://api.abuseipdb.com/api/v2/check?ipAddress={ipAddress}&maxAgeInDays=90");
        var content = await response.Content.ReadAsStringAsync();
        return JsonConvert.DeserializeObject<AbuseIPDBCheckResponse>(content);
    }
}

public async Task<bool> ReportIPAsync(string ipAddress, string comment, int[] categories)
{
    using (var client = new HttpClient())
    {
        var formData = new List<KeyValuePair<string, string>>
        {
            new KeyValuePair<string, string>("ip", ipAddress),
            new KeyValuePair<string, string>("categories", string.Join(",", categories)),
            new KeyValuePair<string, string>("comment", comment)
        };
        
        var response = await client.PostAsync("https://api.abuseipdb.com/api/v2/report", 
            new FormUrlEncodedContent(formData));
        return response.IsSuccessStatusCode;
    }
}

3.5 База данных и модели данных

3.5.1 Схема базы данных

Коллекции LiteDB:

  1. Addr — агрегированная информация по IP-адресам
  2. Session — детали пользовательских сессий
  3. Process — процессы, запущенные в сессиях
  4. Prop — системные свойства и настройки
  5. WhiteList — белый список IP (новая)
  6. BlackList — чёрный список IP (новая)
  7. Settings — настройки приложения (новая)

3.5.2 Модели данных

Addr — агрегация по IP-адресам:

public class Addr
{
    [BsonId]
    public string AddrId { get; set; }  // IP-адрес
    public long Type { get; set; }
    public long Flags { get; set; }
    public long Prot { get; set; }      // Protection flags
    public int FailCount { get; set; }  // Неудачные попытки
    public int SuccessCount { get; set; } // Успешные подключения
    public DateTime First { get; set; } // Первое обнаружение
    public DateTime Last { get; set; }  // Последняя активность
    public HashSet<string> UserNames { get; set; } // Список пользователей
    
    [Flags] 
    public enum ProtFlag { None = 0, Blocked = 1 };
    
    public bool IsOngoing() => (DateTime.UtcNow.Subtract(this.Last).TotalMinutes < 2);
    public bool IsAttack(int offset = 0) => (FailCount >= 10 + offset);
    public bool IsLegit() => (SuccessCount > 0 && FailCount < 100);
}

Session — пользовательские сессии:

public class Session
{
    [BsonId]
    public long SessionUid { get; set; }  // Уникальный ID
    public long WtsSessionId { get; set; } // WTS Session ID
    public DateTime Start { get; set; }    // Начало сессии
    public DateTime? End { get; set; }     // Конец сессии (null для активных)
    public string User { get; set; }       // Имя пользователя
    public long Flags { get; set; }
    public string Addr { get; set; }       // IP-адрес клиента
    
    public static long GetSessionUid(long wtsSessionId, DateTime start)
    {
        long ret = start.Ticks;
        ret &= ~(0x7ff0000000000000);
        long hiBit = wtsSessionId;
        hiBit <<= 48;
        ret |= hiBit;
        return ret;
    }
}

3.5.3 Новые модели данных

Settings — настройки приложения:

public class Settings
{
    [BsonId]
    public string SettingId { get; set; }
    public int FailuresForBlocking { get; set; } = 10;
    public int FailuresForAbuseReport { get; set; } = 20;
    public string AbuseIPDBApiKey { get; set; }
    public int AbuseConfidenceThreshold { get; set; } = 75;
    public bool EnableAutoBlocking { get; set; } = true;
    public bool EnableAbuseIPDBCheck { get; set; } = false;
    public int CheckFailuresThreshold { get; set; } = 5;
    public DateTime LastModified { get; set; }
}

WhiteList — белый список:

public class WhiteListEntry
{
    [BsonId]
    public string Id { get; set; }
    public string IPAddress { get; set; }
    public string Description { get; set; }
    public DateTime Added { get; set; }
    public string AddedBy { get; set; }
    public bool IsRange { get; set; }  // CIDR диапазон
    public string CIDR { get; set; }   // Для диапазонов
}

BlackList — чёрный список:

public class BlackListEntry
{
    [BsonId]
    public string Id { get; set; }
    public string IPAddress { get; set; }
    public DateTime BlockedAt { get; set; }
    public string Reason { get; set; }
    public bool AutoBlocked { get; set; }  // Автоматическая или ручная блокировка
    public int FailuresCount { get; set; }
    public bool FirewallRuleCreated { get; set; }
    public bool ReportedToAbuseIPDB { get; set; }
}

3.5.4 Индексы и оптимизация

Создание индексов для производительности:

// Основные индексы для быстрого поиска
addrTable.EnsureIndex(x => x.Last);        // Сортировка по времени
addrTable.EnsureIndex(x => x.FailCount);   // Фильтрация атак
addrTable.EnsureIndex(x => x.SuccessCount); // Легитимные подключения

sessionTable.EnsureIndex(x => x.Start);    // Сортировка сессий
sessionTable.EnsureIndex(x => x.User);     // Поиск по пользователю
sessionTable.EnsureIndex(x => x.Addr);     // Поиск по IP

blacklistTable.EnsureIndex(x => x.BlockedAt); // Сортировка блокировок
whitelistTable.EnsureIndex(x => x.Added);     // Сортировка исключений

Оптимизация запросов:

// Эффективные запросы с использованием индексов
var recentAttacks = addrTable.Find(Query.And(
    Query.GTE("Last", DateTime.UtcNow.AddHours(-24)),
    Query.GTE("FailCount", 10)
));

var activeSessions = sessionTable.Find(Query.EQ("End", null));

// Агрегация с группировкой
var topAttackers = addrTable
    .Find(Query.GTE("FailCount", 10))
    .OrderByDescending(x => x.FailCount)
    .Take(10)
    .ToList();

3.6 Полные коды новых модулей

3.6.1 IPBlocker.cs — Блокировка IP-адресов

using NetFwTypeLib;
using System;
using System.IO;
using LiteDB;

namespace Cameyo.RdpMon
{
    /// <summary>
    /// Модуль для автоматической и ручной блокировки IP-адресов через Windows Firewall
    /// </summary>
    public class IPBlocker
    {
        private const string RULE_NAME_TCP = "RdpMon.BlackList TCP";
        private const string RULE_NAME_UDP = "RdpMon.BlackList UDP";
        private const string RULE_DESCRIPTION = "RDP brute-force addresses blocked by RdpMon";
        
        private readonly WinFirewall firewall;
        private readonly ListManager listManager;
        
        public IPBlocker()
        {
            firewall = new WinFirewall();
            listManager = new ListManager();
        }
        
        /// <summary>
        /// Блокирует IP-адрес через Windows Firewall и добавляет в чёрный список
        /// </summary>
        /// <param name="ipAddress">IP-адрес для блокировки</param>
        /// <param name="reason">Причина блокировки</param>
        /// <param name="autoBlocked">Была ли блокировка автоматической</param>
        /// <param name="failuresCount">Количество неудачных попыток</param>
        /// <returns>True если блокировка успешна</returns>
        public bool BlockIP(string ipAddress, string reason, bool autoBlocked = false, int failuresCount = 0)
        {
            var logPrefix = $"BlockIP({ipAddress}): ";
            
            try
            {
                // Проверяем, не находится ли IP в белом списке
                if (listManager.IsInWhiteList(ipAddress))
                {
                    Log(logPrefix + "IP находится в белом списке, блокировка отменена");
                    return false;
                }
                
                // Проверяем, не заблокирован ли уже
                if (listManager.IsInBlackList(ipAddress))
                {
                    Log(logPrefix + "IP уже заблокирован");
                    return true;
                }
                
                // Создаём правила фаервола для TCP и UDP
                bool tcpSuccess = firewall.AddIp(false, ipAddress, 3389, false, RULE_NAME_TCP, RULE_DESCRIPTION);
                bool udpSuccess = firewall.AddIp(false, ipAddress, 3389, false, RULE_NAME_UDP, RULE_DESCRIPTION);
                
                if (tcpSuccess && udpSuccess)
                {
                    // Добавляем в чёрный список
                    var blacklistEntry = new BlackListEntry
                    {
                        Id = Guid.NewGuid().ToString(),
                        IPAddress = ipAddress,
                        BlockedAt = DateTime.UtcNow,
                        Reason = reason,
                        AutoBlocked = autoBlocked,
                        FailuresCount = failuresCount,
                        FirewallRuleCreated = true,
                        ReportedToAbuseIPDB = false
                    };
                    
                    listManager.AddToBlackList(blacklistEntry);
                    
                    Log(logPrefix + $"успешно заблокирован. Причина: {reason}");
                    return true;
                }
                else
                {
                    Log(logPrefix + "ошибка создания правил фаервола");
                    return false;
                }
            }
            catch (Exception ex)
            {
                Log(logPrefix + "исключение: " + ex.ToString());
                return false;
            }
        }
        
        /// <summary>
        /// Разблокирует IP-адрес
        /// </summary>
        /// <param name="ipAddress">IP-адрес для разблокировки</param>
        /// <returns>True если разблокировка успешна</returns>
        public bool UnblockIP(string ipAddress)
        {
            var logPrefix = $"UnblockIP({ipAddress}): ";
            
            try
            {
                // Удаляем правила фаервола
                bool tcpRemoved = RemoveFirewallRule(RULE_NAME_TCP, ipAddress);
                bool udpRemoved = RemoveFirewallRule(RULE_NAME_UDP, ipAddress);
                
                // Удаляем из чёрного списка
                listManager.RemoveFromBlackList(ipAddress);
                
                Log(logPrefix + "разблокирован");
                return tcpRemoved && udpRemoved;
            }
            catch (Exception ex)
            {
                Log(logPrefix + "исключение: " + ex.ToString());
                return false;
            }
        }
        
        /// <summary>
        /// Автоматическая блокировка при превышении лимита попыток
        /// </summary>
        /// <param name="addr">Информация об IP-адресе</param>
        /// <param name="threshold">Пороговое значение неудачных попыток</param>
        /// <returns>True если IP был заблокирован</returns>
        public bool AutoBlockIfNeeded(Addr addr, int threshold)
        {
            if (addr.FailCount >= threshold && !addr.IsLegit())
            {
                string reason = $"Автоматическая блокировка: {addr.FailCount} неудачных попыток входа";
                return BlockIP(addr.AddrId, reason, true, addr.FailCount);
            }
            
            return false;
        }
        
        /// <summary>
        /// Удаляет конкретный IP из правила фаервола
        /// </summary>
        private bool RemoveFirewallRule(string ruleName, string ipAddress)
        {
            try
            {
                var policy = (INetFwPolicy2)Activator.CreateInstance(
                    Type.GetTypeFromCLSID(new Guid("{E2B3C97F-6AE1-41AC-817A-F6F92166D7DD}")));
                
                var rule = policy.Rules.Item(ruleName);
                if (rule != null)
                {
                    var addresses = rule.RemoteAddresses;
                    var ipWithMask = ipAddress + "/255.255.255.255";
                    
                    if (addresses.Contains(ipWithMask))
                    {
                        // Удаляем IP из списка адресов правила
                        var newAddresses = addresses.Replace(ipWithMask + ",", "").Replace("," + ipWithMask, "").Replace(ipWithMask, "");
                        
                        if (string.IsNullOrEmpty(newAddresses) || newAddresses == ",")
                        {
                            // Если это был единственный IP, удаляем правило полностью
                            policy.Rules.Remove(ruleName);
                        }
                        else
                        {
                            // Обновляем правило с новым списком адресов
                            rule.RemoteAddresses = newAddresses;
                        }
                    }
                }
                
                return true;
            }
            catch (Exception ex)
            {
                Log($"RemoveFirewallRule({ruleName}, {ipAddress}): исключение: " + ex.ToString());
                return false;
            }
        }
        
        /// <summary>
        /// Получает список всех заблокированных IP-адресов
        /// </summary>
        /// <returns>Список заблокированных IP</returns>
        public System.Collections.Generic.List<BlackListEntry> GetBlockedIPs()
        {
            return listManager.GetBlackList();
        }
        
        /// <summary>
        /// Проверяет, заблокирован ли IP-адрес
        /// </summary>
        /// <param name="ipAddress">IP-адрес для проверки</param>
        /// <returns>True если IP заблокирован</returns>
        public bool IsBlocked(string ipAddress)
        {
            return listManager.IsInBlackList(ipAddress);
        }
        
        private static void Log(string msg)
        {
            Utils.Log("IPBlocker: " + msg);
        }
    }
}

3.6.2 AbuseReporter.cs — AbuseIPDB интеграция

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using System.Text;
using Newtonsoft.Json;

namespace Cameyo.RdpMon
{
    /// <summary>
    /// Модуль для интеграции с AbuseIPDB API
    /// </summary>
    public class AbuseReporter
    {
        private readonly string apiKey;
        private readonly HttpClient httpClient;
        private const string BASE_URL = "https://api.abuseipdb.com/api/v2";
        
        // Категории AbuseIPDB
        public static class Categories
        {
            public const int SSH_BRUTEFORCE = 18;
            public const int BRUTE_FORCE = 18;
            public const int BAD_WEB_BOT = 19;
            public const int EXPLOITED_HOST = 20;
            public const int WEB_APP_ATTACK = 21;
            public const int SSH = 22;
        }
        
        public AbuseReporter(string abuseIPDBApiKey)
        {
            apiKey = abuseIPDBApiKey;
            httpClient = new HttpClient();
            httpClient.DefaultRequestHeaders.Add("Key", apiKey);
            httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
        }
        
        /// <summary>
        /// Проверяет репутацию IP-адреса в AbuseIPDB
        /// </summary>
        /// <param name="ipAddress">IP-адрес для проверки</param>
        /// <param name="maxAgeInDays">Максимальный возраст отчётов (по умолчанию 90 дней)</param>
        /// <returns>Информация о репутации IP</returns>
        public async Task<AbuseIPDBCheckResponse> CheckIPAsync(string ipAddress, int maxAgeInDays = 90)
        {
            var logPrefix = $"CheckIP({ipAddress}): ";
            
            try
            {
                var url = $"{BASE_URL}/check?ipAddress={ipAddress}&maxAgeInDays={maxAgeInDays}&verbose";
                Log(logPrefix + "отправка запроса к AbuseIPDB");
                
                var response = await httpClient.GetAsync(url);
                var content = await response.Content.ReadAsStringAsync();
                
                if (response.IsSuccessStatusCode)
                {
                    var result = JsonConvert.DeserializeObject<AbuseIPDBCheckResponse>(content);
                    Log(logPrefix + $"получен ответ, confidence: {result.Data.AbuseConfidencePercentage}%");
                    return result;
                }
                else
                {
                    Log(logPrefix + $"ошибка HTTP {response.StatusCode}: {content}");
                    return null;
                }
            }
            catch (Exception ex)
            {
                Log(logPrefix + "исключение: " + ex.ToString());
                return null;
            }
        }
        
        /// <summary>
        /// Отправляет сообщение о вредоносной активности IP-адреса
        /// </summary>
        /// <param name="ipAddress">IP-адрес злоумышленника</param>
        /// <param name="categories">Категории атаки</param>
        /// <param name="comment">Комментарий с деталями атаки</param>
        /// <returns>True если сообщение успешно отправлено</returns>
        public async Task<bool> ReportIPAsync(string ipAddress, int[] categories, string comment)
        {
            var logPrefix = $"ReportIP({ipAddress}): ";
            
            try
            {
                var formData = new List<KeyValuePair<string, string>>
                {
                    new KeyValuePair<string, string>("ip", ipAddress),
                    new KeyValuePair<string, string>("categories", string.Join(",", categories)),
                    new KeyValuePair<string, string>("comment", comment)
                };
                
                Log(logPrefix + "отправка сообщения в AbuseIPDB");
                
                var response = await httpClient.PostAsync($"{BASE_URL}/report", 
                    new FormUrlEncodedContent(formData));
                
                var content = await response.Content.ReadAsStringAsync();
                
                if (response.IsSuccessStatusCode)
                {
                    Log(logPrefix + "сообщение успешно отправлено");
                    return true;
                }
                else
                {
                    Log(logPrefix + $"ошибка отправки HTTP {response.StatusCode}: {content}");
                    return false;
                }
            }
            catch (Exception ex)
            {
                Log(logPrefix + "исключение: " + ex.ToString());
                return false;
            }
        }
        
        /// <summary>
        /// Автоматическая отправка сообщения о brute-force атаке
        /// </summary>
        /// <param name="addr">Информация об атакующем IP</param>
        /// <returns>True если сообщение отправлено</returns>
        public async Task<bool> ReportBruteForceAsync(Addr addr)
        {
            var comment = $"RDP brute-force attack detected by RdpMon. " +
                         $"Failed attempts: {addr.FailCount}, " +
                         $"Period: {addr.First:yyyy-MM-dd HH:mm} - {addr.Last:yyyy-MM-dd HH:mm} UTC, " +
                         $"Targeted users: {string.Join(", ", addr.UserNames)}";
            
            var categories = new[] { Categories.BRUTE_FORCE, Categories.SSH };
            
            return await ReportIPAsync(addr.AddrId, categories, comment);
        }
        
        /// <summary>
        /// Получает чёрный список наиболее опасных IP от AbuseIPDB
        /// </summary>
        /// <param name="confidenceMinimum">Минимальный процент confidence (по умолчанию 90)</param>
        /// <param name="limit">Количество записей (максимум 10000)</param>
        /// <returns>Список IP-адресов</returns>
        public async Task<AbuseIPDBBlacklistResponse> GetBlacklistAsync(int confidenceMinimum = 90, int limit = 1000)
        {
            var logPrefix = "GetBlacklist: ";
            
            try
            {
                var url = $"{BASE_URL}/blacklist?confidenceMinimum={confidenceMinimum}&limit={limit}";
                Log(logPrefix + $"запрос чёрного списка (confidence >= {confidenceMinimum}%)");
                
                var response = await httpClient.GetAsync(url);
                var content = await response.Content.ReadAsStringAsync();
                
                if (response.IsSuccessStatusCode)
                {
                    var result = JsonConvert.DeserializeObject<AbuseIPDBBlacklistResponse>(content);
                    Log(logPrefix + $"получено {result.Data.Count} записей");
                    return result;
                }
                else
                {
                    Log(logPrefix + $"ошибка HTTP {response.StatusCode}: {content}");
                    return null;
                }
            }
            catch (Exception ex)
            {
                Log(logPrefix + "исключение: " + ex.ToString());
                return null;
            }
        }
        
        /// <summary>
        /// Проверяет корректность API ключа
        /// </summary>
        /// <returns>True если ключ валидный</returns>
        public async Task<bool> TestConnectionAsync()
        {
            try
            {
                // Делаем тестовый запрос на проверку localhost
                var result = await CheckIPAsync("127.0.0.1", 1);
                return result != null;
            }
            catch
            {
                return false;
            }
        }
        
        public void Dispose()
        {
            httpClient?.Dispose();
        }
        
        private static void Log(string msg)
        {
            Utils.Log("AbuseReporter: " + msg);
        }
    }
    
    // Модели ответов AbuseIPDB
    public class AbuseIPDBCheckResponse
    {
        public AbuseIPDBCheckData Data { get; set; }
    }
    
    public class AbuseIPDBCheckData
    {
        public string IpAddress { get; set; }
        public bool IsPublic { get; set; }
        public int IpVersion { get; set; }
        public bool IsWhitelisted { get; set; }
        public int AbuseConfidencePercentage { get; set; }
        public string CountryCode { get; set; }
        public string CountryName { get; set; }
        public string UsageType { get; set; }
        public string Isp { get; set; }
        public string Domain { get; set; }
        public int TotalReports { get; set; }
        public int NumDistinctUsers { get; set; }
        public DateTime? LastReportedAt { get; set; }
    }
    
    public class AbuseIPDBBlacklistResponse
    {
        public List<AbuseIPDBBlacklistItem> Data { get; set; }
    }
    
    public class AbuseIPDBBlacklistItem
    {
        public string IpAddress { get; set; }
        public int AbuseConfidencePercentage { get; set; }
        public string CountryCode { get; set; }
        public DateTime LastReportedAt { get; set; }
    }
}

3.6.3 ListManager.cs — Управление списками

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.IO;
using LiteDB;

namespace Cameyo.RdpMon
{
    /// <summary>
    /// Модуль для управления белыми и чёрными списками IP-адресов
    /// </summary>
    public class ListManager
    {
        private readonly string dbPath;
        
        public ListManager()
        {
            dbPath = Utils.MyPath("RdpMon.db");
        }
        
        #region Белый список
        
        /// <summary>
        /// Добавляет IP-адрес в белый список
        /// </summary>
        /// <param name="ipAddress">IP-адрес или CIDR диапазон</param>
        /// <param name="description">Описание</param>
        /// <param name="addedBy">Кто добавил</param>
        /// <returns>True если успешно добавлено</returns>
        public bool AddToWhiteList(string ipAddress, string description = "", string addedBy = "System")
        {
            var logPrefix = $"AddToWhiteList({ipAddress}): ";
            
            try
            {
                if (!IsValidIPOrCIDR(ipAddress))
                {
                    Log(logPrefix + "некорректный IP-адрес или CIDR");
                    return false;
                }
                
                using (var db = new LiteDatabase($"Filename={dbPath};utc=true"))
                {
                    var collection = db.GetCollection<WhiteListEntry>("WhiteList");
                    
                    // Проверяем, не существует ли уже
                    if (collection.FindOne(x => x.IPAddress == ipAddress) != null)
                    {
                        Log(logPrefix + "IP уже в белом списке");
                        return false;
                    }
                    
                    var entry = new WhiteListEntry
                    {
                        Id = Guid.NewGuid().ToString(),
                        IPAddress = ipAddress,
                        Description = description,
                        Added = DateTime.UtcNow,
                        AddedBy = addedBy,
                        IsRange = ipAddress.Contains("/"),
                        CIDR = ipAddress.Contains("/") ? ipAddress : null
                    };
                    
                    collection.Insert(entry);
                    Log(logPrefix + "добавлено в белый список");
                    return true;
                }
            }
            catch (Exception ex)
            {
                Log(logPrefix + "исключение: " + ex.ToString());
                return false;
            }
        }
        
        /// <summary>
        /// Удаляет IP-адрес из белого списка
        /// </summary>
        /// <param name="ipAddress">IP-адрес для удаления</param>
        /// <returns>True если успешно удалено</returns>
        public bool RemoveFromWhiteList(string ipAddress)
        {
            try
            {
                using (var db = new LiteDatabase($"Filename={dbPath};utc=true"))
                {
                    var collection = db.GetCollection<WhiteListEntry>("WhiteList");
                    var deleted = collection.Delete(x => x.IPAddress == ipAddress);
                    
                    Log($"RemoveFromWhiteList({ipAddress}): удалено {deleted} записей");
                    return deleted > 0;
                }
            }
            catch (Exception ex)
            {
                Log($"RemoveFromWhiteList({ipAddress}): исключение: " + ex.ToString());
                return false;
            }
        }
        
        /// <summary>
        /// Проверяет, находится ли IP в белом списке
        /// </summary>
        /// <param name="ipAddress">IP-адрес для проверки</param>
        /// <returns>True если IP в белом списке</returns>
        public bool IsInWhiteList(string ipAddress)
        {
            try
            {
                using (var db = new LiteDatabase($"Filename={dbPath};utc=true"))
                {
                    var collection = db.GetCollection<WhiteListEntry>("WhiteList");
                    var entries = collection.FindAll().ToList();
                    
                    foreach (var entry in entries)
                    {
                        if (entry.IsRange)
                        {
                            // Проверка CIDR диапазона
                            if (IsIPInCIDR(ipAddress, entry.CIDR))
                                return true;
                        }
                        else
                        {
                            // Точное совпадение IP
                            if (entry.IPAddress == ipAddress)
                                return true;
                        }
                    }
                    
                    return false;
                }
            }
            catch (Exception ex)
            {
                Log($"IsInWhiteList({ipAddress}): исключение: " + ex.ToString());
                return false; // В случае ошибки считаем, что IP не в белом списке
            }
        }
        
        /// <summary>
        /// Получает все записи белого списка
        /// </summary>
        /// <returns>Список записей белого списка</returns>
        public List<WhiteListEntry> GetWhiteList()
        {
            try
            {
                using (var db = new LiteDatabase($"Filename={dbPath};utc=true"))
                {
                    var collection = db.GetCollection<WhiteListEntry>("WhiteList");
                    return collection.FindAll().OrderBy(x => x.Added).ToList();
                }
            }
            catch (Exception ex)
            {
                Log("GetWhiteList: исключение: " + ex.ToString());
                return new List<WhiteListEntry>();
            }
        }
        
        #endregion
        
        #region Чёрный список
        
        /// <summary>
        /// Добавляет IP-адрес в чёрный список
        /// </summary>
        /// <param name="entry">Запись чёрного списка</param>
        /// <returns>True если успешно добавлено</returns>
        public bool AddToBlackList(BlackListEntry entry)
        {
            var logPrefix = $"AddToBlackList({entry.IPAddress}): ";
            
            try
            {
                using (var db = new LiteDatabase($"Filename={dbPath};utc=true"))
                {
                    var collection = db.GetCollection<BlackListEntry>("BlackList");
                    
                    // Проверяем, не существует ли уже
                    var existing = collection.FindOne(x => x.IPAddress == entry.IPAddress);
                    if (existing != null)
                    {
                        // Обновляем существующую запись
                        existing.FailuresCount = entry.FailuresCount;
                        existing.Reason = entry.Reason;
                        existing.FirewallRuleCreated = entry.FirewallRuleCreated;
                        existing.ReportedToAbuseIPDB = entry.ReportedToAbuseIPDB;
                        
                        collection.Update(existing);
                        Log(logPrefix + "обновлена существующая запись");
                    }
                    else
                    {
                        collection.Insert(entry);
                        Log(logPrefix + "добавлено в чёрный список");
                    }
                    
                    return true;
                }
            }
            catch (Exception ex)
            {
                Log(logPrefix + "исключение: " + ex.ToString());
                return false;
            }
        }
        
        /// <summary>
        /// Удаляет IP-адрес из чёрного списка
        /// </summary>
        /// <param name="ipAddress">IP-адрес для удаления</param>
        /// <returns>True если успешно удалено</returns>
        public bool RemoveFromBlackList(string ipAddress)
        {
            try
            {
                using (var db = new LiteDatabase($"Filename={dbPath};utc=true"))
                {
                    var collection = db.GetCollection<BlackListEntry>("BlackList");
                    var deleted = collection.Delete(x => x.IPAddress == ipAddress);
                    
                    Log($"RemoveFromBlackList({ipAddress}): удалено {deleted} записей");
                    return deleted > 0;
                }
            }
            catch (Exception ex)
            {
                Log($"RemoveFromBlackList({ipAddress}): исключение: " + ex.ToString());
                return false;
            }
        }
        
        /// <summary>
        /// Проверяет, находится ли IP в чёрном списке
        /// </summary>
        /// <param name="ipAddress">IP-адрес для проверки</param>
        /// <returns>True если IP в чёрном списке</returns>
        public bool IsInBlackList(string ipAddress)
        {
            try
            {
                using (var db = new LiteDatabase($"Filename={dbPath};utc=true"))
                {
                    var collection = db.GetCollection<BlackListEntry>("BlackList");
                    return collection.FindOne(x => x.IPAddress == ipAddress) != null;
                }
            }
            catch (Exception ex)
            {
                Log($"IsInBlackList({ipAddress}): исключение: " + ex.ToString());
                return false;
            }
        }
        
        /// <summary>
        /// Получает все записи чёрного списка
        /// </summary>
        /// <returns>Список записей чёрного списка</returns>
        public List<BlackListEntry> GetBlackList()
        {
            try
            {
                using (var db = new LiteDatabase($"Filename={dbPath};utc=true"))
                {
                    var collection = db.GetCollection<BlackListEntry>("BlackList");
                    return collection.FindAll().OrderByDescending(x => x.BlockedAt).ToList();
                }
            }
            catch (Exception ex)
            {
                Log("GetBlackList: исключение: " + ex.ToString());
                return new List<BlackListEntry>();
            }
        }
        
        /// <summary>
        /// Отмечает IP как отправленный в AbuseIPDB
        /// </summary>
        /// <param name="ipAddress">IP-адрес</param>
        /// <returns>True если успешно обновлено</returns>
        public bool MarkAsReportedToAbuseIPDB(string ipAddress)
        {
            try
            {
                using (var db = new LiteDatabase($"Filename={dbPath};utc=true"))
                {
                    var collection = db.GetCollection<BlackListEntry>("BlackList");
                    var entry = collection.FindOne(x => x.IPAddress == ipAddress);
                    
                    if (entry != null)
                    {
                        entry.ReportedToAbuseIPDB = true;
                        collection.Update(entry);
                        return true;
                    }
                    
                    return false;
                }
            }
            catch (Exception ex)
            {
                Log($"MarkAsReportedToAbuseIPDB({ipAddress}): исключение: " + ex.ToString());
                return false;
            }
        }
        
        #endregion
        
        #region Импорт/Экспорт
        
        /// <summary>
        /// Импортирует белый список из файла
        /// </summary>
        /// <param name="filePath">Путь к файлу (один IP на строку)</param>
        /// <param name="description">Описание для всех импортируемых IP</param>
        /// <returns>Количество импортированных IP</returns>
        public int ImportWhiteListFromFile(string filePath, string description = "Импортировано из файла")
        {
            var imported = 0;
            
            try
            {
                var lines = File.ReadAllLines(filePath);
                
                foreach (var line in lines)
                {
                    var ip = line.Trim();
                    if (!string.IsNullOrEmpty(ip) && !ip.StartsWith("#") && IsValidIPOrCIDR(ip))
                    {
                        if (AddToWhiteList(ip, description, "Import"))
                            imported++;
                    }
                }
                
                Log($"ImportWhiteListFromFile: импортировано {imported} IP из {lines.Length} строк");
            }
            catch (Exception ex)
            {
                Log("ImportWhiteListFromFile: исключение: " + ex.ToString());
            }
            
            return imported;
        }
        
        /// <summary>
        /// Экспортирует белый список в файл
        /// </summary>
        /// <param name="filePath">Путь к файлу для экспорта</param>
        /// <returns>True если успешно экспортировано</returns>
        public bool ExportWhiteListToFile(string filePath)
        {
            try
            {
                var entries = GetWhiteList();
                var lines = new List<string>
                {
                    "# RdpMon White List Export",
                    "# Generated: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                    "# Format: IP_ADDRESS [CIDR] # DESCRIPTION",
                    ""
                };
                
                foreach (var entry in entries)
                {
                    var line = entry.IPAddress;
                    if (!string.IsNullOrEmpty(entry.Description))
                        line += " # " + entry.Description;
                    
                    lines.Add(line);
                }
                
                File.WriteAllLines(filePath, lines);
                Log($"ExportWhiteListToFile: экспортировано {entries.Count} записей в {filePath}");
                return true;
            }
            catch (Exception ex)
            {
                Log("ExportWhiteListToFile: исключение: " + ex.ToString());
                return false;
            }
        }
        
        #endregion
        
        #region Утилиты
        
        /// <summary>
        /// Проверяет, является ли строка корректным IP-адресом или CIDR диапазоном
        /// </summary>
        /// <param name="ipOrCidr">IP-адрес или CIDR</param>
        /// <returns>True если корректный</returns>
        private bool IsValidIPOrCIDR(string ipOrCidr)
        {
            if (string.IsNullOrEmpty(ipOrCidr))
                return false;
            
            // Проверка CIDR
            if (ipOrCidr.Contains("/"))
            {
                var parts = ipOrCidr.Split('/');
                if (parts.Length != 2)
                    return false;
                
                if (!IPAddress.TryParse(parts[^0], out _))
                    return false;
                
                if (!int.TryParse(parts[^1], out int prefix) || prefix < 0 || prefix > 32)
                    return false;
                
                return true;
            }
            
            // Проверка IP
            return IPAddress.TryParse(ipOrCidr, out _);
        }
        
        /// <summary>
        /// Проверяет, входит ли IP-адрес в CIDR диапазон
        /// </summary>
        /// <param name="ipAddress">IP-адрес</param>
        /// <param name="cidr">CIDR диапазон (например, 192.168.1.0/24)</param>
        /// <returns>True если IP входит в диапазон</returns>
        private bool IsIPInCIDR(string ipAddress, string cidr)
        {
            try
            {
                var parts = cidr.Split('/');
                var networkIP = IPAddress.Parse(parts[^0]);
                var prefixLength = int.Parse(parts[^1]);
                var checkIP = IPAddress.Parse(ipAddress);
                
                var networkBytes = networkIP.GetAddressBytes();
                var checkBytes = checkIP.GetAddressBytes();
                
                if (networkBytes.Length != checkBytes.Length)
                    return false;
                
                var bytesToCheck = prefixLength / 8;
                var bitsToCheck = prefixLength % 8;
                
                // Проверяем полные байты
                for (int i = 0; i < bytesToCheck; i++)
                {
                    if (networkBytes[i] != checkBytes[i])
                        return false;
                }
                
                // Проверяем оставшиеся биты
                if (bitsToCheck > 0)
                {
                    var mask = (byte)(0xFF << (8 - bitsToCheck));
                    if ((networkBytes[bytesToCheck] & mask) != (checkBytes[bytesToCheck] & mask))
                        return false;
                }
                
                return true;
            }
            catch
            {
                return false;
            }
        }
        
        private static void Log(string msg)
        {
            Utils.Log("ListManager: " + msg);
        }
        
        #endregion
    }
}

3.6.4 SettingsManager.cs — Настройки

using System;
using LiteDB;

namespace Cameyo.RdpMon
{
    /// <summary>
    /// Модуль для управления настройками приложения
    /// </summary>
    public class SettingsManager
    {
        private readonly string dbPath;
        private const string SETTINGS_ID = "MainSettings";
        
        public SettingsManager()
        {
            dbPath = Utils.MyPath("RdpMon.db");
            InitializeDefaultSettings();
        }
        
        /// <summary>
        /// Получает текущие настройки
        /// </summary>
        /// <returns>Объект настроек</returns>
        public RdpMonSettings GetSettings()
        {
            try
            {
                using (var db = new LiteDatabase($"Filename={dbPath};utc=true"))
                {
                    var collection = db.GetCollection<RdpMonSettings>("Settings");
                    var settings = collection.FindById(SETTINGS_ID);
                    
                    if (settings == null)
                    {
                        // Создаём настройки по умолчанию
                        settings = CreateDefaultSettings();
                        collection.Insert(settings);
                    }
                    
                    return settings;
                }
            }
            catch (Exception ex)
            {
                Log("GetSettings: исключение: " + ex.ToString());
                return CreateDefaultSettings();
            }
        }
        
        /// <summary>
        /// Сохраняет настройки
        /// </summary>
        /// <param name="settings">Настройки для сохранения</param>
        /// <returns>True если успешно сохранено</returns>
        public bool SaveSettings(RdpMonSettings settings)
        {
            try
            {
                settings.SettingId = SETTINGS_ID;
                settings.LastModified = DateTime.UtcNow;
                
                using (var db = new LiteDatabase($"Filename={dbPath};utc=true"))
                {
                    var collection = db.GetCollection<RdpMonSettings>("Settings");
                    collection.Upsert(settings);
                    
                    Log("SaveSettings: настройки успешно сохранены");
                    return true;
                }
            }
            catch (Exception ex)
            {
                Log("SaveSettings: исключение: " + ex.ToString());
                return false;
            }
        }
        
        /// <summary>
        /// Получает конкретный параметр настроек
        /// </summary>
        /// <typeparam name="T">Тип параметра</typeparam>
        /// <param name="parameterName">Имя параметра</param>
        /// <param name="defaultValue">Значение по умолчанию</param>
        /// <returns>Значение параметра</returns>
        public T GetSetting<T>(string parameterName, T defaultValue)
        {
            try
            {
                var settings = GetSettings();
                var property = typeof(RdpMonSettings).GetProperty(parameterName);
                
                if (property != null)
                {
                    var value = property.GetValue(settings);
                    if (value is T)
                        return (T)value;
                }
                
                return defaultValue;
            }
            catch (Exception ex)
            {
                Log($"GetSetting({parameterName}): исключение: " + ex.ToString());
                return defaultValue;
            }
        }
        
        /// <summary>
        /// Устанавливает конкретный параметр настроек
        /// </summary>
        /// <typeparam name="T">Тип параметра</typeparam>
        /// <param name="parameterName">Имя параметра</param>
        /// <param name="value">Новое значение</param>
        /// <returns>True если успешно установлено</returns>
        public bool SetSetting<T>(string parameterName, T value)
        {
            try
            {
                var settings = GetSettings();
                var property = typeof(RdpMonSettings).GetProperty(parameterName);
                
                if (property != null && property.CanWrite)
                {
                    property.SetValue(settings, value);
                    return SaveSettings(settings);
                }
                
                return false;
            }
            catch (Exception ex)
            {
                Log($"SetSetting({parameterName}): исключение: " + ex.ToString());
                return false;
            }
        }
        
        /// <summary>
        /// Сбрасывает настройки к значениям по умолчанию
        /// </summary>
        /// <returns>True если успешно сброшено</returns>
        public bool ResetToDefaults()
        {
            try
            {
                var defaultSettings = CreateDefaultSettings();
                return SaveSettings(defaultSettings);
            }
            catch (Exception ex)
            {
                Log("ResetToDefaults: исключение: " + ex.ToString());
                return false;
            }
        }
        
        /// <summary>
        /// Проверяет валидность настроек
        /// </summary>
        /// <param name="settings">Настройки для проверки</param>
        /// <returns>Результат валидации</returns>
        public SettingsValidationResult ValidateSettings(RdpMonSettings settings)
        {
            var result = new SettingsValidationResult { IsValid = true };
            
            // Проверка количества попыток для блокировки
            if (settings.FailuresForBlocking < 1 || settings.FailuresForBlocking > 1000)
            {
                result.IsValid = false;
                result.Errors.Add("Количество попыток для блокировки должно быть от 1 до 1000");
            }
            
            // Проверка количества попыток для AbuseIPDB
            if (settings.FailuresForAbuseReport < settings.FailuresForBlocking)
            {
                result.IsValid = false;
                result.Errors.Add("Количество попыток для отправки в AbuseIPDB должно быть не меньше количества для блокировки");
            }
            
            // Проверка порога confidence
            if (settings.AbuseConfidenceThreshold < 1 || settings.AbuseConfidenceThreshold > 100)
            {
                result.IsValid = false;
                result.Errors.Add("Порог Confidence of Abuse должен быть от 1% до 100%");
            }
            
            // Проверка API ключа AbuseIPDB (если включена интеграция)
            if (settings.EnableAbuseIPDBCheck && string.IsNullOrWhiteSpace(settings.AbuseIPDBApiKey))
            {
                result.IsValid = false;
                result.Errors.Add("API ключ AbuseIPDB обязателен при включённой интеграции");
            }
            
            // Проверка порога для автоматической проверки
            if (settings.CheckFailuresThreshold < 1 || settings.CheckFailuresThreshold > settings.FailuresForBlocking)
            {
                result.IsValid = false;
                result.Errors.Add("Порог проверки в AbuseIPDB должен быть от 1 до значения блокировки");
            }
            
            return result;
        }
        
        /// <summary>
        /// Экспортирует настройки в JSON файл
        /// </summary>
        /// <param name="filePath">Путь к файлу</param>
        /// <returns>True если успешно экспортировано</returns>
        public bool ExportSettings(string filePath)
        {
            try
            {
                var settings = GetSettings();
                var json = Newtonsoft.Json.JsonConvert.SerializeObject(settings, Newtonsoft.Json.Formatting.Indented);
                System.IO.File.WriteAllText(filePath, json);
                
                Log($"ExportSettings: настройки экспортированы в {filePath}");
                return true;
            }
            catch (Exception ex)
            {
                Log("ExportSettings: исключение: " + ex.ToString());
                return false;
            }
        }
        
        /// <summary>
        /// Импортирует настройки из JSON файла
        /// </summary>
        /// <param name="filePath">Путь к файлу</param>
        /// <returns>True если успешно импортировано</returns>
        public bool ImportSettings(string filePath)
        {
            try
            {
                var json = System.IO.File.ReadAllText(filePath);
                var settings = Newtonsoft.Json.JsonConvert.DeserializeObject<RdpMonSettings>(json);
                
                var validation = ValidateSettings(settings);
                if (!validation.IsValid)
                {
                    Log("ImportSettings: импортированные настройки не прошли валидацию: " + 
                        string.Join(", ", validation.Errors));
                    return false;
                }
                
                var saved = SaveSettings(settings);
                if (saved)
                    Log($"ImportSettings: настройки импортированы из {filePath}");
                
                return saved;
            }
            catch (Exception ex)
            {
                Log("ImportSettings: исключение: " + ex.ToString());
                return false;
            }
        }
        
        /// <summary>
        /// Инициализирует настройки по умолчанию при первом запуске
        /// </summary>
        private void InitializeDefaultSettings()
        {
            try
            {
                using (var db = new LiteDatabase($"Filename={dbPath};utc=true"))
                {
                    var collection = db.GetCollection<RdpMonSettings>("Settings");
                    var existing = collection.FindById(SETTINGS_ID);
                    
                    if (existing == null)
                    {
                        var defaultSettings = CreateDefaultSettings();
                        collection.Insert(defaultSettings);
                        Log("InitializeDefaultSettings: созданы настройки по умолчанию");
                    }
                }
            }
            catch (Exception ex)
            {
                Log("InitializeDefaultSettings: исключение: " + ex.ToString());
            }
        }
        
        /// <summary>
        /// Создаёт объект настроек с значениями по умолчанию
        /// </summary>
        /// <returns>Настройки по умолчанию</returns>
        private RdpMonSettings CreateDefaultSettings()
        {
            return new RdpMonSettings
            {
                SettingId = SETTINGS_ID,
                FailuresForBlocking = 10,
                FailuresForAbuseReport = 20,
                AbuseIPDBApiKey = "",
                AbuseConfidenceThreshold = 75,
                EnableAutoBlocking = true,
                EnableAbuseIPDBCheck = false,
                CheckFailuresThreshold = 5,
                EnableAutoReport = false,
                LastModified = DateTime.UtcNow
            };
        }
        
        private static void Log(string msg)
        {
            Utils.Log("SettingsManager: " + msg);
        }
    }
    
    /// <summary>
    /// Класс настроек RdpMon
    /// </summary>
    public class RdpMonSettings
    {
        [BsonId]
        public string SettingId { get; set; }
        
        /// <summary>
        /// Количество неудачных попыток для автоматической блокировки IP
        /// </summary>
        public int FailuresForBlocking { get; set; } = 10;
        
        /// <summary>
        /// Количество неудачных попыток для отправки сообщения в AbuseIPDB
        /// </summary>
        public int FailuresForAbuseReport { get; set; } = 20;
        
        /// <summary>
        /// API ключ для AbuseIPDB
        /// </summary>
        public string AbuseIPDBApiKey { get; set; } = "";
        
        /// <summary>
        /// Порог Confidence of Abuse % для классификации IP как вредоносный
        /// </summary>
        public int AbuseConfidenceThreshold { get; set; } = 75;
        
        /// <summary>
        /// Включить автоматическую блокировку IP
        /// </summary>
        public bool EnableAutoBlocking { get; set; } = true;
        
        /// <summary>
        /// Включить проверку IP в AbuseIPDB
        /// </summary>
        public bool EnableAbuseIPDBCheck { get; set; } = false;
        
        /// <summary>
        /// Количество неудачных попыток для автоматической проверки в AbuseIPDB
        /// </summary>
        public int CheckFailuresThreshold { get; set; } = 5;
        
        /// <summary>
        /// Включить автоматическую отправку сообщений в AbuseIPDB
        /// </summary>
        public bool EnableAutoReport { get; set; } = false;
        
        /// <summary>
        /// Время последнего изменения настроек
        /// </summary>
        public DateTime LastModified { get; set; }
    }
    
    /// <summary>
    /// Результат валидации настроек
    /// </summary>
    public class SettingsValidationResult
    {
        public bool IsValid { get; set; }
        public System.Collections.Generic.List<string> Errors { get; set; } = 
            new System.Collections.Generic.List<string>();
    }
}

3.6.5 ReportsManager.cs — Отчёты

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using LiteDB;

namespace Cameyo.RdpMon
{
    /// <summary>
    /// Модуль для генерации отчётов по безопасности RDP
    /// </summary>
    public class ReportsManager
    {
        private readonly string dbPath;
        
        public ReportsManager()
        {
            dbPath = Utils.MyPath("RdpMon.db");
        }
        
        /// <summary>
        /// Генерирует сводный отчёт по атакам за указанный период
        /// </summary>
        /// <param name="fromDate">Начальная дата</param>
        /// <param name="toDate">Конечная дата</param>
        /// <returns>Сводный отчёт</returns>
        public AttackSummaryReport GenerateAttackSummary(DateTime fromDate, DateTime toDate)
        {
            var report = new AttackSummaryReport
            {
                PeriodStart = fromDate,
                PeriodEnd = toDate,
                GeneratedAt = DateTime.UtcNow
            };
            
            try
            {
                using (var db = new LiteDatabase($"Filename={dbPath};utc=true"))
                {
                    var addrCollection = db.GetCollection<Addr>("Addr");
                    
                    // Получаем все адреса с активностью в указанном периоде
                    var addresses = addrCollection.Find(x => 
                        x.Last >= fromDate && x.First <= toDate).ToList();
                    
                    // Анализ по типам
                    var attacks = addresses.Where(x => x.IsAttack()).ToList();
                    var legitConnections = addresses.Where(x => x.IsLegit()).ToList();
                    var suspiciousConnections = addresses.Where(x => !x.IsAttack() && !x.IsLegit()).ToList();
                    
                    // Заполняем основные метрики
                    report.TotalUniqueIPs = addresses.Count;
                    report.AttackingIPs = attacks.Count;
                    report.LegitimateIPs = legitConnections.Count;
                    report.SuspiciousIPs = suspiciousConnections.Count;
                    
                    report.TotalFailedAttempts = attacks.Sum(x => x.FailCount);
                    report.TotalSuccessfulConnections = legitConnections.Sum(x => x.SuccessCount);
                    
                    // Топ атакующих IP
                    report.TopAttackingIPs = attacks
                        .OrderByDescending(x => x.FailCount)
                        .Take(10)
                        .Select(x => new TopAttackerInfo
                        {
                            IPAddress = x.AddrId,
                            FailedAttempts = x.FailCount,
                            FirstSeen = x.First,
                            LastSeen = x.Last,
                            TargetedUsers = x.UserNames.ToList()
                        })
                        .ToList();
                    
                    // Анализ по дням
                    report.DailyStats = GenerateDailyStats(fromDate, toDate, addresses);
                    
                    // Анализ по часам
                    report.HourlyDistribution = GenerateHourlyDistribution(attacks);
                    
                    Log($"GenerateAttackSummary: сгенерирован отчёт за период {fromDate:yyyy-MM-dd} - {toDate:yyyy-MM-dd}");
                }
            }
            catch (Exception ex)
            {
                Log("GenerateAttackSummary: исключение: " + ex.ToString());
            }
            
            return report;
        }
        
        /// <summary>
        /// Генерирует отчёт по заблокированным IP-адресам
        /// </summary>
        /// <param name="fromDate">Начальная дата</param>
        /// <param name="toDate">Конечная дата</param>
        /// <returns>Отчёт по блокировкам</returns>
        public BlockedIPsReport GenerateBlockedIPsReport(DateTime fromDate, DateTime toDate)
        {
            var report = new BlockedIPsReport
            {
                PeriodStart = fromDate,
                PeriodEnd = toDate,
                GeneratedAt = DateTime.UtcNow
            };
            
            try
            {
                using (var db = new LiteDatabase($"Filename={dbPath};utc=true"))
                {
                    var blacklistCollection = db.GetCollection<BlackListEntry>("BlackList");
                    
                    var blockedEntries = blacklistCollection.Find(x => 
                        x.BlockedAt >= fromDate && x.BlockedAt <= toDate).ToList();
                    
                    report.TotalBlockedIPs = blockedEntries.Count;
                    report.AutoBlockedIPs = blockedEntries.Count(x => x.AutoBlocked);
                    report.ManuallyBlockedIPs = blockedEntries.Count(x => !x.AutoBlocked);
                    report.ReportedToAbuseIPDB = blockedEntries.Count(x => x.ReportedToAbuseIPDB);
                    
                    report.BlockedIPs = blockedEntries
                        .OrderByDescending(x => x.BlockedAt)
                        .Select(x => new BlockedIPInfo
                        {
                            IPAddress = x.IPAddress,
                            BlockedAt = x.BlockedAt,
                            Reason = x.Reason,
                            FailuresCount = x.FailuresCount,
                            AutoBlocked = x.AutoBlocked,
                            ReportedToAbuseIPDB = x.ReportedToAbuseIPDB
                        })
                        .ToList();
                    
                    Log($"GenerateBlockedIPsReport: сгенерирован отчёт по {blockedEntries.Count} заблокированным IP");
                }
            }
            catch (Exception ex)
            {
                Log("GenerateBlockedIPsReport: исключение: " + ex.ToString());
            }
            
            return report;
        }
        
        /// <summary>
        /// Генерирует отчёт по пользовательским сессиям
        /// </summary>
        /// <param name="fromDate">Начальная дата</param>
        /// <param name="toDate">Конечная дата</param>
        /// <returns>Отчёт по сессиям</returns>
        public SessionsReport GenerateSessionsReport(DateTime fromDate, DateTime toDate)
        {
            var report = new SessionsReport
            {
                PeriodStart = fromDate,
                PeriodEnd = toDate,
                GeneratedAt = DateTime.UtcNow
            };
            
            try
            {
                using (var db = new LiteDatabase($"Filename={dbPath};utc=true"))
                {
                    var sessionCollection = db.GetCollection<Session>("Session");
                    
                    var sessions = sessionCollection.Find(x => 
                        x.Start >= fromDate && x.Start <= toDate).ToList();
                    
                    report.TotalSessions = sessions.Count;
                    report.ActiveSessions = sessions.Count(x => x.End == null);
                    report.CompletedSessions = sessions.Count(x => x.End != null);
                    
                    // Анализ по пользователям
                    var userStats = sessions
                        .GroupBy(x => x.User)
                        .Select(g => new UserSessionInfo
                        {
                            UserName = g.Key,
                            SessionCount = g.Count(),
                            TotalDuration = g.Where(s => s.End != null)
                                .Sum(s => (s.End.Value - s.Start).TotalMinutes),
                            UniqueIPs = g.Select(s => s.Addr).Distinct().Count()
                        })
                        .OrderByDescending(x => x.SessionCount)
                        .ToList();
                    
                    report.UserStats = userStats;
                    
                    // Анализ по IP-адресам
                    var ipStats = sessions
                        .Where(x => !string.IsNullOrEmpty(x.Addr))
                        .GroupBy(x => x.Addr)
                        .Select(g => new IPSessionInfo
                        {
                            IPAddress = g.Key,
                            SessionCount = g.Count(),
                            UniqueUsers = g.Select(s => s.User).Distinct().Count(),
                            FirstConnection = g.Min(s => s.Start),
                            LastConnection = g.Max(s => s.Start)
                        })
                        .OrderByDescending(x => x.SessionCount)
                        .ToList();
                    
                    report.IPStats = ipStats;
                    
                    Log($"GenerateSessionsReport: сгенерирован отчёт по {sessions.Count} сессиям");
                }
            }
            catch (Exception ex)
            {
                Log("GenerateSessionsReport: исключение: " + ex.ToString());
            }
            
            return report;
        }
        
        /// <summary>
        /// Экспортирует отчёт в CSV формат
        /// </summary>
        /// <param name="report">Отчёт для экспорта</param>
        /// <param name="filePath">Путь к файлу</param>
        /// <returns>True если успешно экспортировано</returns>
        public bool ExportToCSV(AttackSummaryReport report, string filePath)
        {
            try
            {
                var csv = new StringBuilder();
                
                // Заголовок
                csv.AppendLine("RdpMon Attack Summary Report");
                csv.AppendLine($"Period: {report.PeriodStart:yyyy-MM-dd} - {report.PeriodEnd:yyyy-MM-dd}");
                csv.AppendLine($"Generated: {report.GeneratedAt:yyyy-MM-dd HH:mm:ss} UTC");
                csv.AppendLine();
                
                // Сводная статистика
                csv.AppendLine("Summary Statistics");
                csv.AppendLine("Metric,Value");
                csv.AppendLine($"Total Unique IPs,{report.TotalUniqueIPs}");
                csv.AppendLine($"Attacking IPs,{report.AttackingIPs}");
                csv.AppendLine($"Legitimate IPs,{report.LegitimateIPs}");
                csv.AppendLine($"Suspicious IPs,{report.SuspiciousIPs}");
                csv.AppendLine($"Total Failed Attempts,{report.TotalFailedAttempts}");
                csv.AppendLine($"Total Successful Connections,{report.TotalSuccessfulConnections}");
                csv.AppendLine();
                
                // Топ атакующих IP
                csv.AppendLine("Top Attacking IPs");
                csv.AppendLine("Rank,IP Address,Failed Attempts,First Seen,Last Seen,Targeted Users");
                
                for (int i = 0; i < report.TopAttackingIPs.Count; i++)
                {
                    var attacker = report.TopAttackingIPs[i];
                    csv.AppendLine($"{i + 1},{attacker.IPAddress},{attacker.FailedAttempts}," +
                                  $"{attacker.FirstSeen:yyyy-MM-dd HH:mm},{attacker.LastSeen:yyyy-MM-dd HH:mm}," +
                                  $"\"{string.Join("; ", attacker.TargetedUsers)}\"");
                }
                
                csv.AppendLine();
                
                // Статистика по дням
                csv.AppendLine("Daily Statistics");
                csv.AppendLine("Date,Unique IPs,Attacks,Failed Attempts,Successful Connections");
                
                foreach (var day in report.DailyStats)
                {
                    csv.AppendLine($"{day.Date:yyyy-MM-dd},{day.UniqueIPs},{day.AttackingIPs}," +
                                  $"{day.FailedAttempts},{day.SuccessfulConnections}");
                }
                
                File.WriteAllText(filePath, csv.ToString(), Encoding.UTF8);
                Log($"ExportToCSV: отчёт экспортирован в {filePath}");
                return true;
            }
            catch (Exception ex)
            {
                Log("ExportToCSV: исключение: " + ex.ToString());
                return false;
            }
        }
        
        /// <summary>
        /// Экспортирует отчёт в HTML формат
        /// </summary>
        /// <param name="report">Отчёт для экспорта</param>
        /// <param name="filePath">Путь к файлу</param>
        /// <returns>True если успешно экспортировано</returns>
        public bool ExportToHTML(AttackSummaryReport report, string filePath)
        {
            try
            {
                var html = new StringBuilder();
                
                html.AppendLine("<!DOCTYPE html>");
                html.AppendLine("<html>");
                html.AppendLine("<head>");
                html.AppendLine("<meta charset='utf-8'>");
                html.AppendLine("<title>RdpMon Attack Summary Report</title>");
                html.AppendLine("<style>");
                html.AppendLine("body { font-family: Arial, sans-serif; margin: 20px; }");
                html.AppendLine("table { border-collapse: collapse; width: 100%; margin: 20px 0; }");
                html.AppendLine("th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }");
                html.AppendLine("th { background-color: #f2f2f2; }");
                html.AppendLine(".summary { background-color: #f9f9f9; padding: 15px; margin: 20px 0; }");
                html.AppendLine(".red { color: #d9534f; }");
                html.AppendLine(".green { color: #5cb85c; }");
                html.AppendLine(".orange { color: #f0ad4e; }");
                html.AppendLine("</style>");
                html.AppendLine("</head>");
                html.AppendLine("<body>");
                
                // Заголовок
                html.AppendLine("<h1>RdpMon Attack Summary Report</h1>");
                html.AppendLine($"<p><strong>Period:</strong> {report.PeriodStart:yyyy-MM-dd} - {report.PeriodEnd:yyyy-MM-dd}</p>");
                html.AppendLine($"<p><strong>Generated:</strong> {report.GeneratedAt:yyyy-MM-dd HH:mm:ss} UTC</p>");
                
                // Сводная статистика
                html.AppendLine("<div class='summary'>");
                html.AppendLine("<h2>Summary Statistics</h2>");
                html.AppendLine("<table>");
                html.AppendLine("<tr><th>Metric</th><th>Value</th></tr>");
                html.AppendLine($"<tr><td>Total Unique IPs</td><td>{report.TotalUniqueIPs}</td></tr>");
                html.AppendLine($"<tr><td>Attacking IPs</td><td class='red'>{report.AttackingIPs}</td></tr>");
                html.AppendLine($"<tr><td>Legitimate IPs</td><td class='green'>{report.LegitimateIPs}</td></tr>");
                html.AppendLine($"<tr><td>Suspicious IPs</td><td class='orange'>{report.SuspiciousIPs}</td></tr>");
                html.AppendLine($"<tr><td>Total Failed Attempts</td><td class='red'>{report.TotalFailedAttempts}</td></tr>");
                html.AppendLine($"<tr><td>Total Successful Connections</td><td class='green'>{report.TotalSuccessfulConnections}</td></tr>");
                html.AppendLine("</table>");
                html.AppendLine("</div>");
                
                // Топ атакующих IP
                html.AppendLine("<h2>Top Attacking IPs</h2>");
                html.AppendLine("<table>");
                html.AppendLine("<tr><th>Rank</th><th>IP Address</th><th>Failed Attempts</th><th>First Seen</th><th>Last Seen</th><th>Targeted Users</th></tr>");
                
                for (int i = 0; i < report.TopAttackingIPs.Count; i++)
                {
                    var attacker = report.TopAttackingIPs[i];
                    html.AppendLine($"<tr>");
                    html.AppendLine($"<td>{i + 1}</td>");
                    html.AppendLine($"<td>{attacker.IPAddress}</td>");
                    html.AppendLine($"<td class='red'>{attacker.FailedAttempts}</td>");
                    html.AppendLine($"<td>{attacker.FirstSeen:yyyy-MM-dd HH:mm}</td>");
                    html.AppendLine($"<td>{attacker.LastSeen:yyyy-MM-dd HH:mm}</td>");
                    html.AppendLine($"<td>{string.Join(", ", attacker.TargetedUsers)}</td>");
                    html.AppendLine($"</tr>");
                }
                
                html.AppendLine("</table>");
                
                html.AppendLine("</body>");
                html.AppendLine("</html>");
                
                File.WriteAllText(filePath, html.ToString(), Encoding.UTF8);
                Log($"ExportToHTML: отчёт экспортирован в {filePath}");
                return true;
            }
            catch (Exception ex)
            {
                Log("ExportToHTML: исключение: " + ex.ToString());
                return false;
            }
        }
        
        /// <summary>
        /// Генерирует статистику по дням
        /// </summary>
        private List<DayStats> GenerateDailyStats(DateTime fromDate, DateTime toDate, List<Addr> addresses)
        {
            var dailyStats = new List<DayStats>();
            
            for (var date = fromDate.Date; date <= toDate.Date; date = date.AddDays(1))
            {
                var dayAddresses = addresses.Where(x => 
                    x.Last.Date == date || (x.First.Date <= date && x.Last.Date >= date)).ToList();
                
                var attacks = dayAddresses.Where(x => x.IsAttack()).ToList();
                var legitConnections = dayAddresses.Where(x => x.IsLegit()).ToList();
                
                dailyStats.Add(new DayStats
                {
                    Date = date,
                    UniqueIPs = dayAddresses.Count,
                    AttackingIPs = attacks.Count,
                    FailedAttempts = attacks.Sum(x => x.FailCount),
                    SuccessfulConnections = legitConnections.Sum(x => x.SuccessCount)
                });
            }
            
            return dailyStats;
        }
        
        /// <summary>
        /// Генерирует распределение атак по часам
        /// </summary>
        private List<HourStats> GenerateHourlyDistribution(List<Addr> attacks)
        {
            var hourlyStats = new List<HourStats>();
            
            for (int hour = 0; hour < 24; hour++)
            {
                var hourAttacks = attacks.Where(x => x.Last.Hour == hour).ToList();
                
                hourlyStats.Add(new HourStats
                {
                    Hour = hour,
                    AttackCount = hourAttacks.Count,
                    TotalFailedAttempts = hourAttacks.Sum(x => x.FailCount)
                });
            }
            
            return hourlyStats;
        }
        
        private static void Log(string msg)
        {
            Utils.Log("ReportsManager: " + msg);
        }
    }
    
    #region Модели отчётов
    
    /// <summary>
    /// Сводный отчёт по атакам
    /// </summary>
    public class AttackSummaryReport
    {
        public DateTime PeriodStart { get; set; }
        public DateTime PeriodEnd { get; set; }
        public DateTime GeneratedAt { get; set; }
        
        public int TotalUniqueIPs { get; set; }
        public int AttackingIPs { get; set; }
        public int LegitimateIPs { get; set; }
        public int SuspiciousIPs { get; set; }
        
        public long TotalFailedAttempts { get; set; }
        public long TotalSuccessfulConnections { get; set; }
        
        public List<TopAttackerInfo> TopAttackingIPs { get; set; } = new List<TopAttackerInfo>();
        public List<DayStats> DailyStats { get; set; } = new List<DayStats>();
        public List<HourStats> HourlyDistribution { get; set; } = new List<HourStats>();
    }
    
    /// <summary>
    /// Информация о топ атакующем IP
    /// </summary>
    public class TopAttackerInfo
    {
        public string IPAddress { get; set; }
        public int FailedAttempts { get; set; }
        public DateTime FirstSeen { get; set; }
        public DateTime LastSeen { get; set; }
        public List<string> TargetedUsers { get; set; } = new List<string>();
    }
    
    /// <summary>
    /// Статистика за день
    /// </summary>
    public class DayStats
    {
        public DateTime Date { get; set; }
        public int UniqueIPs { get; set; }
        public int AttackingIPs { get; set; }
        public long FailedAttempts { get; set; }
        public long SuccessfulConnections { get; set; }
    }
    
    /// <summary>
    /// Статистика за час
    /// </summary>
    public class HourStats
    {
        public int Hour { get; set; }
        public int AttackCount { get; set; }
        public long TotalFailedAttempts { get; set; }
    }
    
    /// <summary>
    /// Отчёт по заблокированным IP
    /// </summary>
    public class BlockedIPsReport
    {
        public DateTime PeriodStart { get; set; }
        public DateTime PeriodEnd { get; set; }
        public DateTime GeneratedAt { get; set; }
        
        public int TotalBlockedIPs { get; set; }
        public int AutoBlockedIPs { get; set; }
        public int ManuallyBlockedIPs { get; set; }
        public int ReportedToAbuseIPDB { get; set; }
        
        public List<BlockedIPInfo> BlockedIPs { get; set; } = new List<BlockedIPInfo>();
    }
    
    /// <summary>
    /// Информация о заблокированном IP
    /// </summary>
    public class BlockedIPInfo
    {
        public string IPAddress { get; set; }
        public DateTime BlockedAt { get; set; }
        public string Reason { get; set; }
        public int FailuresCount { get; set; }
        public bool AutoBlocked { get; set; }
        public bool ReportedToAbuseIPDB { get; set; }
    }
    
    /// <summary>
    /// Отчёт по сессиям
    /// </summary>
    public class SessionsReport
    {
        public DateTime PeriodStart { get; set; }
        public DateTime PeriodEnd { get; set; }
        public DateTime GeneratedAt { get; set; }
        
        public int TotalSessions { get; set; }
        public int ActiveSessions { get; set; }
        public int CompletedSessions { get; set; }
        
        public List<UserSessionInfo> UserStats { get; set; } = new List<UserSessionInfo>();
        public List<IPSessionInfo> IPStats { get; set; } = new List<IPSessionInfo>();
    }
    
    /// <summary>
    /// Информация о сессиях пользователя
    /// </summary>
    public class UserSessionInfo
    {
        public string UserName { get; set; }
        public int SessionCount { get; set; }
        public double TotalDuration { get; set; }
        public int UniqueIPs { get; set; }
    }
    
    /// <summary>
    /// Информация о сессиях с IP
    /// </summary>
    public class IPSessionInfo
    {
        public string IPAddress { get; set; }
        public int SessionCount { get; set; }
        public int UniqueUsers { get; set; }
        public DateTime FirstConnection { get; set; }
        public DateTime LastConnection { get; set; }
    }
    
    #endregion
}

3.6.6 ContextMenuManager.cs — Контекстное меню

using System;
using System.Windows.Forms;
using System.Threading.Tasks;

namespace Cameyo.RdpMon
{
    /// <summary>
    /// Модуль для управления контекстным меню записей подключений
    /// </summary>
    public class ContextMenuManager
    {
        private readonly IPBlocker ipBlocker;
        private readonly AbuseReporter abuseReporter;
        private readonly ListManager listManager;
        private readonly SettingsManager settingsManager;
        
        public ContextMenuManager()
        {
            ipBlocker = new IPBlocker();
            listManager = new ListManager();
            settingsManager = new SettingsManager();
            
            // Инициализируем AbuseReporter только если есть API ключ
            var settings = settingsManager.GetSettings();
            if (!string.IsNullOrEmpty(settings.AbuseIPDBApiKey))
            {
                abuseReporter = new AbuseReporter(settings.AbuseIPDBApiKey);
            }
        }
        
        /// <summary>
        /// Создаёт контекстное меню для записи подключения
        /// </summary>
        /// <param name="addr">Информация об IP-адресе</param>
        /// <returns>Настроенное контекстное меню</returns>
        public ContextMenuStrip CreateConnectionContextMenu(Addr addr)
        {
            var contextMenu = new ContextMenuStrip();
            
            // Заголовок с IP-адресом
            var headerItem = new ToolStripLabel($"IP: {addr.AddrId}")
            {
                Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold)
            };
            contextMenu.Items.Add(headerItem);
            contextMenu.Items.Add(new ToolStripSeparator());
            
            // Копировать IP
            var copyIPItem = new ToolStripMenuItem("Копировать IP")
            {
                Image = Properties.Resources.Copy, // Предполагается наличие иконки
                ShortcutKeys = Keys.Control | Keys.C
            };
            copyIPItem.Click += (sender, e) => CopyIPToClipboard(addr.AddrId);
            contextMenu.Items.Add(copyIPItem);
            
            contextMenu.Items.Add(new ToolStripSeparator());
            
            // Проверить в AbuseIPDB
            if (abuseReporter != null)
            {
                var checkAbuseItem = new ToolStripMenuItem("Проверить в AbuseIPDB")
                {
                    Image = Properties.Resources.Search
                };
                checkAbuseItem.Click += async (sender, e) => await CheckIPInAbuseIPDB(addr);
                contextMenu.Items.Add(checkAbuseItem);
            }
            
            // Блокировать IP (если не заблокирован и не в белом списке)
            if (!listManager.IsInBlackList(addr.AddrId) && !listManager.IsInWhiteList(addr.AddrId))
            {
                var blockIPItem = new ToolStripMenuItem("Блокировать IP")
                {
                    Image = Properties.Resources.Block,
                    ForeColor = System.Drawing.Color.Red
                };
                blockIPItem.Click += (sender, e) => BlockIP(addr);
                contextMenu.Items.Add(blockIPItem);
            }
            
            // Разблокировать IP (если заблокирован)
            if (listManager.IsInBlackList(addr.AddrId))
            {
                var unblockIPItem = new ToolStripMenuItem("Разблокировать IP")
                {
                    Image = Properties.Resources.Unblock,
                    ForeColor = System.Drawing.Color.Green
                };
                unblockIPItem.Click += (sender, e) => UnblockIP(addr);
                contextMenu.Items.Add(unblockIPItem);
            }
            
            contextMenu.Items.Add(new ToolStripSeparator());
            
            // Добавить в белый список (если не в белом списке)
            if (!listManager.IsInWhiteList(addr.AddrId))
            {
                var addToWhitelistItem = new ToolStripMenuItem("Добавить в белый список")
                {
                    Image = Properties.Resources.Whitelist
                };
                addToWhitelistItem.Click += (sender, e) => AddToWhitelist(addr);
                contextMenu.Items.Add(addToWhitelistItem);
            }
            
            // Отправить в AbuseIPDB (только для атак)
            if (addr.IsAttack() && abuseReporter != null)
            {
                contextMenu.Items.Add(new ToolStripSeparator());
                
                var reportToAbuseItem = new ToolStripMenuItem("Отправить информацию о атаке в AbuseIPDB")
                {
                    Image = Properties.Resources.Report,
                    ForeColor = System.Drawing.Color.DarkOrange
                };
                reportToAbuseItem.Click += async (sender, e) => await ReportToAbuseIPDB(addr);
                contextMenu.Items.Add(reportToAbuseItem);
            }
            
            contextMenu.Items.Add(new ToolStripSeparator());
            
            // Детальная информация
            var detailsItem = new ToolStripMenuItem("Детальная информация")
            {
                Image = Properties.Resources.Info
            };
            detailsItem.Click += (sender, e) => ShowDetailedInfo(addr);
            contextMenu.Items.Add(detailsItem);
            
            return contextMenu;
        }
        
        /// <summary>
        /// Копирует IP-адрес в буфер обмена
        /// </summary>
        /// <param name="ipAddress">IP-адрес для копирования</param>
        private void CopyIPToClipboard(string ipAddress)
        {
            try
            {
                Clipboard.SetText(ipAddress);
                ShowNotification($"IP-адрес {ipAddress} скопирован в буфер обмена", 
                    ToolTipIcon.Info);
                
                Log($"CopyIPToClipboard: {ipAddress} скопирован в буфер обмена");
            }
            catch (Exception ex)
            {
                Log($"CopyIPToClipboard: ошибка копирования {ipAddress}: " + ex.Message);
                MessageBox.Show("Ошибка копирования в буфер обмена", "Ошибка", 
                    MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
        }
        
        /// <summary>
        /// Блокирует IP-адрес
        /// </summary>
        /// <param name="addr">Информация об IP-адресе</param>
        private void BlockIP(Addr addr)
        {
            try
            {
                var result = MessageBox.Show(
                    $"Заблокировать IP-адрес {addr.AddrId}?\n\n" +
                    $"Неудачных попыток: {addr.FailCount}\n" +
                    $"Последняя активность: {addr.Last:yyyy-MM-dd HH:mm:ss}",
                    "Подтверждение блокировки",
                    MessageBoxButtons.YesNo,
                    MessageBoxIcon.Question);
                
                if (result == DialogResult.Yes)
                {
                    string reason = $"Ручная блокировка: {addr.FailCount} неудачных попыток";
                    bool success = ipBlocker.BlockIP(addr.AddrId, reason, false, addr.FailCount);
                    
                    if (success)
                    {
                        ShowNotification($"IP-адрес {addr.AddrId} успешно заблокирован", 
                            ToolTipIcon.Info);
                        Log($"BlockIP: {addr.AddrId} заблокирован вручную");
                    }
                    else
                    {
                        MessageBox.Show("Ошибка блокировки IP-адреса", "Ошибка",
                            MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }
            }
            catch (Exception ex)
            {
                Log($"BlockIP: ошибка блокировки {addr.AddrId}: " + ex.ToString());
                MessageBox.Show("Произошла ошибка при блокировке IP-адреса", "Ошибка",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
        
        /// <summary>
        /// Разблокирует IP-адрес
        /// </summary>
        /// <param name="addr">Информация об IP-адресе</param>
        private void UnblockIP(Addr addr)
        {
            try
            {
                var result = MessageBox.Show(
                    $"Разблокировать IP-адрес {addr.AddrId}?",
                    "Подтверждение разблокировки",
                    MessageBoxButtons.YesNo,
                    MessageBoxIcon.Question);
                
                if (result == DialogResult.Yes)
                {
                    bool success = ipBlocker.UnblockIP(addr.AddrId);
                    
                    if (success)
                    {
                        ShowNotification($"IP-адрес {addr.AddrId} разблокирован", 
                            ToolTipIcon.Info);
                        Log($"UnblockIP: {addr.AddrId} разблокирован");
                    }
                    else
                    {
                        MessageBox.Show("Ошибка разблокировки IP-адреса", "Ошибка",
                            MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }
            }
            catch (Exception ex)
            {
                Log($"UnblockIP: ошибка разблокировки {addr.AddrId}: " + ex.ToString());
                MessageBox.Show("Произошла ошибка при разблокировке IP-адреса", "Ошибка",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
        
        /// <summary>
        /// Добавляет IP в белый список
        /// </summary>
        /// <param name="addr">Информация об IP-адресе</param>
        private void AddToWhitelist(Addr addr)
        {
            try
            {
                var inputForm = new InputForm(
                    "Добавление в белый список",
                    $"Добавить IP-адрес {addr.AddrId} в белый список?",
                    "Описание (опционально):");
                
                if (inputForm.ShowDialog() == DialogResult.OK)
                {
                    string description = inputForm.InputText;
                    bool success = listManager.AddToWhiteList(addr.AddrId, description, "Manual");
                    
                    if (success)
                    {
                        ShowNotification($"IP-адрес {addr.AddrId} добавлен в белый список", 
                            ToolTipIcon.Info);
                        Log($"AddToWhitelist: {addr.AddrId} добавлен в белый список");
                        
                        // Если IP был заблокирован, разблокируем его
                        if (listManager.IsInBlackList(addr.AddrId))
                        {
                            ipBlocker.UnblockIP(addr.AddrId);
                        }
                    }
                    else
                    {
                        MessageBox.Show("Ошибка добавления в белый список", "Ошибка",
                            MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }
            }
            catch (Exception ex)
            {
                Log($"AddToWhitelist: ошибка добавления {addr.AddrId}: " + ex.ToString());
                MessageBox.Show("Произошла ошибка при добавлении в белый список", "Ошибка",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
        
        /// <summary>
        /// Проверяет IP в AbuseIPDB
        /// </summary>
        /// <param name="addr">Информация об IP-адресе</param>
        private async Task CheckIPInAbuseIPDB(Addr addr)
        {
            try
            {
                ShowNotification($"Проверка {addr.AddrId} в AbuseIPDB...", ToolTipIcon.Info);
                
                var response = await abuseReporter.CheckIPAsync(addr.AddrId);
                
                if (response != null && response.Data != null)
                {
                    var data = response.Data;
                    var message = $"Результат проверки IP {addr.AddrId} в AbuseIPDB:\n\n" +
                                  $"Confidence of Abuse: {data.AbuseConfidencePercentage}%\n" +
                                  $"Страна: {data.CountryName} ({data.CountryCode})\n" +
                                  $"ISP: {data.Isp}\n" +
                                  $"Общее количество сообщений: {data.TotalReports}\n" +
                                  $"Количество репортёров: {data.NumDistinctUsers}\n" +
                                  $"Последнее сообщение: {data.LastReportedAt?.ToString("yyyy-MM-dd HH:mm") ?? "N/A"}\n" +
                                  $"В белом списке: {(data.IsWhitelisted ? "Да" : "Нет")}";
                    
                    var icon = data.AbuseConfidencePercentage >= 75 ? MessageBoxIcon.Warning :
                               data.AbuseConfidencePercentage >= 25 ? MessageBoxIcon.Information :
                               MessageBoxIcon.None;
                    
                    MessageBox.Show(message, "Результат проверки AbuseIPDB", 
                        MessageBoxButtons.OK, icon);
                    
                    Log($"CheckIPInAbuseIPDB: {addr.AddrId} проверен, confidence: {data.AbuseConfidencePercentage}%");
                }
                else
                {
                    MessageBox.Show("Не удалось получить информацию из AbuseIPDB", "Ошибка",
                        MessageBoxButtons.OK, MessageBoxIcon.Warning);
                }
            }
            catch (Exception ex)
            {
                Log($"CheckIPInAbuseIPDB: ошибка проверки {addr.AddrId}: " + ex.ToString());
                MessageBox.Show("Произошла ошибка при проверке в AbuseIPDB", "Ошибка",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
        
        /// <summary>
        /// Отправляет сообщение о атаке в AbuseIPDB
        /// </summary>
        /// <param name="addr">Информация об атакующем IP</param>
        private async Task ReportToAbuseIPDB(Addr addr)
        {
            try
            {
                var result = MessageBox.Show(
                    $"Отправить сообщение о атаке с IP {addr.AddrId} в AbuseIPDB?\n\n" +
                    $"Неудачных попыток: {addr.FailCount}\n" +
                    $"Период активности: {addr.First:yyyy-MM-dd HH:mm} - {addr.Last:yyyy-MM-dd HH:mm}",
                    "Отправка сообщения в AbuseIPDB",
                    MessageBoxButtons.YesNo,
                    MessageBoxIcon.Question);
                
                if (result == DialogResult.Yes)
                {
                    ShowNotification($"Отправка сообщения о {addr.AddrId} в AbuseIPDB...", 
                        ToolTipIcon.Info);
                    
                    bool success = await abuseReporter.ReportBruteForceAsync(addr);
                    
                    if (success)
                    {
                        ShowNotification($"Сообщение о {addr.AddrId} отправлено в AbuseIPDB", 
                            ToolTipIcon.Info);
                        
                        // Отмечаем в базе данных, что сообщение отправлено
                        listManager.MarkAsReportedToAbuseIPDB(addr.AddrId);
                        
                        Log($"ReportToAbuseIPDB: {addr.AddrId} сообщение отправлено");
                    }
                    else
                    {
                        MessageBox.Show("Ошибка отправки сообщения в AbuseIPDB", "Ошибка",
                            MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }
            }
            catch (Exception ex)
            {
                Log($"ReportToAbuseIPDB: ошибка отправки {addr.AddrId}: " + ex.ToString());
                MessageBox.Show("Произошла ошибка при отправке сообщения в AbuseIPDB", "Ошибка",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
        
        /// <summary>
        /// Показывает детальную информацию об IP-адресе
        /// </summary>
        /// <param name="addr">Информация об IP-адресе</param>
        private void ShowDetailedInfo(Addr addr)
        {
            try
            {
                var details = $"Детальная информация об IP-адресе {addr.AddrId}\n\n" +
                              $"Тип подключения: {(addr.IsAttack() ? "🔴 Атака" : addr.IsLegit() ? "🟢 Легитимный" : "🟡 Подозрительный")}\n" +
                              $"Неудачных попыток: {addr.FailCount}\n" +
                              $"Успешных подключений: {addr.SuccessCount}\n" +
                              $"Первое обнаружение: {addr.First:yyyy-MM-dd HH:mm:ss} UTC\n" +
                              $"Последняя активность: {addr.Last:yyyy-MM-dd HH:mm:ss} UTC\n" +
                              $"Продолжительность: {Utils.DurationStr(addr.Last.Subtract(addr.First))}\n" +
                              $"Текущая активность: {(addr.IsOngoing() ? "Да" : "Нет")}\n\n" +
                              $"Пользователи, на которых нацелены атаки:\n{string.Join("\n", addr.UserNames)}\n\n" +
                              $"В белом списке: {(listManager.IsInWhiteList(addr.AddrId) ? "Да" : "Нет")}\n" +
                              $"В чёрном списке: {(listManager.IsInBlackList(addr.AddrId) ? "Да" : "Нет")}";
                
                MessageBox.Show(details, "Детальная информация", 
                    MessageBoxButtons.OK, MessageBoxIcon.Information);
                
                Log($"ShowDetailedInfo: показана информация о {addr.AddrId}");
            }
            catch (Exception ex)
            {
                Log($"ShowDetailedInfo: ошибка показа информации {addr.AddrId}: " + ex.ToString());
                MessageBox.Show("Произошла ошибка при отображении информации", "Ошибка",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
        
        /// <summary>
        /// Показывает уведомление пользователю
        /// </summary>
        /// <param name="message">Текст уведомления</param>
        /// <param name="icon">Иконка уведомления</param>

4. Ссылки по теме

  1. RDP Monitoring — Cameyo
  2. New Open Source Tool Helps Prevent Brute Force and Ransomware Attacks — malwaretips.com
  3. Cameyo introduces security features to protect customers from brute-force attacks and ransomware
  4. Exposed RDP Servers See 150k Brute Force Attempts Per Week — simpatico.com
  5. AbuseIPDB Integration for Cortex XSOAR — xsoar.pan.dev
  6. AbuseIPDB and Splunk Integration
  7. AbuseIPDB Documentation
  8. Fail2ban Integration — AbuseIPDB
  9. Architecture for WinForms Applications — Stack Overflow
  10. Service Application Programming Architecture — Microsoft Docs
  11. LiteDB in C# — FoxLearn
  12. LiteDB in ASP.NET Core API — Blog
  13. RDPmon на GitHub
  14. PDF Инструкция по RdpMon
  15. Supported platforms: Windows Monitoring — Syteca
  16. RDCMan на GitHub
  17. GitHub Copilot: Исследование проектов на GitHub (документация на русском)
  18. Cameyo на GitHub
  19. Обзор ПО для мониторинга удалённого рабочего стола — CurrentWare
  20. RDPMonWebGUI — GitHub
  21. Создание нового репозитория на GitHub (русская документация)
  22. RDP Port Shield против ransomware — SensorsTechForum
  23. Загрузка RDCMan — Microsoft
  24. RdpMon.cs — исходный код
  25. Главная страница GitHub
  26. Топ 5 RDP-менеджеров — diright.ru
  27. README.md RDPmon — GitHub (radtek)
  28. Data_analyst — GitHub
  29. Мониторинг пользователей терминала Windows через Zabbix
  30. Cameyo защищает от brute-force атак и ransomware — PremisesNetworks.com
  31. Remote Desktop Services — Википедия (англ.)
  32. Logical Architecture for Desktop Hosting — Microsoft
  33. README.md RDPmon — GitHub (plain)
  34. Reference Architecture for Desktop Hosting — Microsoft
  35. AbuseIPDB API — GitHub topics
  36. Скачать RDPmon — Cameyo
  37. UI Design Pattern for Windows Forms — Stack Overflow
  38. Как структурировать WinForms приложение — Reddit
  39. WinForms Architecture Patterns — GitHub
  40. Сетевые отчёты Splunk — research.splunk.com
  41. Model-View-Presenter для WinForms — markheath.net
  42. Исследования по RDP мониторингу — muni.cz
  43. YouTube: Introduction to RDPmon
  44. LiteDB and CommunityToolkit DataSync
  45. Data Science для кибербезопасности: анализ brute force по RDP — Microsoft
  46. Использование шаблонов проектирования в WinForms — MSDN Magazine
  47. Concurrent Access to LiteDB file over Network — Stack Overflow
  48. Playbook of the Week: Responding to RDP Brute Force Attacks — PaloAlto Networks
  49. Пример отчёта, PNG (AI output direct)
  50. Исходный CSV пример (AI output direct)
    Разработка и продвижение сайтов webseed.ru
    Прокрутить вверх