guard/docs/research/anti-bot.md
loki5512344 cf9608ce5d
Initial commit: Rampart v0.2.0
Multi-layer DDoS protection for Minecraft servers.

- rampart-core: Edge node with XDP/eBPF + Rust L7 filtering
- rampart-manager: REST API with JWT auth, Redis sync
- rampart-cli: CLI tool for operators
- velocity-plugin: Domain check, HMAC verify, server registry, load balancer
- paper-plugin: Auto-registration, heartbeat, HMAC verify
- dashboard: React + Vite web UI for management
2026-07-20 20:53:32 +02:00

9.6 KiB
Raw Blame History

Anti-Bot - Sonar, Challenge системы, Fingerprinting

Актуально: v0.2+

Путь игрока через защиту

Новый игрок
    |
    v
┌──────────────────┐
│  Edge нода       │  Rate limit, Blacklist, Death code
│  Rust            │  Невалидные пакеты → бан IP
└────────┬─────────┘
         v (валидный handshake)
┌──────────────────┐
│  Velocity        │  DomainCheck, HmacCheck
│  Java            │  Неизвестный домен → блок
└────────┬─────────┘
         v (подписанный HMAC)
┌──────────────────┐
│  Sonar Limbo     │  Гравитация, Vehicle, TCP timing
│  Java            │  Не прошёл → блок IP на N мин
└────────┬─────────┘
         v (прошёл физику)
┌──────────────────┐
│  Custom          │  Timing challenge, Map CAPTCHA
│  Challenge       │  Не прошёл → блок IP
└────────┬─────────┘
         v
┌──────────────────┐
│  Hub / Game      │  Игрок на сервере
│  Server          │  Поведенческий анализ первые 30 сек
└──────────────────┘

Каждый слой может заблокировать игрока. Verified DB на Redis - прошёл один раз, не проверяется снова (TTL 24h).


Слои защиты от ботов

[1] XDP rate limit         - ограничивает скорость SYN flood
[2] Rust rate limit        - ограничивает connections/сек per IP
[3] HMAC verification      - только через наш edge (криптография)
[4] ASN reputation         - датацентровые IP = строже
[5] Sonar 3.0 (Limbo)     - физическая проверка
[6] Custom challenge       - кастомная механика (нет готового обхода)
[7] Behavioral analysis    - паттерны поведения на хабе

Sonar 3.0 - базовый слой (июль 2026)

GitHub: jonesdevelopment/sonar
Версия: 3.x, релиз 12 июля 2026
Поддержка: Velocity 3.4-3.5.x, MC 1.8-26.2

Как работает

Игрок → Velocity → Sonar перехватывает
  ↓
Отправляет на Limbo (лёгкий фейковый сервер)
  ↓
Проверки на Limbo:
  ├─ Гравитация: игрок должен падать вниз
  ├─ Vehicle: правильные пакеты при взаимодействии с лодкой
  ├─ TCP timing: не слишком быстрые ответы
  └─ Очередь: физически ограничивает число одновременных верификаций
  ↓
Прошёл → IP в verified DB → следующие подключения проходят мгновенно

Конфиг

# sonar/config.yml
general:
  max-online-per-ip: 3
  min-players-for-attack: 8       # при N+ новых conn/сек → режим атаки

verification:
  timing:
    first-packet: 3500            # мс на первый пакет
    movement: 10000               # мс на проверку физики
  gravity:
    enabled: true
    captcha-on-fail: true
  vehicle:
    enabled: true

database:
  type: MYSQL                     # или POSTGRESQL, H2
  host: "10.0.0.1"
  database: "sonar"
  expiration: 5                   # verified IP живёт N дней

Кастомный challenge (поверх Sonar)

Почему нужен кастомный

Sonar открытый → атакующий читает код → пишет обход
Кастомный → нет готового обхода → атакующий тратит время
Меняем механику регулярно → обход устаревает

Идеи challenge (от простого к сложному)

1. Timing challenge

// Игрок должен ответить МЕЖДУ 2 и 8 секундами
// Боты отвечают мгновенно или с постоянной задержкой

long sent = System.currentTimeMillis();
// ...ждём ответ...
long elapsed = System.currentTimeMillis() - sent;

if (elapsed < 2000) {
    // Слишком быстро - скрипт
    fail("Ответ слишком быстрый");
} else if (elapsed > 8000) {
    // AFK/медленный скрипт
    fail("Время вышло");
} else {
    pass();
}

