V5.0 安全模块大扩展:Meteor Client 全功能反作弊 + AI行为分析 + 服务器保护

新增 45 个安全模块,覆盖 Meteor Client 全部 hack 类别:

【反作弊基础扩展 6 模块】
- AntiTimerService: 游戏加速检测(移动tick间隔分析)
- AntiVelocityService: 击退修改检测(伤害后速度分析)
- AntiPhaseService: 穿墙检测(固体方块射线追踪)
- AntiBlinkService: 闪烁检测(伤害-断连-重连时序)
- AntiFastBreakService: 快速破坏检测(方块标准破坏时间对比)
- AntiElytraFlyService: 鞘翅飞行检测(速度/高度操控分析)

【Meteor Combat 10 模块】
- AntiCriticalsService: 自动暴击检测(跳跃/地面状态验证)
- AntiAutoCrystalService: 水晶Aura检测(放置/爆炸时序分析)
- AntiAutoTotemService: 自动图腾检测(副手装备速度分析)
- AntiSurroundService: 自动包围检测(脚下方块精确放置模式)
- AntiAutoTrapService: 自动陷阱检测(目标周围方块网格分析)
- AntiAutoArmorService: 自动装备检测(多槽位同tick交换)
- AntiChestSwapService: 自动胸甲交换检测(鞘翅-胸甲乒乓切换)
- AntiAutoLogService: 自动断线检测(伤害-断连500ms时序)
- AntiHitboxesService: 扩展碰撞箱检测(射线-边界边缘命中率)
- AntiBowAimBotService: 弓箭自瞄检测(移动目标精度分析)

【Meteor Movement 10 模块】
- AntiNoSlowService: 无减速检测(物品使用+移动速度组合)
- AntiSpiderService: 爬墙检测(无攀爬方块垂直上升)
- AntiStepService: 自动跨步检测(非跳跃1格上升)
- AntiPacketFlyService: 数据包飞行检测(连续悬浮/弹跳模式)
- AntiAirJumpService: 空中跳跃检测(离地跳跃包)
- AntiLongJumpService: 远跳检测(跳跃后水平速度衰减分析)
- AntiAntiHungerService: 免饥饿检测(活动量vs饥饿消耗率)
- AntiFastFallService: 快速下落检测(超终端速度持续下落)
- AntiVClipService: 垂直穿墙检测(固体方块间瞬移)
- AntiNoFallService 增强: 新增GroundSpoof/数据包地面/坠落累积/瞬移坠落检测

【Meteor World+Player 10 模块】
- AntiSpeedMineService: 加速挖掘检测(方块标准破坏时间对比)
- AntiFastUseService: 快速使用检测(物品冷却时间分析)
- AntiNoInteractService: 交互绕过检测(持剑开箱等异常操作)
- AntiAutoMineService: 自动挖掘检测(挖掘间隔方差/视角追踪)
- AntiVeinMinerService: 连锁挖矿检测(相邻矿石快速序列)
- AntiAutoToolService: 自动工具检测(同tick切工具+开挖)
- AntiFakePlayerService: 假人检测(心跳响应/交互缺失/移动模式)
- AntiPistonAuraService: 活塞光环检测(战斗上下文活塞激活率)
- AntiAnchorService: 锚定检测(受击零位移+封闭空间)
- AntiStashFinderService: 仓库扫描检测(网格区块加载+零交互)

【网络协议安全 5 模块】
- ProtocolStateValidator: 协议状态机验证(四态转换违规检测)
- TokenBucketRateLimiter: 令牌桶速率限制器(可复用速率控制基元)
- BotFingerprintDetector: 机器人指纹检测(五维行为指纹分析)
- NBTExploitPrevention: NBT漏洞防护(深度/大小/书页/告示牌验证)
- ConnectionHandshakeValidator: 握手完整性验证(协议版本/状态/主机名)

【服务器保护 4 模块】
- ChunkLoadRateLimiter: 区块加载限制器(WARN/LIMIT/BLOCK三级响应)
- EntityCountEnforcer: 实体数量执行器(分块限制+优先级清理)
- RedstoneUpdateLimiter: 红石更新限制器(三级渐进+观察者钟检测)
- CrashExploitSignatureDB: 崩溃漏洞签名库(12种已知漏洞+4级自动响应)

【AI/ML 行为分析 4 模块】
- BehavioralProfilingEngine: 行为分析引擎(5维特征+香农熵+Z分数)
- ThreatScoreAggregator: 威胁分数聚合器(指数衰减+4级上报)
- MovementPatternAnalyzer: 移动模式分析器(FFT频谱+旋转分析)
- CombatPatternRecognizer: 战斗模式识别器(CPS方差+多目标熵+扫视分析)

