Add server-side anti-cheat evidence scoring
This commit is contained in:
@@ -801,6 +801,13 @@ bool IS_SPEED_HACK(LPCHARACTER ch, LPCHARACTER victim, DWORD current_time)
|
||||
if (current_time - ch->m_kAttackLog.dwTime < GET_ATTACK_SPEED(ch))
|
||||
{
|
||||
INCREASE_SPEED_HACK_COUNT(ch);
|
||||
char szDetail[128];
|
||||
snprintf(szDetail, sizeof(szDetail), "delta=%u limit=%u victim=%u count=%d",
|
||||
current_time - ch->m_kAttackLog.dwTime,
|
||||
GET_ATTACK_SPEED(ch),
|
||||
victim->GetVID(),
|
||||
ch->m_speed_hack_count);
|
||||
ch->RecordAntiCheatViolation("ATTACK_SPEED", 6, szDetail);
|
||||
|
||||
if (test_server)
|
||||
{
|
||||
@@ -830,6 +837,13 @@ bool IS_SPEED_HACK(LPCHARACTER ch, LPCHARACTER victim, DWORD current_time)
|
||||
if (current_time - victim->m_AttackedLog.dwAttackedTime < GET_ATTACK_SPEED(ch))
|
||||
{
|
||||
INCREASE_SPEED_HACK_COUNT(ch);
|
||||
char szDetail[128];
|
||||
snprintf(szDetail, sizeof(szDetail), "delta=%u limit=%u victim=%u count=%d",
|
||||
current_time - victim->m_AttackedLog.dwAttackedTime,
|
||||
GET_ATTACK_SPEED(ch),
|
||||
victim->GetVID(),
|
||||
ch->m_speed_hack_count);
|
||||
ch->RecordAntiCheatViolation("ATTACK_SPEED", 6, szDetail);
|
||||
|
||||
if (test_server)
|
||||
{
|
||||
@@ -854,4 +868,3 @@ bool IS_SPEED_HACK(LPCHARACTER ch, LPCHARACTER victim, DWORD current_time)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -347,6 +347,9 @@ void CHARACTER::Initialize()
|
||||
m_dwLastComboTime = 0;
|
||||
m_bComboIndex = 0;
|
||||
m_iComboHackCount = 0;
|
||||
m_iAntiCheatScore = 0;
|
||||
m_dwLastAntiCheatScoreTime = 0;
|
||||
m_dwLastAntiCheatPersistTime = 0;
|
||||
m_dwSkipComboAttackByTime = 0;
|
||||
|
||||
m_dwMountTime = 0;
|
||||
@@ -7238,6 +7241,68 @@ void CHARACTER::ResetComboHackCount()
|
||||
m_iComboHackCount = 0;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr int kAntiCheatMaxScore = 1000;
|
||||
constexpr int kAntiCheatDisconnectScore = 100;
|
||||
constexpr DWORD kAntiCheatDecayIntervalMs = 15000;
|
||||
constexpr DWORD kAntiCheatPersistIntervalMs = 5000;
|
||||
}
|
||||
|
||||
void CHARACTER::DecayAntiCheatScore(DWORD dwNow)
|
||||
{
|
||||
if (0 == m_dwLastAntiCheatScoreTime)
|
||||
{
|
||||
m_dwLastAntiCheatScoreTime = dwNow;
|
||||
return;
|
||||
}
|
||||
|
||||
const DWORD dwElapsed = dwNow - m_dwLastAntiCheatScoreTime;
|
||||
if (dwElapsed < kAntiCheatDecayIntervalMs)
|
||||
return;
|
||||
|
||||
const int iDecaySteps = dwElapsed / kAntiCheatDecayIntervalMs;
|
||||
m_iAntiCheatScore = MAX(0, m_iAntiCheatScore - iDecaySteps);
|
||||
m_dwLastAntiCheatScoreTime += iDecaySteps * kAntiCheatDecayIntervalMs;
|
||||
}
|
||||
|
||||
void CHARACTER::RecordAntiCheatViolation(const char* category, int score, const char* detail, bool forcePersist)
|
||||
{
|
||||
const DWORD dwNow = get_dword_time();
|
||||
DecayAntiCheatScore(dwNow);
|
||||
|
||||
if (score <= 0)
|
||||
score = 1;
|
||||
|
||||
m_iAntiCheatScore = MIN(kAntiCheatMaxScore, m_iAntiCheatScore + score);
|
||||
|
||||
char szReason[512];
|
||||
snprintf(szReason,
|
||||
sizeof(szReason),
|
||||
"ANTI_CHEAT[%s] score=%d map=%ld pos=(%ld,%ld) detail=%s",
|
||||
category ? category : "UNKNOWN",
|
||||
m_iAntiCheatScore,
|
||||
static_cast<long>(GetMapIndex()),
|
||||
static_cast<long>(GetX()),
|
||||
static_cast<long>(GetY()),
|
||||
detail ? detail : "-");
|
||||
|
||||
sys_log(0, "%s pid=%u name=%s", szReason, GetPlayerID(), GetName());
|
||||
|
||||
const bool shouldPersist = forcePersist || (GetDesc() && dwNow - m_dwLastAntiCheatPersistTime >= kAntiCheatPersistIntervalMs);
|
||||
if (shouldPersist && GetDesc())
|
||||
{
|
||||
LogManager::instance().HackLog(szReason, this);
|
||||
m_dwLastAntiCheatPersistTime = dwNow;
|
||||
}
|
||||
|
||||
if (GetDesc() && m_iAntiCheatScore >= kAntiCheatDisconnectScore)
|
||||
{
|
||||
if (GetDesc()->DelayedDisconnect(number(3, 8)))
|
||||
sys_log(0, "ANTI_CHEAT_DISCONNECT: %s score=%d", GetName(), m_iAntiCheatScore);
|
||||
}
|
||||
}
|
||||
|
||||
void CHARACTER::SkipComboAttackByTime(int interval)
|
||||
{
|
||||
m_dwSkipComboAttackByTime = get_dword_time() + interval;
|
||||
|
||||
@@ -1304,14 +1304,20 @@ class CHARACTER : public CEntity, public CFSM, public CHorseRider
|
||||
void ResetComboHackCount();
|
||||
void SkipComboAttackByTime(int interval);
|
||||
DWORD GetSkipComboAttackByTime() const;
|
||||
void RecordAntiCheatViolation(const char* category, int score, const char* detail = nullptr, bool forcePersist = false);
|
||||
int GetAntiCheatScore() const { return m_iAntiCheatScore; }
|
||||
|
||||
protected:
|
||||
void DecayAntiCheatScore(DWORD dwNow);
|
||||
BYTE m_bComboSequence;
|
||||
DWORD m_dwLastComboTime;
|
||||
int m_iValidComboInterval;
|
||||
BYTE m_bComboIndex;
|
||||
int m_iComboHackCount;
|
||||
DWORD m_dwSkipComboAttackByTime;
|
||||
int m_iAntiCheatScore;
|
||||
DWORD m_dwLastAntiCheatScoreTime;
|
||||
DWORD m_dwLastAntiCheatPersistTime;
|
||||
|
||||
protected:
|
||||
void UpdateAggrPointEx(LPCHARACTER ch, EDamageType type, int dam, TBattleInfo & info);
|
||||
|
||||
@@ -254,6 +254,9 @@ bool CHARACTER::Attack(LPCHARACTER pkVictim, BYTE bType)
|
||||
{
|
||||
if (dwCurrentTime - m_dwLastSkillTime > 1500)
|
||||
{
|
||||
char szDetail[128];
|
||||
snprintf(szDetail, sizeof(szDetail), "skill=%u delta=%u victim=%s", bType, dwCurrentTime - m_dwLastSkillTime, pkVictim ? pkVictim->GetName() : "-");
|
||||
RecordAntiCheatViolation("SKILL_TERM", 10, szDetail, true);
|
||||
sys_log(1, "HACK: Too long skill using term. Name(%s) PID(%u) delta(%u)",
|
||||
GetName(), GetPlayerID(), (dwCurrentTime - m_dwLastSkillTime));
|
||||
return false;
|
||||
@@ -3755,4 +3758,3 @@ void CHARACTER::ChangeVictimByAggro(int iNewAggro, LPCHARACTER pNewVictim)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1539,7 +1539,12 @@ bool CheckComboHack(LPCHARACTER ch, BYTE bArg, DWORD dwTime, bool CheckSpeedHack
|
||||
{
|
||||
// 말에 타거나 내렸을 때 1.5초간 공격은 핵으로 간주하지 않되 공격력은 없게 하는 처리
|
||||
if (get_dword_time() - ch->GetLastMountTime() > 1500)
|
||||
{
|
||||
char szDetail[128];
|
||||
snprintf(szDetail, sizeof(szDetail), "arg=%u interval=%d valid=%d scalar=%d", bArg, ComboInterval, ch->GetValidComboInterval(), HackScalar);
|
||||
ch->RecordAntiCheatViolation("COMBO", MIN(20, 2 + HackScalar), szDetail, HackScalar >= 5);
|
||||
ch->IncreaseComboHackCount(1 + HackScalar);
|
||||
}
|
||||
|
||||
ch->SkipComboAttackByTime(ch->GetValidComboInterval());
|
||||
}
|
||||
@@ -1556,6 +1561,9 @@ void CInputMain::Move(LPCHARACTER ch, const char * data)
|
||||
|
||||
if (pinfo->bFunc >= FUNC_MAX_NUM && !(pinfo->bFunc & 0x80))
|
||||
{
|
||||
char szDetail[64];
|
||||
snprintf(szDetail, sizeof(szDetail), "func=%u arg=%u", pinfo->bFunc, pinfo->bArg);
|
||||
ch->RecordAntiCheatViolation("MOVE_TYPE", 12, szDetail, true);
|
||||
sys_err("invalid move type: %s", ch->GetName());
|
||||
return;
|
||||
}
|
||||
@@ -1580,13 +1588,17 @@ void CInputMain::Move(LPCHARACTER ch, const char * data)
|
||||
|
||||
if (((false == ch->IsRiding() && fDist > 25) || fDist > 40) && OXEVENT_MAP_INDEX != ch->GetMapIndex())
|
||||
{
|
||||
if( false == LC_IsEurope() )
|
||||
{
|
||||
const PIXEL_POSITION & warpPos = ch->GetWarpPosition();
|
||||
|
||||
if (warpPos.x == 0 && warpPos.y == 0)
|
||||
LogManager::instance().HackLog("Teleport", ch); // 부정확할 수 있음
|
||||
}
|
||||
char szDetail[160];
|
||||
snprintf(szDetail,
|
||||
sizeof(szDetail),
|
||||
"dist=%.1f riding=%d cur=(%ld,%ld) dst=(%ld,%ld)",
|
||||
fDist,
|
||||
ch->IsRiding() ? 1 : 0,
|
||||
static_cast<long>(ch->GetX()),
|
||||
static_cast<long>(ch->GetY()),
|
||||
static_cast<long>(pinfo->lX),
|
||||
static_cast<long>(pinfo->lY));
|
||||
ch->RecordAntiCheatViolation("MOVE_DISTANCE", 15, szDetail, true);
|
||||
|
||||
sys_log(0, "MOVE: %s trying to move too far (dist: %.1fm) Riding(%d)", ch->GetName(), fDist, ch->IsRiding());
|
||||
|
||||
@@ -1659,7 +1671,7 @@ void CInputMain::Move(LPCHARACTER ch, const char * data)
|
||||
|
||||
char szBuf[256];
|
||||
snprintf(szBuf, sizeof(szBuf), "SKILL_HACK: name=%s, job=%d, group=%d, motion=%d", name, job, group, motion);
|
||||
LogManager::instance().HackLog(szBuf, ch->GetDesc()->GetAccountTable().login, ch->GetName(), ch->GetDesc()->GetHostName());
|
||||
ch->RecordAntiCheatViolation("SKILL_MOTION", 40, szBuf, true);
|
||||
sys_log(0, "%s", szBuf);
|
||||
|
||||
if (test_server)
|
||||
@@ -1869,6 +1881,9 @@ int CInputMain::SyncPosition(LPCHARACTER ch, const char * c_pcData, size_t uiByt
|
||||
if( iCount > nCountLimit )
|
||||
{
|
||||
//LogManager::instance().HackLog( "SYNC_POSITION_HACK", ch );
|
||||
char szDetail[64];
|
||||
snprintf(szDetail, sizeof(szDetail), "count=%d limit=%d", iCount, nCountLimit);
|
||||
ch->RecordAntiCheatViolation("SYNC_POSITION_COUNT", 3, szDetail);
|
||||
sys_err( "Too many SyncPosition Count(%d) from Name(%s)", iCount, ch->GetName() );
|
||||
//ch->GetDesc()->SetPhase(PHASE_CLOSE);
|
||||
//return -1;
|
||||
@@ -1918,12 +1933,29 @@ int CInputMain::SyncPosition(LPCHARACTER ch, const char * c_pcData, size_t uiByt
|
||||
if (ch->GetSyncHackCount() < g_iSyncHackLimitCount)
|
||||
{
|
||||
ch->SetSyncHackCount(ch->GetSyncHackCount() + 1);
|
||||
char szDetail[160];
|
||||
snprintf(szDetail,
|
||||
sizeof(szDetail),
|
||||
"owner_dist=%.1f limit=%.1f victim=%s count=%d",
|
||||
fDistWithSyncOwner,
|
||||
fLimitDistWithSyncOwner,
|
||||
victim->GetName(),
|
||||
ch->GetSyncHackCount());
|
||||
ch->RecordAntiCheatViolation("SYNC_OWNER_DISTANCE", 8, szDetail);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogManager::instance().HackLog( "SYNC_POSITION_HACK", ch );
|
||||
|
||||
char szDetail[192];
|
||||
snprintf(szDetail,
|
||||
sizeof(szDetail),
|
||||
"owner_dist=%.1f limit=%.1f victim=%s sync=(%ld,%ld)",
|
||||
fDistWithSyncOwner,
|
||||
fLimitDistWithSyncOwner,
|
||||
victim->GetName(),
|
||||
static_cast<long>(e->lX),
|
||||
static_cast<long>(e->lY));
|
||||
ch->RecordAntiCheatViolation("SYNC_OWNER_DISTANCE", 35, szDetail, true);
|
||||
sys_err( "Too far SyncPosition DistanceWithSyncOwner(%f)(%s) from Name(%s) CH(%d,%d) VICTIM(%d,%d) SYNC(%d,%d)",
|
||||
fDistWithSyncOwner, victim->GetName(), ch->GetName(), ch->GetX(), ch->GetY(), victim->GetX(), victim->GetY(),
|
||||
e->lX, e->lY );
|
||||
@@ -1947,12 +1979,29 @@ int CInputMain::SyncPosition(LPCHARACTER ch, const char * c_pcData, size_t uiByt
|
||||
if (ch->GetSyncHackCount() < g_iSyncHackLimitCount)
|
||||
{
|
||||
ch->SetSyncHackCount(ch->GetSyncHackCount() + 1);
|
||||
char szDetail[160];
|
||||
snprintf(szDetail,
|
||||
sizeof(szDetail),
|
||||
"interval_ms=%ld limit_us=%ld victim=%s count=%d",
|
||||
tvDiff->tv_sec * 1000 + tvDiff->tv_usec / 1000,
|
||||
static_cast<long>(g_lValidSyncInterval),
|
||||
victim->GetName(),
|
||||
ch->GetSyncHackCount());
|
||||
ch->RecordAntiCheatViolation("SYNC_INTERVAL", 8, szDetail);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogManager::instance().HackLog( "SYNC_POSITION_HACK", ch );
|
||||
|
||||
char szDetail[192];
|
||||
snprintf(szDetail,
|
||||
sizeof(szDetail),
|
||||
"interval_ms=%ld limit_us=%ld victim=%s sync=(%ld,%ld)",
|
||||
tvDiff->tv_sec * 1000 + tvDiff->tv_usec / 1000,
|
||||
static_cast<long>(g_lValidSyncInterval),
|
||||
victim->GetName(),
|
||||
static_cast<long>(e->lX),
|
||||
static_cast<long>(e->lY));
|
||||
ch->RecordAntiCheatViolation("SYNC_INTERVAL", 35, szDetail, true);
|
||||
sys_err( "Too often SyncPosition Interval(%ldms)(%s) from Name(%s) VICTIM(%d,%d) SYNC(%d,%d)",
|
||||
tvDiff->tv_sec * 1000 + tvDiff->tv_usec / 1000, victim->GetName(), ch->GetName(), victim->GetX(), victim->GetY(),
|
||||
e->lX, e->lY );
|
||||
@@ -1964,8 +2013,17 @@ int CInputMain::SyncPosition(LPCHARACTER ch, const char * c_pcData, size_t uiByt
|
||||
}
|
||||
else if( fDist > 25.0f )
|
||||
{
|
||||
LogManager::instance().HackLog( "SYNC_POSITION_HACK", ch );
|
||||
|
||||
char szDetail[192];
|
||||
snprintf(szDetail,
|
||||
sizeof(szDetail),
|
||||
"dist=%.1f victim=%s cur=(%ld,%ld) sync=(%ld,%ld)",
|
||||
fDist,
|
||||
victim->GetName(),
|
||||
static_cast<long>(victim->GetX()),
|
||||
static_cast<long>(victim->GetY()),
|
||||
static_cast<long>(e->lX),
|
||||
static_cast<long>(e->lY));
|
||||
ch->RecordAntiCheatViolation("SYNC_DISTANCE", 30, szDetail, true);
|
||||
sys_err( "Too far SyncPosition Distance(%f)(%s) from Name(%s) CH(%d,%d) VICTIM(%d,%d) SYNC(%d,%d)",
|
||||
fDist, victim->GetName(), ch->GetName(), ch->GetX(), ch->GetY(), victim->GetX(), victim->GetY(),
|
||||
e->lX, e->lY );
|
||||
@@ -3492,4 +3550,3 @@ int CInputDead::Analyze(LPDESC d, uint16_t wHeader, const char * c_pData)
|
||||
|
||||
return (this->*(it->second.handler))(d, c_pData);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user