Architektura zapobiegająca oszustwom: uprawnienia serwera i analiza behawioralna
Oszukiwanie w grach wideo to problem branży gier warty 30 miliardów dolarów rocznie. Nie chodzi o sprawiedliwość: oszuści niszczą doświadczenie innych graczy, zwiększają je rezygnacja z gry, zmniejszenie przychodów i zniszczenie reputacji gry. Badanie przeprowadzone w 2024 r. wykazało odkryło, że 77% graczy porzuciło grę wieloosobową z powodu oszustów.
Tradycyjne rozwiązania zapobiegające oszustwom – Valve VAC, Easy Anti-Cheat, BattlEye – opierają się na głównie o wykrywanie po stronie klienta: sterownik monitorujący procesy system operacyjny do wykrywania oszustw programowych. Podejście to ma zasadniczy problem: oraz grę w kotka i myszkę, w której oszuści konsekwentnie wygrywają na niestandardowym sprzęcie, hypervisor i obejście jądra. Nowoczesne środki zaradcze iarchitektura autorytatywna serwerowa w połączeniu z analiza behawioralna ML.
W tym artykule omówimy kompletną architekturę zapobiegającą oszustwom: z autorytatywnego serwera waliduje każde działanie, po statystyczną walidację wartości odstających, po systemy oparte na uczeniu maszynowym transformator do wykrywania zachowań (dokładność 96,94%, AUC 98,36% w wyszukiwaniach w 2025 r.).
Czego się nauczysz
- Rodzaje cheatów: speed hack, aim bot, hack na ścianę, ESP, exploit ekonomiczny
- Architektura autorytatywna serwerowo: co delegować klientowi, a co nie
- Walidacja po stronie serwera: fizyka, wykrywanie kolizji, linia wzroku
- Statystyczne wykrywanie wartości odstających: Z-score, k-sigma do analizy celu
- Analiza behawioralna ML: inżynieria funkcji dla zapobiegania oszustwom
- Wykrywanie oszustw w oparciu o transformator (podejście AntiCheatPT)
- Analiza powtórek: wykrywanie post-hoc za pomocą telemetrii
- Fałszywie pozytywne zarządzanie: ochrona niewinnych graczy
1. Taksonomia oszustw: z czym walczysz
Przed zaprojektowaniem zabezpieczeń istotne jest zrozumienie, czemu należy przeciwdziałać. Kody są podzielone na dwie kategorie makr: te, które modyfikują zachowanie klienta (manipulacja danymi wejściowymi) oraz te, które wykorzystują luki w protokole lub logice gry (eksploatacja protokołu).
Taksonomia – kody i środki zaradcze
| Kody | Opis | Wykrywanie | Zapobieganie |
|---|---|---|---|
| Szybki hack | Zmień zegar systemowy, aby działać szybciej | Serwer sprawdzania prędkości | Fizyka autorytatywna dla serwera |
| Hack teleportacyjny | Ustaw dowolną pozycję, pomiń sprawdzanie ruchu | Kontrola delty pozycji | Autorytatywne stanowisko serwera |
| Celuj w bota | Automatycznie celuje w graczy z nadludzką precyzją | Analiza celów statystycznych | Wykrywanie zachowań ML |
| Hack ścienny / ESP | Widzi graczy przez ściany | Ubój ściętych elementów po stronie serwera | Nie wysyłaj niewidocznych lokalizacji wroga |
| Bez odrzutu | Eliminuje odrzuty, umożliwiając strzelanie ze stałą precyzją | Analiza wzorca strzałów | Symulacja odrzutu po stronie serwera |
| Eksploat ekonomiczny | Duplikuj walutę lub przedmioty w trybie wyścigu | Audyt transakcji | Transakcje idempotentne + ograniczenie stawki |
| Manipulacja pakietami | Modyfikuj pakiety sieciowe, aby zmienić stan gry | Weryfikacja wiadomości | DTLS/TLS + walidacja schematu |
2. Architektura autorytatywna serwerowa: podstawa bezpieczeństwa
Kluczową zasadą współczesnego przeciwdziałania oszustwom jest: serwer jest źródłem prawdy absolutnej. Klient wysyła tylko plik wejście (co gracz chce zrobić), nigdy wyniki (co się stało). Serwer oblicza stan gry i przekazuje go klientom. To kategorycznie eliminuje hacki przyspieszające, hacki teleportacyjne, exploity ekonomiczne i hacki ścian pakietów.
// Server-Authoritative Game Loop - Go
// Il server calcola TUTTO: posizione, danno, risultati
type AuthoritativeServer struct {
players map[string]*PlayerState
world *WorldState
physics *PhysicsEngine // Server-side physics simulation
lineOfSight *LOSCalculator // Calcolo visibilità server-side
}
// ProcessInput: l'unica cosa che il client invia e l'input
// Il server valida e calcola il risultato
func (s *AuthoritativeServer) ProcessInput(playerID string, input PlayerInput) *GameStateUpdate {
player, ok := s.players[playerID]
if !ok {
return nil
}
// === VALIDAZIONE INPUT ===
// 1. Rate limiting: un player non può inviare input più di N/tick
if !s.rateLimiter.Allow(playerID) {
return &GameStateUpdate{Error: "input_rate_exceeded"}
}
// 2. Validazione movimento: fisica server-side
if input.Type == InputTypeMove {
newPos := player.Position.Add(input.MoveDelta)
// Verifica velocità massima (impossibile con speed hack se server calcola)
maxSpeed := player.GetMaxSpeed() // Dipende da buff, terreno, etc.
actualSpeed := input.MoveDelta.Length() / s.tickDeltaTime
if actualSpeed > maxSpeed*1.1 { // 10% tolerance per jitter di rete
s.flagSuspicious(playerID, "speed_violation",
fmt.Sprintf("speed=%.2f max=%.2f", actualSpeed, maxSpeed))
return &GameStateUpdate{Position: player.Position} // Ignora il movimento
}
// Verifica collisioni server-side
if !s.world.IsPositionValid(newPos, player.Size) {
s.flagSuspicious(playerID, "wall_clip_attempt",
fmt.Sprintf("pos=%v", newPos))
return &GameStateUpdate{Position: player.Position}
}
// Aggiorna posizione SOLO dopo validazione
player.Position = newPos
}
// 3. Validazione attacco: server-side hit detection
if input.Type == InputTypeShoot {
shot := s.validateShot(player, input)
if shot != nil {
s.applyDamage(shot)
}
}
// 4. Visibility culling: non invia posizioni di nemici non visibili
// Previene wall hack via packet sniffing
visiblePlayers := s.lineOfSight.GetVisiblePlayers(player)
return &GameStateUpdate{
Position: player.Position,
VisiblePlayers: visiblePlayers, // Solo chi il player PUO vedere
// Non include mai posizioni di giocatori non visibili!
}
}
// validateShot: hit detection server-side con lag compensation
func (s *AuthoritativeServer) validateShot(shooter *PlayerState, input PlayerInput) *ShotResult {
// Lag compensation: ricostruisci lo stato del mondo al momento dello sparo lato client
// Il client ha inviato l'input con timestamp: usa quello per trovare lo stato server passato
pastState := s.history.GetStateAt(input.ClientTimestamp - shooter.Latency)
// Verifica line-of-sight al momento dello sparo
if !s.lineOfSight.HasLoS(shooter.Position, input.TargetPosition, pastState) {
return nil // Muro tra shooter e target: no shot
}
// Verifica distanza massima dell'arma
weapon := shooter.GetEquippedWeapon()
dist := shooter.Position.DistanceTo(input.TargetPosition)
if dist > weapon.MaxRange {
return nil // Fuori portata
}
// Verifica che il target esista e sia effettivamente alla posizione indicata
// Con tolerance per il lag (lagCompensation)
target := pastState.GetPlayerAt(input.TargetPosition, lagCompensationRadius(shooter.Latency))
if target == nil {
return nil // Nessun target in quella posizione
}
return &ShotResult{
ShooterID: shooter.ID,
TargetID: target.ID,
Damage: weapon.CalculateDamage(dist),
Headshot: input.IsHeadshot && s.validateHeadshot(shooter, target, pastState),
}
}
3. Wykrywanie botów celowych: analiza statystyczna
Bot celujący tworzy wzorce celowania, które są statystycznie niemożliwe dla człowieka: kąty obrotu natychmiastowe, szybkie strzały ze 100% precyzją, doskonałe śledzenie. Wykrywanie opiera się na analizie statystyki ruchów myszy/sticka w czasie, porównujące gracza z rozkładem populacji.
// aim_analysis.go - Statistical aim bot detection
package anticheat
import (
"math"
"time"
)
// AimSample: campione di movimento del mouse/stick in un singolo frame
type AimSample struct {
DeltaYaw float64 // Angolo orizzontale (gradi/frame)
DeltaPitch float64 // Angolo verticale
OnTarget bool // Se punta verso un nemico
SnapToTarget float64 // Distanza di snap verso il target più vicino
Timestamp time.Time
}
// PlayerAimProfile: profilo cumulativo dei movimenti di mira
type PlayerAimProfile struct {
PlayerID string
Samples []AimSample
SnapRates []float64 // Storico snap-to-target rates
FlickAngles []float64 // Angoli dei flick shots
}
// AnalyzeAim: restituisce un aim suspicion score (0.0 - 1.0)
func AnalyzeAim(profile *PlayerAimProfile) AimAnalysisResult {
if len(profile.Samples) < 100 {
return AimAnalysisResult{Score: 0, Insufficient: true}
}
// Feature 1: Snap rate analysis
// Un aimbot "snappa" al target con velocità sovrumana
snapsToTarget := 0
for _, s := range profile.Samples {
if s.OnTarget && s.SnapToTarget > 50 { // 50 gradi snap in un frame = impossibile
snapsToTarget++
}
}
snapRate := float64(snapsToTarget) / float64(len(profile.Samples))
// Feature 2: Micro-correction analysis
// Gli aimbot mostrano pattern di micro-correzione innaturali dopo ogni sparo
corrections := extractMicroCorrections(profile.Samples)
correctionMean := mean(corrections)
correctionStd := stddev(corrections, correctionMean)
// Feature 3: Jitter analysis
// Il mouse umano ha jitter naturale. Zero jitter = aimbot
jitter := calculateJitter(profile.Samples)
humanJitterRange := [2]float64{0.3, 3.0} // Range tipico umano (gradi/frame)
// Feature 4: FOV tracking efficiency
// Aimbot = efficienza quasi perfetta nel FOV del target
trackingEfficiency := calculateTrackingEfficiency(profile.Samples)
// Calcola score combinato (threshold empirici da dati reali)
score := 0.0
if snapRate > 0.05 { // > 5% snap shots = sospetto
score += 0.4 * math.Min(snapRate/0.05, 1.0)
}
if jitter < humanJitterRange[0] { // Jitter troppo basso = aimbot
score += 0.3 * (1.0 - jitter/humanJitterRange[0])
}
if trackingEfficiency > 0.92 { // > 92% tracking efficiency = sovrumano
score += 0.3 * math.Min((trackingEfficiency-0.92)/0.08, 1.0)
}
return AimAnalysisResult{
Score: score,
SnapRate: snapRate,
CorrectionStd: correctionStd,
Jitter: jitter,
TrackingEfficiency: trackingEfficiency,
Suspicious: score > 0.7,
}
}
func mean(data []float64) float64 {
sum := 0.0
for _, v := range data { sum += v }
return sum / float64(len(data))
}
func stddev(data []float64, m float64) float64 {
variance := 0.0
for _, v := range data { variance += (v - m) * (v - m) }
return math.Sqrt(variance / float64(len(data)))
}
4. Uczenie maszynowe: Transformatory do wykrywania zachowań
Analiza statystyczna wychwytuje grubsze oszustwa, ale zaawansowane oszustwa (np. boty celujące z jitterem sztuczne) wymagają podejścia ML. Najnowsze badania (AntiCheatPT, 2025) pokazują, jak m.in Transformatory zastosowane w sekwencjach akcji gier osiągają dokładność na poziomie 96,94%. AUC wynoszące 98,36% w wykrywaniu, przewyższające LSTM i tradycyjne CNN.
// Feature engineering per ML anti-cheat
// Estrae feature da una finestra temporale di azioni di gioco
from typing import List, Dict
import numpy as np
from dataclasses import dataclass
@dataclass
class GameAction:
timestamp: float
action_type: str # "move", "shoot", "reload", "ability"
delta_x: float # Movimento mouse X
delta_y: float # Movimento mouse Y
aim_x: float # Angolo mira
aim_y: float
on_target: bool # True se mira verso un nemico
result: str # "hit", "miss", "kill"
def extract_features(actions: List[GameAction], window_size: int = 100) -> np.ndarray:
"""
Estrae feature dalla finestra di azioni per classificazione ML.
Output: array di shape (window_size, feature_dim) per Transformer
"""
features = []
for i in range(min(len(actions), window_size)):
a = actions[i]
# Feature cinematiche (movimento)
aim_speed = np.sqrt(a.delta_x**2 + a.delta_y**2)
aim_accel = 0.0
if i > 0:
prev_speed = np.sqrt(actions[i-1].delta_x**2 + actions[i-1].delta_y**2)
dt = a.timestamp - actions[i-1].timestamp
aim_accel = (aim_speed - prev_speed) / max(dt, 0.001)
# Feature di target acquisition
snap_magnitude = 0.0
if a.on_target and i > 0 and not actions[i-1].on_target:
snap_magnitude = aim_speed # Velocita di snap al target
# Feature di shooting behavior
is_shoot = 1.0 if a.action_type == "shoot" else 0.0
is_hit = 1.0 if a.result == "hit" else 0.0
is_kill = 1.0 if a.result == "kill" else 0.0
# Feature inter-azione
time_since_last_shoot = 0.0
for j in range(i-1, max(0, i-10), -1):
if actions[j].action_type == "shoot":
time_since_last_shoot = a.timestamp - actions[j].timestamp
break
feature_vector = np.array([
aim_speed, # Velocita di mira
aim_accel, # Accelerazione mira
a.delta_x, # Movimento X raw
a.delta_y, # Movimento Y raw
snap_magnitude, # Magnitudine snap
float(a.on_target), # On target flag
is_shoot, # E uno sparo?
is_hit, # Ha colpito?
is_kill, # Ha ucciso?
time_since_last_shoot, # Tempo dall'ultimo sparo
])
features.append(feature_vector)
# Padding se la finestra e più corta di window_size
while len(features) < window_size:
features.append(np.zeros(10))
return np.array(features, dtype=np.float32)
# Modello Transformer per classificazione comportamentale (PyTorch)
import torch
import torch.nn as nn
class AntiCheatTransformer(nn.Module):
def __init__(self, feature_dim=10, d_model=64, nhead=4, num_layers=3, window_size=100):
super().__init__()
self.input_projection = nn.Linear(feature_dim, d_model)
self.positional_encoding = nn.Embedding(window_size, d_model)
encoder_layer = nn.TransformerEncoderLayer(
d_model=d_model, nhead=nhead, dim_feedforward=256,
dropout=0.1, batch_first=True
)
self.transformer = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)
self.classifier = nn.Sequential(
nn.Linear(d_model, 32),
nn.ReLU(),
nn.Dropout(0.1),
nn.Linear(32, 2) # 2 classi: legittimo, cheater
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
# x: (batch, window_size, feature_dim)
batch_size, seq_len, _ = x.shape
positions = torch.arange(seq_len, device=x.device).unsqueeze(0).expand(batch_size, -1)
x = self.input_projection(x) + self.positional_encoding(positions)
x = self.transformer(x)
# Usa il CLS token (prima posizione) per classificazione
x = self.classifier(x[:, 0, :])
return x # logits: (batch, 2)
5. Fałszywie pozytywne zarządzanie: ochrona niewinnych graczy
Największy błąd systemu przeciwdziałającego oszustwom i banowania niewinnych graczy: VPN, który symuluje duże opóźnienia, gracz o wysokich umiejętnościach z wyjątkowymi reakcjami, nieprawidłowo działający kontroler generuje nietypowe dane wejściowe. Fałszywy pozytywny wynik niszczy pewność gracza i jest prawie niemożliwy odzyskać to. System musi być zaprojektowany z wieloma warstwami potwierdzeń przed każdym zakazem.
// sanction_pipeline.go - Multi-layer sanction decision pipeline
package anticheat
type SuspicionReport struct {
PlayerID string
ReportType string // "speed_hack", "aim_bot", etc.
Score float64 // 0.0 - 1.0
Evidence []Evidence
Timestamp time.Time
}
type SanctionPipeline struct {
suspicionDB *SuspicionDatabase
accountAge *AccountAgeService
humanReview *HumanReviewQueue
}
// ProcessSuspicion: decide cosa fare con una segnalazione sospetta
func (p *SanctionPipeline) ProcessSuspicion(report SuspicionReport) SanctionDecision {
// Layer 1: Accumulo prove nel tempo
// Una singola violazione non e sufficiente per agire
history := p.suspicionDB.GetHistory(report.PlayerID, 30*24*time.Hour) // 30 giorni
history = append(history, report)
// Calcola score cumulativo pesato per recency
cumulativeScore := 0.0
for i, h := range history {
age := time.Since(h.Timestamp).Hours() / 24 // giorni
weight := math.Exp(-age / 7) // Decadimento esponenziale su 7 giorni
cumulativeScore += h.Score * weight * (float64(i+1) / float64(len(history)))
}
// Layer 2: Account age factor
// Account nuovi con alto suspicion score = più probabile cheater
accountAgeDays := p.accountAge.GetAgeDays(report.PlayerID)
if accountAgeDays < 7 {
cumulativeScore *= 1.3 // Boost per account nuovi
} else if accountAgeDays > 365 {
cumulativeScore *= 0.8 // Discount per account vecchi e stabiliti
}
// Layer 3: Decision tree
switch {
case cumulativeScore >= 0.95:
// Auto-ban: evidenze schiaccianti, molto difficile falso positivo
return SanctionDecision{
Action: "permanent_ban",
AutoApply: true,
Reason: fmt.Sprintf("cumulative_score=%.3f", cumulativeScore),
}
case cumulativeScore >= 0.80:
// Soft ban temporaneo + review umana obbligatoria
return SanctionDecision{
Action: "temp_ban_24h",
AutoApply: true,
SendToReview: true,
Reason: "high_suspicion_pending_review",
}
case cumulativeScore >= 0.60:
// Solo monitoraggio aumentato, nessuna sanzione automatica
p.suspicionDB.SetMonitoringLevel(report.PlayerID, MonitoringHigh)
return SanctionDecision{
Action: "monitor_only",
AutoApply: false,
}
default:
// Falso positivo probabile: solo log
return SanctionDecision{Action: "log_only", AutoApply: false}
}
}
6. Analiza powtórek: wykrywanie post-hoc
Nie wszystkie cheaty można wykryć w czasie rzeczywistym. Analiza powtórek - możliwa dzięki pełna telemetria rejestrowana przez serwer - pozwala na przeglądanie meczów po min Gracz został zgłoszony przez innych, stosując bardziej wymagającą obliczeniowo analizę.
-- ClickHouse: Query per identificare candidati sospetti da analisi replay
-- Identifica giocatori con statistiche outlier negli ultimi 7 giorni
SELECT
player_id,
count() AS total_matches,
avg(toFloat64OrZero(payload['headshot_rate'])) AS avg_headshot_rate,
avg(toFloat64OrZero(payload['kda'])) AS avg_kda,
avg(toFloat64OrZero(payload['avg_ttk_ms'])) AS avg_ttk_ms,
-- Accuracy Z-Score rispetto alla media della regione
-- Z > 3: più di 3 sigma sopra la media = outlier statistico
(avg(toFloat64OrZero(payload['headshot_rate'])) -
avg(avg(toFloat64OrZero(payload['headshot_rate'])))
OVER (PARTITION BY toStartOfDay(server_ts))) /
stddevPop(toFloat64OrZero(payload['headshot_rate']))
OVER (PARTITION BY toStartOfDay(server_ts)) AS headshot_zscore,
-- Ratio di report ricevuti da altri player
countIf(payload['was_reported'] = 'true') / count() AS report_rate
FROM game_analytics.events_all
WHERE event_type = 'gameplay.match_end'
AND server_ts >= now() - INTERVAL 7 DAY
GROUP BY player_id
HAVING
total_matches >= 10 -- Minimo partite per avere dati significativi
AND (
headshot_zscore > 3 -- Statistically impossible accuracy
OR avg_ttk_ms < 100 -- Kills troppo veloci
OR report_rate > 0.3 -- > 30% partite con segnalazioni
)
ORDER BY headshot_zscore DESC, report_rate DESC
LIMIT 100;
Typowe błędy w przeciwdziałaniu oszustwom
- Ban tylko za wysokie K/D: Bardzo dobry gracz ma wysokie K/D. Analizuj zawsze w połączeniu z wieloma sygnałami (wzorce celowania, prędkość, raporty) przed podjęciem działania.
- Ignoruj opóźnienia w kontrolkach: Z kompensacją opóźnień, gracz z Opóźnienie 100 ms może przypominać „strzelanie przez ściany”. Kalibracja tolerancji walidacyjnych w oparciu o rzeczywiste opóźnienie odtwarzacza.
- Przejrzysty system przeciwdziałający oszustwom: Nigdy nie ujawniaj szczegółów systemu ochrona przed oszustwami: oszuści analizują reakcje, aby zrozumieć, co uruchomić, a czego unikać. Używaj niejednoznacznych odpowiedzi i losowych opóźnień przed banami.
- Brak procedury odwoławczej: Nawet najbardziej precyzyjny system popełnia błędy. Oferta A przejrzysty i humanitarny proces odwoławczy w przypadku kwestionowanych zakazów.
Wnioski
Skuteczny system przeciwdziałający oszustwom w 2025 roku wymaga wielowarstwowego podejścia:architektura autorytatywny dla serwera jako niepodważalny fundament, walidacja statystyczna dla wzorców anomalnych i uczenie maszynowe (zwłaszcza Transformers) dla zaawansowane wykrywanie zachowań. Żaden z tych trzech elementów sam w sobie nie jest wystarczający.
Zarządzanie fałszywymi alarmami jest równie ważne jak ich wykrywanie: system, który blokuje wiele z nich niewinny i gorszy niż brak systemu przeciwdziałającego oszustwom. Wielopoziomowy rurociąg sankcji z przeglądem obowiązkowe prawa człowieka w przypadkach granicznych oraz otwarta procedura odwoławcza nie podlegają negocjacjom.
Kolejne kroki w serii Game Backend
- Poprzedni artykuł: System dobierania graczy: ELO, Glicko-2 i zarządzanie kolejkami
- Następny artykuł: Open Match i Nakama: zaplecze gier typu open source
- Powiązane serie: Bezpieczeństwo sieciowe — ocena bezpieczeństwa API i podatności na zagrożenia







