アンチチートアーキテクチャ: サーバー権限と動作分析
ビデオゲームにおける不正行為は、ゲーム業界にとって年間 300 億ドル規模の問題です。そうではない すべては公平性です。チーターは他のプレイヤーの経験を破壊し、増加させます。 チャーンし、収益を減らし、ゲームの評判を傷つけます。 2024 年の調査では、 プレイヤーの 77% がチーターのためにマルチプレイヤー タイトルを放棄したことがわかりました。
従来のアンチチート ソリューション (Valve の VAC、Easy Anti-Cheat、BattlEye) がベースになっています 主にについて クライアント側の検出: のプロセスを監視するドライバー ソフトウェアのチートを検出するオペレーティング システム。このアプローチには根本的な問題があります。 そして、カスタムハードウェアでは詐欺師が常に勝ついたちごっこです。 ハイパーバイザーとカーネルのバイパス。現代の対策とサーバー権限のあるアーキテクチャ と組み合わせた 行動分析ML.
この記事では、権限のあるサーバーから、完全なアンチチート アーキテクチャを探っていきます。 外れ値の統計的検証、ML ベースのシステムに至るまで、あらゆるアクションを検証します。 行動検出用のトランス (2025 年の検索で精度 96.94%、AUC 98.36%)。
何を学ぶか
- チートの種類: スピードハック、エイムボット、ウォールハック、ESP、経済エクスプロイト
- サーバー権限アーキテクチャ: クライアントに何を委任し、何を委任しないのか
- サーバー側の検証: 物理学、衝突検出、見通し線
- 統計的外れ値の検出: 目的分析のための Z スコア、k シグマ
- 行動分析 ML: チート対策のための特徴エンジニアリング
- トランスフォーマーベースのチート検出 (AntiCheatPT アプローチ)
- リプレイ分析: テレメトリによる事後検出
- 誤検知管理: 無実のプレイヤーを保護する
1. チート分類: 何と戦っているのか
防御を設計する前に、何に対抗する必要があるかを理解することが重要です。チートは分割されています 2 つのマクロ カテゴリに分類されます。クライアントの動作 (入力操作) を変更するものと、 プロトコルまたはゲーム ロジックの脆弱性を悪用するもの (プロトコル悪用)。
分類上の不正行為とその対策
| チート | 説明 | 検出 | 防止 |
|---|---|---|---|
| スピードハック | システムクロックを変更してより速く動作させる | 速度チェックサーバー | サーバー権限のある物理学 |
| テレポートハック | 任意の位置を設定、モーション検証をスキップ | 位置デルタチェック | サーバー権限のある立場 |
| エイムボット | 超人的な精度でプレイヤーを自動的に狙います | 統計的目的分析 | ML 行動検出 |
| ウォールハック/ESP | 彼は壁越しに選手たちを見ている | サーバー側の錐台カリング | 見えない敵の位置を送信しない |
| 無反動 | 反動を排除し、安定した精度で射撃します。 | ショットパターン分析 | サーバー側の反動シミュレーション |
| 経済の活用 | 競合状態による通貨またはアイテムの重複 | 取引監査 | べき等トランザクション + レート制限 |
| パケット操作 | ネットワークパケットを変更してゲームの状態を変更する | メッセージの検証 | DTLS/TLS + スキーマ検証 |
2. サーバー権限のあるアーキテクチャ: セキュリティの基盤
最新のアンチチートの重要な原則は次のとおりです。 サーバーは絶対的な真実の源です。 クライアントは送信するだけです 入力 (プレイヤーがやりたいこと)、決して結果ではない (何が起こったのか)。サーバーはゲームの状態を計算し、それをクライアントに伝えます。 これにより、スピード ハッキング、テレポート ハッキング、経済的搾取、パケット ウォール ハッキングが完全に排除されます。
// 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. エイムボットの検出: 統計分析
照準ボットは、統計的に人間には不可能な照準パターン、つまり回転角度を生成します。 100% の精度で瞬時にフリックショットを行い、完璧なトラッキングを実現します。検出は分析に基づいて行われます プレイヤーと分布を比較した、マウス/スティックの動きの経時的な統計。 人口の。
// 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. 機械学習: 行動検出用のトランスフォーマー
統計分析では、より粗いチートもキャプチャしますが、高度なチート (例: ジッターのあるエイムボット) 人工) には ML アプローチが必要です。最新の研究 (AntiCheatPT、2025) では、 ゲームのアクション シーケンスに適用されたトランスフォーマーは 96.94% の精度に達し、 検出における AUC は 98.36% で、LSTM や従来の 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. 誤検知管理: 無実のプレイヤーを保護する
チート対策システムと無実のプレイヤーの禁止による最悪の間違い: VPN をシミュレートする 高い遅延、並外れた反応を持つハイスキルのプレイヤー、故障したコントローラー 異常な入力を生成します。誤検知はプレイヤーの自信を打ち砕き、ほぼ不可能です。 それを回復してください。システムは、禁止する前に複数の層で確認を行うように設計する必要があります。
// 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. リプレイ分析:事後検出
すべてのチートがリアルタイムで検出できるわけではありません。リプレイ分析 - おかげで可能 サーバーによって記録された完全なテレメトリ - 試合後に試合を確認できます。 player は他の人によって報告されており、より計算量の多い分析が適用されています。
-- 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;
アンチチートでよくある間違い
- 高K/Dの場合のみ禁止: 非常に優れたプレイヤーは高い K/D を持っています。分析する 行動する前に、常に複数の信号 (照準パターン、速度、レポート) と組み合わせてください。
- コントロールのレイテンシを無視する:ラグ補正あり、 100ms の遅延は「壁を突き抜けている」ように感じることがあります。検証許容誤差を調整する プレーヤーの実際の遅延に基づきます。
- 明確なアンチチートシステム: システムの詳細を決して公開しないでください アンチチート: チーターは応答を分析して、何をトリガーし、何を回避すべきかを理解します。 禁止される前に、曖昧な回答とランダムな遅延を使用してください。
- 異議申し立ての手続きはありません: 最も正確なシステムでも間違いは起こります。を提供します 異議のある禁止に対する明確かつ人道的な控訴プロセス。
結論
2025 年の効果的なチート対策システムには、多層的なアプローチが必要です。建築 サーバー権限のある 揺るぎない基盤として、 統計的検証 異常なパターンについては、 機械学習 (特にトランスフォーマー) 高度な行動検出。 3 つのどれだけでも十分ではありません。
誤検知の管理は検出と同じくらい重要です。つまり、多数のユーザーを禁止するシステムです。 無実であり、アンチチート システムがないよりも悪いです。審査を伴う多層制裁パイプライン 境界線にある事件に対する強制的人権と公開の上訴手続きには交渉の余地はありません。
ゲーム バックエンド シリーズの次のステップ
- 前の記事: マッチメイキング システム: ELO、Glicko-2、キュー管理
- 次の記事: オープンマッチとナカマ: オープンソースのゲームバックエンド
- 関連シリーズ: Web セキュリティ - API のセキュリティと脆弱性の評価