2. Map CAPTCHA

// Рендерим картинку на карте Minecraft
// Случайный шрифт из пула 50+ шрифтов
// Игрок вводит код в чате

MapRenderer renderer = new CaptchaMapRenderer(challenge.getCode());
ItemStack map = new ItemStack(Material.FILLED_MAP);
map.setItemMeta(mapMeta);
player.getInventory().setItemInMainHand(map);
player.sendMessage("§eВведи код с карты в чат:");

3. Поведенческий анализ (первые 30 сек на хабе)

// Смотрим на паттерны движения
// Реальный игрок: случайные повороты, ускорения, паузы
// Бот: линейное движение или полная неподвижность

@EventHandler
public void onPlayerMove(PlayerMoveEvent e) {
    BehaviorProfile profile = profiles.get(e.getPlayer().getUniqueId());
    profile.recordMovement(e.getTo());

    if (profile.getSamples() >= 50) {
        double score = profile.calculateBotProbability();
        if (score > 0.85) {
            triggerChallenge(e.getPlayer());
        }
    }
}

4. Контекстный вопрос

// Вопрос зависит от случайного события на сервере
// Бот не знает контекст

String[] events = {"Последний вошедший игрок", "Текущее время на сервере"};
// "Как зовут последнего игрока который зашёл перед тобой?"
// Бот не знает → провал

Репутационная система IP

// Каждый IP получает score от -100 до +100
// Хранится в Redis с TTL

pub struct IpReputation {
    score: i32,
    last_updated: u64,
}

impl IpReputation {
    pub fn apply_event(&mut self, event: ReputationEvent) {
        let delta = match event {
            ReputationEvent::SuccessfulLogin    => +10,
            ReputationEvent::HourWithoutIssues  => +5,
            ReputationEvent::RateLimitHit       => -20,
            ReputationEvent::InvalidPacket      => -30,
            ReputationEvent::BotChallengeFailed => -50,
            ReputationEvent::BotChallengePass   => +15,
        };
        self.score = (self.score + delta).clamp(-100, 100);
    }

    pub fn get_rate_multiplier(&self) -> f64 {
        match self.score {
            s if s >= 80  => 2.0,   // доверенный - больше лимит
            s if s >= 0   => 1.0,   // нормальный
            s if s >= -30 => 0.5,   // подозрительный
            s if s >= -60 => 0.2,   // проблемный
            _             => 0.05,  // почти в бане
        }
    }
}

Bloom Filter для блэклиста

// Для очень больших блэклистов (миллионы IP)
// Bloom filter: 1% false positive, но 100x меньше памяти

// HashSet<u32> на 1M IP: ~32 MB
// Bloom filter на 1M IP:  ~2 MB при p=0.01

use bloomfilter::Bloom;

pub struct FastBlacklist {
    bloom: Bloom<u32>,       // быстрая предпроверка (может дать false positive)
    exact: DashMap<u32, BanEntry>, // точная проверка (только если bloom сказал "да")
}

impl FastBlacklist {
    pub fn is_blocked(&self, ip: u32) -> bool {
        // Если bloom говорит "нет" - точно не в блэклисте (нет false negative)
        if !self.bloom.check(&ip) { return false; }
        // Bloom говорит "возможно да" - проверяем точно
        self.exact.contains_key(&ip)
    }
}

VPN / Proxy детекция

pub struct VpnDetector {
    // MaxMind GeoLite2-ASN + список известных VPN/proxy ASN
    asn_reader: maxminddb::Reader<Vec<u8>>,
    vpn_asns: HashSet<u32>,
    datacenter_keywords: Vec<Regex>,
}

impl VpnDetector {
    pub fn classify(&self, ip: IpAddr) -> IpCategory {
        let Ok(record) = self.asn_reader.lookup::<Asn>(ip) else {
            return IpCategory::Unknown;
        };

        if let Some(asn) = record.autonomous_system_number {
            if self.vpn_asns.contains(&asn) {
                return IpCategory::VPN;
            }
        }

        if let Some(org) = record.autonomous_system_organization {
            if self.datacenter_keywords.iter().any(|r| r.is_match(org)) {
                return IpCategory::Datacenter;
            }
        }

        IpCategory::Residential
    }
}

Список VPN ASN: https://github.com/X4BNet/lists_vpn (обновляется еженедельно)
MaxMind GeoLite2-ASN: бесплатно при регистрации на maxmind.com