配置更新:AlertType新增39个枚举值,SuperEvolutionConfig新增39个开关,
application.yml新增39个配置项。323项全量测试通过,0失败。
This commit is contained in:
Peijun Zhao
2026-05-16 18:02:49 +08:00
parent 67e873525b
commit 1b66197804
54 changed files with 23497 additions and 1 deletions
@@ -0,0 +1,347 @@
package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
/**
* 自动挖矿检测 (AutoMine) — V5.3 世界/玩家/杂物安全模块
*
* 检测原理:
* Meteor Client 的 AutoMine 模块能自动挖掘玩家视野中或预设类型的方块,无需人工瞄准。
* 正常人类挖矿具有明显的非均匀特征:反应延迟可变、方块间切换有停顿、瞄准有微调。
* 而 AutoMine 表现出的行为模式是高度一致的机器人特征。本模块通过以下维度检测:
* 1. 挖掘间隔一致性——人类挖掘间隔有自然随机波动(变异系数通常 > 0.25),
* 机器人挖掘间隔高度一致(变异系数 < 0.12)。
* 2. 零遗漏检测——人类会偶尔打空或错过目标方块,AutoMine 每次都精准命中。
* 如果一个玩家挖了 20+ 方块却没有一次空挥,即为可疑。
* 3. 视线微调检测——人类在切换挖掘目标时会有小幅度的视角调整,
* 机器人直接跳转到目标方块,没有中间过渡。
* 4. 长时间连续挖掘——正常玩家每隔数分钟会移动、拾取物品或暂停,
* AutoMine 可以连续挖矿数十分钟不中断。
*
* 配置开关:serverguard.security.super-evolution.anti-auto-mine
*/
@Service
public class AntiAutoMineService {
private final ServerGuardConfig config;
/** 每个玩家的挖掘记录 */
private final Map<String, List<MineRecord>> playerMineRecords = new ConcurrentHashMap<>();
/** 每个玩家的空挥计数(swing without breaking) */
private final Map<String, AtomicLong> playerMissedSwings = new ConcurrentHashMap<>();
/** 已标记的玩家 */
private final Map<String, Instant> flaggedPlayers = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private final AtomicLong totalMineEvents = new AtomicLong(0);
private final AtomicLong autoMineViolations = new AtomicLong(0);
/** 挖掘间隔变异系数阈值——低于此值为机器人特征 */
private static final double INTERVAL_COV_THRESHOLD = 0.12;
/** 最少样本数用于变异系数分析 */
private static final int MIN_SAMPLES_FOR_COV = 10;
/** 零遗漏判定——超过此数量连续命中无空挥即为可疑 */
private static final int PERFECT_STREAK_THRESHOLD = 20;
/** 视线切换角度阈值(度)——人类在切换方块间会有微调 */
private static final double LOOK_MICRO_ADJUST_THRESHOLD = 1.5;
/** 长时间连续挖掘阈值(分钟) */
private static final long LONG_MINING_MINUTES = 15;
/** 记录保留时间(秒) */
private static final long RECORD_RETENTION_SECONDS = 900;
/** 标记持续时间(秒) */
private static final long FLAG_DURATION_SECONDS = 1800;
public AntiAutoMineService() {
this(new ServerGuardConfig());
}
@Autowired
public AntiAutoMineService(ServerGuardConfig config) {
this.config = config;
scheduler.scheduleAtFixedRate(this::cleanupOldRecords, 120, 300, TimeUnit.SECONDS);
}
/**
* 记录一次挥动事件(无论是否命中方块)。
*
* @param playerName 玩家名称
* @param playerUUID 玩家 UUID
* @param lookYaw 玩家当前水平视角
* @param lookPitch 玩家当前垂直视角
*/
public void recordSwing(String playerName, String playerUUID, float lookYaw, float lookPitch) {
if (!config.getSecurity().getSuperEvolution().isAntiAutoMine()) {
return;
}
AtomicLong missed = playerMissedSwings.computeIfAbsent(playerName,
k -> new AtomicLong(0));
missed.incrementAndGet();
}
/**
* 检测一次方块破坏完成事件——与上一次破坏进行对比分析。
*
* @param playerName 玩家名称
* @param playerUUID 玩家 UUID
* @param blockType 被破坏的方块类型
* @param x 方块 X 坐标
* @param y 方块 Y 坐标
* @param z 方块 Z 坐标
* @param lookYaw 完成破坏时的水平视角
* @param lookPitch 完成破坏时的垂直视角
* @return 检测结果
*/
public DetectionResult detectBlockBreak(String playerName, String playerUUID,
String blockType, int x, int y, int z,
float lookYaw, float lookPitch) {
if (!config.getSecurity().getSuperEvolution().isAntiAutoMine()) {
return DetectionResult.clean();
}
Instant flaggedUntil = flaggedPlayers.get(playerName);
if (flaggedUntil != null) {
if (Instant.now().isAfter(flaggedUntil)) {
flaggedPlayers.remove(playerName);
} else {
return DetectionResult.flagged(List.of(
"PLAYER_ALREADY_FLAGGED: " + playerName + " under auto-mine investigation"));
}
}
Instant now = Instant.now();
List<MineRecord> records = playerMineRecords.computeIfAbsent(playerName,
k -> Collections.synchronizedList(new ArrayList<>()));
// 获取本次破坏前一次挥动的空挥状态
AtomicLong missed = playerMissedSwings.computeIfAbsent(playerName,
k -> new AtomicLong(0));
boolean hadMissBefore = missed.get() > 0;
long missedCount = missed.getAndSet(0); // 重置空挥计数器
MineRecord record = new MineRecord(now, blockType, x, y, z, lookYaw, lookPitch,
missedCount == 0);
records.add(record);
totalMineEvents.incrementAndGet();
int score = 0;
List<String> reasons = new ArrayList<>();
// === 检测 1: 挖掘间隔一致性 ===
if (records.size() >= MIN_SAMPLES_FOR_COV) {
double cov = calculateIntervalCOV(records);
if (cov < INTERVAL_COV_THRESHOLD) {
score += 30;
reasons.add("UNIFORM_INTERVAL: break interval COV=" + String.format("%.4f", cov) +
" (bot-like consistency, threshold < " + INTERVAL_COV_THRESHOLD + ")");
}
}
// === 检测 2: 零遗漏检测(完美连续命中) ===
// 计算连续完美命中(无空挥)的次数
long perfectStreak = countPerfectStreak(records);
if (perfectStreak >= PERFECT_STREAK_THRESHOLD) {
score += 35;
reasons.add("PERFECT_STREAK: " + perfectStreak +
" consecutive blocks broken without a single missed swing (bot-like precision)");
}
// === 检测 3: 视线微调缺失检测 ===
// 检查最近两次破坏间的视角变化
if (records.size() >= 2) {
MineRecord prev = records.get(records.size() - 2);
double yawDiff = Math.abs(record.lookYaw - prev.lookYaw);
double pitchDiff = Math.abs(record.lookPitch - prev.lookPitch);
// 如果切换了方块但视角几乎没变,说明不是手动瞄准
boolean sameBlock = (prev.x == x && prev.y == y && prev.z == z);
if (!sameBlock && yawDiff < LOOK_MICRO_ADJUST_THRESHOLD && pitchDiff < LOOK_MICRO_ADJUST_THRESHOLD) {
// 切换方块但视角无微调,可疑(累积检测)
}
// 检测大量无微调的方块切换
if (records.size() >= 10) {
long noAdjustSwitches = countNoAdjustSwitches(records);
if (noAdjustSwitches >= 8) {
score += 20;
reasons.add("NO_LOOK_ADJUST: " + noAdjustSwitches +
" block switches with no visible micro-adjustment (auto-targeting)");
}
}
}
// === 检测 4: 长时间连续挖掘 ===
if (records.size() >= 2) {
long sessionMs = now.toEpochMilli() - records.get(0).time.toEpochMilli();
long sessionMinutes = sessionMs / 60000;
if (sessionMinutes > LONG_MINING_MINUTES) {
score += 20;
reasons.add("LONG_SESSION: continuously mining for " + sessionMinutes +
" minutes without break (threshold=" + LONG_MINING_MINUTES + " min)");
}
}
if (score >= 50) {
flaggedPlayers.put(playerName, Instant.now().plusSeconds(FLAG_DURATION_SECONDS));
autoMineViolations.incrementAndGet();
return DetectionResult.flagged(reasons);
} else if (score >= 30) {
return DetectionResult.suspicious(score, reasons);
}
return DetectionResult.clean();
}
/**
* 计算挖掘间隔的变异系数(COV)。
*/
private double calculateIntervalCOV(List<MineRecord> records) {
List<Long> intervals = new ArrayList<>();
int start = Math.max(0, records.size() - 20);
for (int i = start + 1; i < records.size(); i++) {
intervals.add(records.get(i).time.toEpochMilli() - records.get(i - 1).time.toEpochMilli());
}
if (intervals.size() < 5) return 1.0;
double mean = intervals.stream().mapToLong(Long::longValue).average().orElse(0);
if (mean == 0) return 1.0;
double variance = intervals.stream()
.mapToDouble(i -> Math.pow(i - mean, 2))
.average().orElse(0);
return Math.sqrt(variance) / mean;
}
/**
* 统计连续完美命中(无空挥)的次数。
*/
private long countPerfectStreak(List<MineRecord> records) {
int streak = 0;
for (int i = records.size() - 1; i >= 0; i--) {
if (records.get(i).wasPerfectHit) {
streak++;
} else {
break;
}
}
return streak;
}
/**
* 统计最近方块切换中无视差微调的次数。
*/
private long countNoAdjustSwitches(List<MineRecord> records) {
int count = 0;
int start = Math.max(0, records.size() - 10);
for (int i = start + 1; i < records.size(); i++) {
MineRecord prev = records.get(i - 1);
MineRecord curr = records.get(i);
boolean sameBlock = (prev.x == curr.x && prev.y == curr.y && prev.z == curr.z);
if (!sameBlock) {
double yawDiff = Math.abs(curr.lookYaw - prev.lookYaw);
double pitchDiff = Math.abs(curr.lookPitch - prev.lookPitch);
if (yawDiff < LOOK_MICRO_ADJUST_THRESHOLD && pitchDiff < LOOK_MICRO_ADJUST_THRESHOLD) {
count++;
}
}
}
return count;
}
public void clearPlayer(String playerName) {
playerMineRecords.remove(playerName);
playerMissedSwings.remove(playerName);
flaggedPlayers.remove(playerName);
}
public Map<String, Object> getStatus() {
Map<String, Object> s = new LinkedHashMap<>();
s.put("totalMineEvents", totalMineEvents.get());
s.put("autoMineViolations", autoMineViolations.get());
s.put("trackedPlayers", playerMineRecords.size());
s.put("flaggedPlayers", flaggedPlayers.size());
s.put("enabled", config.getSecurity().getSuperEvolution().isAntiAutoMine());
return s;
}
public long getTotalMineEvents() { return totalMineEvents.get(); }
public long getAutoMineViolations() { return autoMineViolations.get(); }
private void cleanupOldRecords() {
Instant cutoff = Instant.now().minusSeconds(RECORD_RETENTION_SECONDS);
playerMineRecords.entrySet().removeIf(e -> {
List<MineRecord> records = e.getValue();
records.removeIf(r -> r.time.isBefore(cutoff));
return records.isEmpty();
});
playerMissedSwings.entrySet().removeIf(e ->
!playerMineRecords.containsKey(e.getKey()));
flaggedPlayers.entrySet().removeIf(e -> e.getValue().isBefore(Instant.now()));
}
// ==================== 内部数据类 ====================
/**
* 挖掘记录——追踪单次方块破坏的时间、位置和视角信息。
*/
private static class MineRecord {
final Instant time;
final String blockType;
final int x, y, z;
final float lookYaw, lookPitch;
final boolean wasPerfectHit; // 本次破坏前是否没有空挥(完美命中)
MineRecord(Instant time, String blockType, int x, int y, int z,
float lookYaw, float lookPitch, boolean wasPerfectHit) {
this.time = time;
this.blockType = blockType;
this.x = x;
this.y = y;
this.z = z;
this.lookYaw = lookYaw;
this.lookPitch = lookPitch;
this.wasPerfectHit = wasPerfectHit;
}
}
// ==================== 检测结果类 ====================
/**
* 自动挖矿检测结果。
*/
public static class DetectionResult {
private final boolean flagged;
private final boolean suspicious;
private final int score;
private final List<String> reasons;
private DetectionResult(boolean flagged, boolean suspicious, int score, List<String> reasons) {
this.flagged = flagged;
this.suspicious = suspicious;
this.score = score;
this.reasons = reasons;
}
public static DetectionResult clean() {
return new DetectionResult(false, false, 0, List.of());
}
public static DetectionResult suspicious(int score, List<String> reasons) {
return new DetectionResult(false, true, score, reasons);
}
public static DetectionResult flagged(List<String> reasons) {
return new DetectionResult(true, true, 100, reasons);
}
public boolean isFlagged() { return flagged; }
public boolean isSuspicious() { return suspicious; }
public boolean isClean() { return !flagged && !suspicious; }
public int getScore() { return score; }
public List<String> getReasons() { return reasons; }
}
}