server.dll)中移动输入启发式算法的静态分析本文对 Counter-Strike 2 引擎(server.dll)中实现的两个客户端移动验证例程进行了静态逆向工程分析:输入自动化 / SOCD 评估器(sub_1801C6B30,命名为 CVE-2026-1801C / QUANTUM-SHIFT)和跳跃请求速率限制器(sub_180A7EDB0,命名为 CVE-2026-180A7 / BAL-JUMP)。通过反汇编分析以及对底层状态机的形式化数学建模,我们证明了这两种启发式算法均存在结构性边界情况。具体而言,我们记录了 sub_1801C6B30 中的离散时间量化如何在固定单 tick 相位偏移下产生不变分类边界,以及 sub_180A7EDB0 如何依赖一个在锁相单脉冲触发下保持不变的单变量区间度量($\Delta t$)。
sub_1801C6B30server.dll(Win64 正式版)0x1C6B30void __fastcall sub_1801C6B30(int *pMovementServices, __int64 pPlayerController, __int64 pUserCmdPB)该例程评估通过客户端 CBaseUserCmdPB 载荷提交的移动按键转换事件(+moveleft / +moveright)。执行遵循条件流水线:
sv_auto_cstrafe_attempt_window,默认 100)。sub_1801C6B30)void __fastcall sub_1801C6B30(int *pMovementServices, __int64 pPlayerController, __int64 pUserCmdPB)
{
if (!pPlayerController || (*(_BYTE *)(pUserCmdPB + 16) & 1) == 0)
return;
__int64 pPawn = sub_180B137A0(pPlayerController);
if (!pPawn || !(*(unsigned __int8 (__fastcall **)(__int64))(*(_QWORD *)pPawn + 3216i64))(pPawn))
return;
float *pVel = (float *)sub_1803CBD90(pPawn, &szVelocityBuf);
float vSq = (pVel[0] * pVel[0]) + (pVel[1] * pVel[1]) + (pVel[2] * pVel[2]);
// Gating: Minimum velocity threshold
if (vSq <= 18279.04f)
return;
int rawTicks = *(_DWORD *)(pUserCmdPB + 24);
bool bIsOverlap = (rawTicks >= 0);
int absTicks = bIsOverlap ? rawTicks : ~rawTicks;
double curTime = sub_1801F46D0(off_181C9F350);
if (curTime * 1e-9 * (double)absTicks >= 15.5)
return;
int roundedTicks = (int)V_roundd();
if ((unsigned int)roundedTicks > 15)
return;
// Symbol Assignment
char symbolCode;
if (roundedTicks == 0)
symbolCode = 1; // Success / Perfect 0-tick
else
symbolCode = bIsOverlap ? 2 : 3; // Overlap or Underlap
// Fetch ConVar configuration pointers
unsigned int windowCap = *GetConVarUInt(&unk_181DDBD30, &qword_181DDBD38);
if (windowCap - 1 > 999)
return;
sub_1801ECEA0(pMovementServices, windowCap);
// Append symbol to circular buffer
int bufCap = pMovementServices[0];
if (bufCap > 0)
{
int writeIdx = pMovementServices[6];
if (writeIdx >= 0 && writeIdx < bufCap)
{
*(_BYTE *)(writeIdx + *((_QWORD *)pMovementServices + 1)) = symbolCode;
if (++pMovementServices[6] == bufCap)
pMovementServices[6] = 0;
}
}
// Update discrete tick frequency histograms
if (bIsOverlap)
pMovementServices[roundedTicks + 7]++;
else
pMovementServices[roundedTicks + 23]++;
int seqLen = *GetConVarInt(&unk_181DDBD40, &qword_181DDBD48);
if ((unsigned int)(seqLen - 1) > 999)
return;
// Evaluate sliding window sequence metrics
int totalValid = 0;
int totalOverlaps = 0;
int curSequenceSuccesses = 0;
int maxSequenceSuccesses = 0;
for (int i = 0; i < (int)windowCap; ++i)
{
if (i < bufCap)
{
int readIdx = i + pMovementServices[6] - bufCap;
if (i + pMovementServices[6] < bufCap)
readIdx = i + pMovementServices[6];
char sym = *(_BYTE *)(readIdx + *((_QWORD *)pMovementServices + 1));
if (sym != 0)
{
totalValid++;
if (sym == 2) totalOverlaps++;
else if (sym == 1) curSequenceSuccesses++;
}
}
// Sliding window update
if (i >= seqLen && (i - seqLen) >= 0)
{
if ((i - seqLen) < bufCap)
{
int popIdx = i + pMovementServices[6] - seqLen - bufCap;
if (i + pMovementServices[6] - seqLen < bufCap)
popIdx = i + pMovementServices[6] - seqLen;
if (*(_BYTE *)(popIdx + *((_QWORD *)pMovementServices + 1)) == 1)
curSequenceSuccesses--;
}
}
if (maxSequenceSuccesses < curSequenceSuccesses)
maxSequenceSuccesses = curSequenceSuccesses;
}
int minAttempts = *GetConVarInt(&unk_181DDBD20, &qword_181DDBD28);
float dynamicLimit = 0.0f;
if (totalValid >= minAttempts)
{
int minSuccessThreshold = *GetConVarInt(&unk_181DDBD50, &qword_181DDBD58);
if (maxSequenceSuccesses >= minSuccessThreshold)
{
float successRatio = 0.0f;
if (seqLen > minSuccessThreshold)
{
successRatio = (float)(maxSequenceSuccesses - minSuccessThreshold) /
(float)(seqLen - minSuccessThreshold);
}
float lowerPct = *GetConVarFloat(&unk_181DDBD70, &qword_181DDBD78); // Default 20.0%
float upperPct = *GetConVarFloat(&unk_181DDBD60, &qword_181DDBD68); // Default 5.0%
dynamicLimit = (lowerPct - upperPct) * successRatio + upperPct;
}
}
float observedOverlapPct = ((float)totalOverlaps / (float)totalValid) * 100.0f;
// Violation Condition: Dynamic limit exceeds observed overlap ratio
if (dynamicLimit > observedOverlapPct)
{
sub_1801ECEA0(pMovementServices, 0); // Flush buffer state
bool bEnableKick = *GetConVarByte(&unk_181DDBD90, &qword_181DDBD98);
if (bEnableKick)
{
__int64 pEngine = qword_182012050;
void (__fastcall *pfnKickClient)(__int64, unsigned int, _QWORD, __int64) =
*(void (__fastcall **)(__int64, unsigned int, _QWORD, __int64))(*(_QWORD *)pEngine + 768i64);
int slotIdx = -1;
sub_181265470(pPlayerController, &slotIdx);
// Disconnect Code 162: NETWORK_DISCONNECT_KICKED_INPUTAUTOMATION
pfnKickClient(pEngine, (unsigned int)(slotIdx - 1), 0, 162);
}
}
}
设 $\mathbf{S} = {s_1, s_2, \dots, s_N}$ 表示大小为 $N$ 的历史缓冲区中的事件符号序列。参数 $S_{\text{max}}$ 表示长度为 $L$ 的任何连续子序列中代码 $1$ 符号($s_i = 1$)的最大数量:
$$S_{\text{max}} = \max_{0 \le j \le N - L} \sum_{i=j}^{j+L-1} \mathbb{I}(s_i = 1)$$
自适应阈值函数 $T(S_{\text{max}})$ 以 $S_{\text{max}} \ge S_{\text{thresh}}$ 为条件:
$$T(S_{\text{max}}) = \begin{cases} \theta_{\text{upper}} + (\theta_{\text{lower}} - \theta_{\text{upper}}) \cdot \frac{S_{\text{max}} - S_{\text{thresh}}}{L - S_{\text{thresh}}} & \text{if } S_{\text{max}} \ge S_{\text{thresh}} \ 0 & \text{if } S_{\text{max}} < S_{\text{thresh}} \end{cases}$$
其中默认配置值定义如下:
sv_auto_cstrafe_sequence_length)sv_auto_cstrafe_success_threshold)sv_auto_cstrafe_lower_overlap_pct_threshold)sv_auto_cstrafe_upper_overlap_pct_threshold)当且仅当 $T(S_{\text{max}}) > P_{\text{overlap}}$ 时声明违规,其中 $P_{\text{overlap}}$ 是代码 $2$ 事件的样本比率:
$$P_{\text{overlap}} = \frac{100}{N} \sum_{i=1}^{N} \mathbb{I}(s_i = 2)$$
对于任何相位偏移 $\Delta t_{\text{trans}}$ 被约束为 $k = \text{round}(\Delta t_{\text{trans}} / \tau_{\text{tick}}) \ge 1$ 的输入流:
pfnKickClient 的评估路径保持未执行状态。sub_180A7EDB0)server.dll(Win64 正式版)0x1A7EDB0void* __fastcall sub_180A7EDB0(__int64 pMovementServices, __int64 pMoveData)sub_180A7EDB0)void *__fastcall sub_180A7EDB0(__int64 pMovementServices, __int64 pMoveData)
{
__int64 pPawn = *(_QWORD *)(pMovementServices + 8);
sub_180C28550(pPawn, 2i64);
if (!*(_QWORD *)(pPawn + 56))
nullsub_908(pPawn);
// Collision hull & ground contact evaluation
(*(void (__fastcall **)(_QWORD))(**(_QWORD **)(pPawn + 56) + 1624i64))(*(_QWORD *)(pPawn + 56));
sub_1803CBD30(*(_QWORD *)(pPawn + 56));
sub_1803CD4C0(*(_QWORD *)(pPawn + 56), v14);
if (dword_181FDB358)
--dword_181FDB358;
// Check IN_JUMP bit (0x2) in command button mask
if ((*(_BYTE *)(pPawn + 88) & 2) != 0 || sub_180C28550(pPawn, 2i64))
{
_BYTE *pPenaltyActive = (_BYTE *)(pMovementServices + 16);
if (*pPenaltyActive)
{
float curTime = *((float *)off_181C9F350 + 12);
float lastJumpTime = *(float *)(pMovementServices + 20);
float penaltyInterval = sub_18029CE70(&unk_181FDB4D8, 0xFFFFFFFFi64); // sv_jump_spam_penalty_time
// Interval evaluation: curTime > lastJumpTime + penaltyInterval
if (curTime > (penaltyInterval + lastJumpTime))
{
if (*pPenaltyActive)
{
sub_180A99DE0(pMovementServices + 16, 0xFFFFFFFFi64, 0xFFFFFFFFi64);
*pPenaltyActive = 0; // Clear penalty state
}
// Apply jump impulse
sub_180A7F550(pMovementServices, pMoveData);
if (!*pPenaltyActive)
{
sub_180A99DE0(pPenaltyActive, 0xFFFFFFFFi64, 0xFFFFFFFFi64);
*pPenaltyActive = 1; // Engage penalty state
}
if (sub_180C28550(*(_QWORD *)(pMovementServices + 8), 2i64))
{
// Store jump timestamp: lastJumpTime = curTime - frameTime
*(float *)(pMovementServices + 20) = *((float *)off_181C9F350 + 12) -
*((float *)off_181C9F350 + 13);
}
return pMovementServices;
}
}
bool bDebugSpam = *GetConVarByte(&unk_181FDB640, &qword_181FDB648);
if (!bDebugSpam)
{
// Apply jump impulse when penalty debug is disabled
sub_180A7F550(pMovementServices, pMoveData);
if (!*pPenaltyActive)
{
sub_180A99DE0(pPenaltyActive, 0xFFFFFFFFi64, 0xFFFFFFFFi64);
*pPenaltyActive = 1;
}
*(float *)(pMovementServices + 20) = *((float *)off_181C9F350 + 12) -
*((float *)off_181C9F350 + 13);
}
}
return pMovementServices;
}
设 $t_n$ 表示第 $n$ 次跳跃请求的执行时间戳,$\tau_{\text{penalty}}$ 表示 sv_jump_spam_penalty_time 的值(通常为 $1$ tick,即 $\approx 15.625\text{ ms}$)。惩罚标志 $P \in {0, 1}$ 和冲量激活 $I \in {0, 1}$ 的状态转换定义如下:
$$I_n = \begin{cases} 1 & \text{if } P_{n-1} = 0 \text{ 或 } t_n - t_{n-1} > \tau_{\text{penalty}} \ 0 & \text{if } P_{n-1} = 1 \text{ 且 } t_n - t_{n-1} \le \tau_{\text{penalty}} \end{cases}$$
FL_ONGROUND($P_{\text{ground}} = 1$)时发出,则请求间持续时间对应于弹道飞行时间 $T_{\text{flight}}$:$$t_n - t_{n-1} = T_{\text{flight}} \ge \frac{2 \cdot v_z}{g} \approx 300\text{--}500\text{ ms}$$
由于 $T_{\text{flight}} \gg \tau_{\text{penalty}}$,谓词 $t_n - t_{n-1} > \tau_{\text{penalty}}$ 对所有 $n$ 无条件成立,从而阻止抑制路径的激活。
| 组件 | 目标符号 | 当前原语 | 分析弱点 | 建议修复方案 |
|---|---|---|---|---|
| 输入自动化 | sub_1801C6B30 | 二元分类($k=0$ 对比 $k \ge 1$)+ 阈值化 | 固定相位偏移($k=1$)导致 $S_{\text{max}} = 0$ 恒成立 | 评估所有 $k$ 上的样本方差 $\sigma^2(\Delta t)$;无论 bin 索引如何,标记低方差分布 |
| 跳跃速率限制 | sub_180A7EDB0 | 标量时间增量比较($t_n - t_{n-1} > \tau$) | 在空中相位延迟下保持不变($T_{\text{flight}} \gg \tau$) | 评估速度向量连续性和地面接触持续时间($\Delta t_{\text{contact}}$) |