
Análisis estático de ingeniería inversa de las heurísticas de entrada de movimiento en el motor Source 2, identificando casos límite estructurales en la automatización de entradas y las rutinas de limitación de velocidad de salto.
server.dll)Este documento presenta un análisis estático de ingeniería inversa de dos rutinas de verificación de movimiento del lado del cliente implementadas en el motor de Counter-Strike 2 (server.dll): el evaluador de automatización de entrada / SOCD (sub_1801C6B30, designado CVE-2026-1801C / QUANTUM-SHIFT) y el limitador de tasa de solicitudes de salto (sub_180A7EDB0, designado CVE-2026-180A7 / BAL-JUMP). Mediante análisis de desensamblado y modelado matemático formal de las máquinas de estado subyacentes, demostramos casos límite estructurales en ambas heurísticas. Específicamente, documentamos cómo la cuantización temporal discreta en sub_1801C6B30 crea un límite de clasificación invariante bajo desplazamientos de fase fijos de un solo tick, y cómo depende de una métrica de intervalo univariante ($\Delta t$) que permanece invariante bajo disparadores de impulso único bloqueados por fase.
sub_180A7EDB0sub_1801C6B30)server.dll (Compilación Minorista Win64)0x1C6B30void __fastcall sub_1801C6B30(int *pMovementServices, __int64 pPlayerController, __int64 pUserCmdPB)La rutina evalúa eventos de transición de botones de movimiento (+moveleft / +moveright) enviados a través de payloads de cliente CBaseUserCmdPB. La ejecución sigue un pipeline condicional:
sv_auto_cstrafe_attempt_window, por defecto 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]);
// Filtrado: Umbral mínimo de velocidad
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;
// Asignación de Símbolo
char symbolCode;
if (roundedTicks == 0)
symbolCode = 1; // Éxito / 0 ticks perfecto
else
symbolCode = bIsOverlap ? 2 : 3; // Solapamiento o Sub-solapamiento
// Obtener punteros de configuración ConVar
unsigned int windowCap = *GetConVarUInt(&unk_181DDBD30, &qword_181DDBD38);
if (windowCap - 1 > 999)
return;
sub_1801ECEA0(pMovementServices, windowCap);
// Añadir símbolo al buffer circular
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;
}
}
// Actualizar histogramas de frecuencia de ticks discretos
if (bIsOverlap)
pMovementServices[roundedTicks + 7]++;
else
pMovementServices[roundedTicks + 23]++;
int seqLen = *GetConVarInt(&unk_181DDBD40, &qword_181DDBD48);
if ((unsigned int)(seqLen - 1) > 999)
return;
// Evaluar métricas de secuencia de ventana deslizante
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++;
}
}
// Actualización de ventana deslizante
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); // Por defecto 20.0%
float upperPct = *GetConVarFloat(&unk_181DDBD60, &qword_181DDBD68); // Por defecto 5.0%
dynamicLimit = (lowerPct - upperPct) * successRatio + upperPct;
}
}
float observedOverlapPct = ((float)totalOverlaps / (float)totalValid) * 100.0f;
// Condición de Violación: El límite dinámico supera la proporción de solapamiento observada
if (dynamicLimit > observedOverlapPct)
{
sub_1801ECEA0(pMovementServices, 0); // Vaciar estado del buffer
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);
// Código de Desconexión 162: NETWORK_DISCONNECT_KICKED_INPUTAUTOMATION
pfnKickClient(pEngine, (unsigned int)(slotIdx - 1), 0, 162);
}
}
}
Sea $\mathbf{S} = {s_1, s_2, \dots, s_N}$ la secuencia de símbolos de eventos en el buffer de historial de tamaño $N$. El parámetro $S_{\text{max}}$ denota el número máximo de símbolos de código $1$ ($s_i = 1$) dentro de cualquier sub-secuencia contigua de longitud $L$:
$$S_{\text{max}} = \max_{0 \le j \le N - L} \sum_{i=j}^{j+L-1} \mathbb{I}(s_i = 1)$$
La función de umbral adaptativo $T(S_{\text{max}})$ está condicionada a $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{si } S_{\text{max}} \ge S_{\text{thresh}} \ 0 & \text{si } S_{\text{max}} < S_{\text{thresh}} \end{cases}$$
Donde los valores de configuración por defecto se definen como:
sv_auto_cstrafe_sequence_length)sv_auto_cstrafe_success_threshold)sv_auto_cstrafe_lower_overlap_pct_threshold)sv_auto_cstrafe_upper_overlap_pct_threshold)Se declara una violación si y solo si $T(S_{\text{max}}) > P_{\text{overlap}}$, donde $P_{\text{overlap}}$ es la proporción muestral de eventos de código $2$:
$$P_{\text{overlap}} = \frac{100}{N} \sum_{i=1}^{N} \mathbb{I}(s_i = 2)$$
Para cualquier flujo de entrada donde el desplazamiento de fase $\Delta t_{\text{trans}}$ esté restringido de modo que $k = \text{round}(\Delta t_{\text{trans}} / \tau_{\text{tick}}) \ge 1$:
pfnKickClient permanece sin ejecutarse.sub_180A7EDB0)server.dll (Compilación Minorista 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);
// Evaluación de colisión de casco y contacto con el suelo
(*(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;
// Comprobar bit IN_JUMP (0x2) en la máscara de botones de comando
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
// Evaluación de intervalo: curTime > lastJumpTime + penaltyInterval
if (curTime > (penaltyInterval + lastJumpTime))
{
if (*pPenaltyActive)
{
sub_180A99DE0(pMovementServices + 16, 0xFFFFFFFFi64, 0xFFFFFFFFi64);
*pPenaltyActive = 0; // Limpiar estado de penalización
}
// Aplicar impulso de salto
sub_180A7F550(pMovementServices, pMoveData);
if (!*pPenaltyActive)
{
sub_180A99DE0(pPenaltyActive, 0xFFFFFFFFi64, 0xFFFFFFFFi64);
*pPenaltyActive = 1; // Activar estado de penalización
}
if (sub_180C28550(*(_QWORD *)(pMovementServices + 8), 2i64))
{
// Almacenar marca de tiempo de salto: 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)
{
// Aplicar impulso de salto cuando la depuración de penalización está deshabilitada
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;
}
Sea $t_n$ la marca de tiempo de ejecución de la $n$-ésima solicitud de salto, y $\tau_{\text{penalty}}$ el valor de sv_jump_spam_penalty_time (típicamente $1$ tick, o $\approx 15.625\text{ ms}$). La transición de estado para el indicador de penalización $P \in {0, 1}$ y la activación de impulso $I \in {0, 1}$ se define por:
$$I_n = \begin{cases} 1 & \text{si } P_{n-1} = 0 \text{ o } t_n - t_{n-1} > \tau_{\text{penalty}} \ 0 & \text{si } P_{n-1} = 1 \text{ y } t_n - t_{n-1} \le \tau_{\text{penalty}} \end{cases}$$
FL_ONGROUND ($P_{\text{ground}} = 1$), la duración entre solicitudes corresponde a la duración del vuelo balístico $T_{\text{flight}}$:$$t_n - t_{n-1} = T_{\text{flight}} \ge \frac{2 \cdot v_z}{g} \approx 300\text{--}500\text{ ms}$$
Debido a que $T_{\text{flight}} \gg \tau_{\text{penalty}}$, el predicado $t_n - t_{n-1} > \tau_{\text{penalty}}$ se cumple incondicionalmente para todo $n$, impidiendo la activación de la ruta de supresión.
| Componente | Símbolo Objetivo | Primitiva Actual | Debilidad Analítica | Remediación Propuesta |
|---|---|---|---|---|
| Automatización de Entrada | sub_1801C6B30 | Clasificación binaria ($k=0$ vs $k \ge 1$) + umbralización | Desplazamiento de fase fijo ($k=1$) produce $S_{\text{max}} = 0$ de manera idéntica | Evaluar la varianza muestral $\sigma^2(\Delta t)$ en todos los $k$; marcar distribuciones de baja varianza independientemente del índice de bin |
| Limitación de Tasa de Salto | sub_180A7EDB0 | Comparación escalar de delta temporal ($t_n - t_{n-1} > \tau$) | Invariante bajo retrasos de fase en el aire ($T_{\text{flight}} \gg \tau$) | Evaluar la continuidad del vector de velocidad y la duración del contacto con el suelo ($\Delta t_{\text{contact}}$) |