diff --git a/extensions/sourcetvmanager.ext.2.tf2.dll b/extensions/sourcetvmanager.ext.2.tf2.dll index b154204b..4a47e594 100755 Binary files a/extensions/sourcetvmanager.ext.2.tf2.dll and b/extensions/sourcetvmanager.ext.2.tf2.dll differ diff --git a/extensions/sourcetvmanager.ext.2.tf2.so b/extensions/sourcetvmanager.ext.2.tf2.so index 1eeeb092..71edfa43 100755 Binary files a/extensions/sourcetvmanager.ext.2.tf2.so and b/extensions/sourcetvmanager.ext.2.tf2.so differ diff --git a/gamedata/sourcetvmanager.games.txt b/gamedata/sourcetvmanager.games.txt index 72456271..91a14bb4 100755 --- a/gamedata/sourcetvmanager.games.txt +++ b/gamedata/sourcetvmanager.games.txt @@ -756,35 +756,42 @@ "Offsets" { + // String: "NetMsg" "CNetChan::SendNetMsg" { "windows" "41" "linux" "42" } - + + // cvar sv_maxrate->ClampClientRate->CBaseClient::SetRate (located below) + // CBaseClient::SetRate "CBaseClient::m_NetChannel" { - "windows" "224" - "linux" "228" + "windows" "228" + "linux" "232" } "CGameClient::ClientPrintf" { "linux" "26" } - + + // String: "%s unpaused the game\n" + // The string above is passed to this function "CBaseServer::BroadcastPrintf" { "windows" "36" "linux" "37" } + // Siring: "Valve_Reject_Background_Map" "CHLTVServer::ConnectClient" { "windows" "50" "linux" "51" } + // String: "RejectConnection: %s - %s" "CHLTVServer::RejectConnection" { "windows" "48" @@ -796,13 +803,15 @@ "windows" "54" "linux" "55" } - + + // String: "UpdatePlayers" "CBaseClient::ActivatePlayer" { "windows" "15" "linux" "61" } - + + // String: "server_pre_shutdown" "CBaseClient::FireGameEvent" { "windows" "1" @@ -814,29 +823,43 @@ "linux" "15" } + // String: "Server shutting down" "CHLTVServer::Shutdown" { "windows" "42" "linux" "43" } + // Library - server.dll + // String: "hltv_fixed" + // we will find the function - CHLTVDirector::StartFixedCameraShot, + // required offset inside "CHLTVDirector::m_iPVSEntity" { - "windows" "16" - "linux" "16" + "windows" "32" + "linux" "32" } - + + // Library - server.dll + // String: "hltv_fixed" + // we will find the function - CHLTVDirector::StartFixedCameraShot, + // required offset inside "CHLTVDirector::m_vPVSOrigin" { - "windows" "20" - "linux" "20" + "windows" "36" + "linux" "36" } + // Library - server.dll + // String: "Please wait for broadcast to start ...", + // we will find the function - CHLTVDirector::StartDelayMessage, + // required offset inside "CHLTVDirector::m_nNextShotTick" { - "windows" "40" - "linux" "40" + "windows" "56" + "linux" "56" } + "CHLTVDemoRecorder_BaseOffset" { "windows" "0" @@ -850,6 +873,7 @@ "library" "engine" "linux" "@host_client" // ping(CCommand const&) "Client ping times:\n" + // 55 8B EC 51 83 3D ? ? ? ? 01 75 ? "windows" "\x55\x8B\xEC\x51\x83\x3D\x2A\x2A\x2A\x2A\x01\x75\x2A" } @@ -858,23 +882,26 @@ "library" "engine" "linux" "@_ZN11CHLTVServer19BroadcastEventLocalEP10IGameEventb" // "SourceTV broadcast local event: %s\n" - "windows" "\x55\x8B\xEC\x81\xEC\x58\x04\x00\x00\xA1\x2A\x2A\x2A\x2A\x33\xC5\x89\x45\xFC\x53\x56" + // 55 8B EC 81 EC A4 04 00 00 A1 ? ? ? ? 33 C5 89 45 FC D9 EE + "windows" "\x55\x8B\xEC\x81\xEC\xA4\x04\x00\x00\xA1\x2A\x2A\x2A\x2A\x33\xC5\x89\x45\xFC\xD9\xEE" } "CHLTVServer::BroadcastLocalChat" { "library" "engine" "linux" "@_ZN11CHLTVServer18BroadcastLocalChatEPKcS1_" - // "hltv_chat" - "windows" "\x55\x8B\xEC\x81\xEC\x60\x04\x00\x00\xA1\x2A\x2A\x2A\x2A\x33\xC5\x89\x45\xFC\x8B\x45\x0C\x53\x8B\x5D\x08\x56\x57\x8B\xF1" + // "hltv_chat", "CHLTVServer: failed to serialize chat '%s'.\n" + // 55 8B EC 81 EC A8 04 00 00 A1 ? ? ? ? 33 C5 89 45 FC 8B 45 0C + "windows" "\x55\x8B\xEC\x81\xEC\xA8\x04\x00\x00\xA1\x2A\x2A\x2A\x2A\x33\xC5\x89\x45\xFC\x8B\x45\x0C" } "CHLTVServer::Shutdown" { - "library" "engine" - "linux" "@_ZN11CHLTVServer8ShutdownEv" + "library" "engine" + "linux" "@_ZN11CHLTVServer8ShutdownEv" // "SourceTV stop." - "windows" "\x56\x8B\xF1\x8B\x86\x2A\x2A\x2A\x2A\x8B\x50\x2A\x8D\x8E\x2A\x2A\x2A\x2A\xFF\xD2\x8B\x86\x2A\x2A\x2A\x2A\x8B\x50\x2A\x8D\x8E" + // 56 8B F1 8B 86 ? ? ? ? 8B 50 ? 8D 8E ? ? ? ? FF D2 8B 86 ? ? ? ? 8B 50 ? 8D 8E + "windows" "\x56\x8B\xF1\x8B\x86\x2A\x2A\x2A\x2A\x8B\x50\x2A\x8D\x8E\x2A\x2A\x2A\x2A\xFF\xD2\x8B\x86\x2A\x2A\x2A\x2A\x8B\x50\x2A\x8D\x8E" } // StartRecording and StopRecording are virtual, but get called directly in the linux binary.. diff --git a/plugins/stac.smx b/plugins/stac.smx index 9e87563d..746f27ea 100755 Binary files a/plugins/stac.smx and b/plugins/stac.smx differ diff --git a/scripting/stac.sp b/scripting/stac.sp index ea8187ee..1a43e5fb 100755 --- a/scripting/stac.sp +++ b/scripting/stac.sp @@ -55,7 +55,7 @@ #pragma semicolon 1 #pragma newdecls required -#define PLUGIN_VERSION "6.1.3-beta7" +#define PLUGIN_VERSION "6.1.4" #define UPDATE_URL "https://raw.githubusercontent.com/sapphonie/StAC-tf2/master/updatefile.txt" @@ -121,11 +121,10 @@ public void OnPluginStart() } // reg admin commands - // TODO: make these invisible for non admins RegConsoleCmd("sm_stac_checkall", checkAdmin, "Force check all client convars (ALL CLIENTS) for anticheat stuff"); RegConsoleCmd("sm_stac_detections", checkAdmin, "Show all current detections on all connected clients"); RegConsoleCmd("sm_stac_getauth", checkAdmin, "Print StAC's cached auth for a client"); - RegConsoleCmd("sm_stac_livefeed", checkAdmin, "Show live feed (debug info etc) for a client. This gets printed to SourceTV if available."); + RegConsoleCmd("sm_stac_livefeed", checkAdmin, "Show live feed (debug info etc) for a client. This gets printed to SourceTV too, if available."); // steamidRegex = CompileRegex("^STEAM_[0-5]:[0-1]:[0-9]+$"); diff --git a/scripting/stac/stac_client.sp b/scripting/stac/stac_client.sp index d927f296..192b405b 100755 --- a/scripting/stac/stac_client.sp +++ b/scripting/stac/stac_client.sp @@ -44,89 +44,58 @@ public bool OnClientPreConnectEx(const char[] name, char password[255], const ch strcopy(latestIP, sizeof(latestIP), ip); strcopy(latestSteamID, sizeof(latestSteamID), steamID); - if (!stac_prevent_connect_spam.BoolValue) { return true; } - // TODO: does this need to be higher? or lower? or...? - static int threshold = 5; - int connects; - IPBuckets.GetValue(ip, connects); // 0 if not present - connects++; - if (connects >= threshold) - { - rejectReason = "Rate limited."; - - DataPack steamidPlusIP = new DataPack(); - steamidPlusIP.Reset(true); - steamidPlusIP.WriteString(steamID); - steamidPlusIP.WriteString(ip); - steamidPlusIP.WriteString(name); - steamidPlusIP.Reset(false); - - // This is not arbitrary! - RequestFrame(DelayedRateLimitBan, steamidPlusIP); - - return false; - } - IPBuckets.SetValue(ip, connects); - - if (stac_debug.BoolValue) + // DO NOT interfere with lan matches until we have a way to store port from here! + ConVar sv_lan = FindConVar("sv_lan"); + if (sv_lan.BoolValue) { - StacLog("-> connects from ip %s %i", ip, connects); + return true; } - return true; -} + // connects is how many times have they connected recently, it decays by 1 every 5 seconds + // threshold how many times is "too many" + // thresholdEx is at what point we should start punishing & making each connect be worth double/triple/etc to the algorithm + int connects = 0; + static int threshold = 6; + static int thresholdEx = 10; + bool punish = false; + IPBuckets.GetValue(ip, connects); // 0 if not present -void DelayedRateLimitBan(DataPack dp) -{ - dp.Reset(false); - char steamID[MAX_AUTHID_LENGTH]; - char ipAddr[256]; - char playerName[MAX_NAME_LENGTH]; - dp.ReadString(steamID, sizeof(steamID)); - dp.ReadString(ipAddr, sizeof(ipAddr)); - dp.ReadString(playerName, sizeof(playerName)); - dp.Reset(true); - delete dp; - - static int rateLimitBanTime = 60; - - // BanIdentity(steamID, 60, BANFLAG_AUTHID, ""); - // BanIdentity(ip, 60, BANFLAG_IP, ""); - - // THE REASON we are doing this so weirdly, is so that we hook into srcds's built in - // "firewall", basically, where with the default game banning system, - // srcds will ignore packets from banned ips. - // this prevents any clients from spamming, in a way that would otherwise not really be possible, - // without stupid memory hacks that would be overcomplicated anyway since this already exists - - // We're doing sm_addban / sm_banip because SourceBans, at least, will propagate this to other servers as well - // which is what we want, so people don't have to get individually banned on each server on the same network that they try to spam on - - // Probably before un-betaing this, i'm going to try to move this to sm_ban and then addip after? - // I don't really know. it's a tricky race condition here, since we're after OnClientPreConnectEx callback, so they should be rejected, - // but they can rejoin, but regardless we don't actually have a client index yet? - if ( CommandExists("sm_banip") && CommandExists("sm_addban") ) - { - ServerCommand("sm_addban %i %s %s", rateLimitBanTime, steamID, "Rate limited"); - ServerCommand("sm_banip %s %i %s", ipAddr, rateLimitBanTime, "Rate limited"); + // inc by one since we're in this callback + connects++; + + // strong punishment + if (connects > thresholdEx) + { + punish = true; + rejectReason = "Rate limited for retry spam. Please try again in a few minutes."; + // worth double + connects++; } - else + // light punishment + else if (connects >= threshold) { - ServerCommand("addip %i %s", rateLimitBanTime, ipAddr); - ServerCommand("banid %i %s kick", rateLimitBanTime, steamID); + punish = true; + rejectReason = "Rate limited for retry spam. Please try again in a bit."; } + else { /* no punishment, they didn't trip any threshold */ } - char formattedMsg[1024]; - Format(formattedMsg, sizeof(formattedMsg), "Rate limited %s / %s / %s for %i minutes for connect spam", playerName, steamID, ipAddr, rateLimitBanTime); - - StacLog(formattedMsg); + // set our connects to our stupid global var thing + IPBuckets.SetValue(ip, connects); - StacNotify(0, formattedMsg, 1); + if (stac_debug.BoolValue) + { + StacLog("[stac_prevent_connect_spam - OnClientPreConnectEx] %i connects from ip %s", connects, ip); + } + + // this func detour returns true to let them in, and false to prevent them from connecting + // rejectReason is displayed as the reason to their client. + // that's just how it is. + return !punish; } Action LeakIPConnectBucket(Handle timer) @@ -150,14 +119,14 @@ Action LeakIPConnectBucket(Handle timer) if (stac_debug.BoolValue) { - StacLog("(LeakIPConnectBucket) connects from ip %s %i", ip, connects); + StacLog("[stac_prevent_connect_spam - LeakIPConnectBucket] %i connects from ip %s", connects, ip); } if (connects <= 0) { if (stac_debug.BoolValue) { - StacLog("-> connects from ip %s %i [ REMOVING ] ", ip, connects); + StacLog("[stac_prevent_connect_spam - LeakIPConnectBucket] %i connects from ip %s [ REMOVING ] ", connects, ip); } IPBuckets.Remove(ip); @@ -370,8 +339,10 @@ public void OnClientPutInServer(int cl) StacLog("OCPIS steamid = %s", SteamAuthFor[cl]); } - // bail if cvar is set to 0 - if (stac_max_connections_from_ip.IntValue > 0) + ConVar sv_lan = FindConVar("sv_lan"); + + // bail if cvar is set to 0 or if we're in sv_lan 1 + if ( stac_max_connections_from_ip.IntValue > 0 && !(sv_lan.BoolValue) ) { checkIP(cl); } @@ -589,6 +560,7 @@ Action OnAllClientCommands(int cl, const char[] command, int argc) return Plugin_Continue; } +// Runs OnClientPutInServer (which runs on map change too!) and OnClientDisconnect void ClearClBasedVars(int userid) { // get fresh cli id @@ -605,6 +577,7 @@ void ClearClBasedVars(int userid) cmdnumSpikeDetects [cl] = 0; tbotDetects [cl] = -1; invalidUsercmdDetects [cl] = 0; + stacProbingDetects [cl] = 0; // frames since client "did something" // [ client index ][history] diff --git a/scripting/stac/stac_commands.sp b/scripting/stac/stac_commands.sp index 4e24ada5..9818845a 100755 --- a/scripting/stac/stac_commands.sp +++ b/scripting/stac/stac_commands.sp @@ -4,10 +4,13 @@ Action checkAdmin(int callingCl, int args) { - char arg0[32]; - GetCmdArg(0, arg0, sizeof(arg0)); - char arg1[32]; - GetCmdArg(1, arg1, sizeof(arg1)); + // dont realloc since this might be hammered + static char arg0[512]; + static char arg1[512]; + + // clear out whatever might be in there + arg0[0] = 0x0; + arg1[0] = 0x0; if (callingCl != 0) { @@ -17,16 +20,33 @@ Action checkAdmin(int callingCl, int args) { isAdmin = true; } + if (!isAdmin) { - PrintToImportant("{hotpink}[StAC]{white} Client %N attempted to use %s, blocked access." , callingCl, arg0); - StacLogSteam(GetClientUserId(callingCl)); - char fmtmsg[512]; - Format(fmtmsg, sizeof(fmtmsg), "Client %N attempted to use %s, blocked access!", callingCl, arg0); - StacNotify(GetClientUserId(callingCl), fmtmsg); - return Plugin_Handled; + // Detect DOES NOT!!! decrement on a timer, it's just reset every map! + stacProbingDetects[callingCl]++; + if + ( + stacProbingDetects[callingCl] == 1 + || stacProbingDetects[callingCl] == 5 + || stacProbingDetects[callingCl] % 10 == 0 + ) + { + GetCmdArg(0, arg0, sizeof(arg0)); + + PrintToImportant("{hotpink}[StAC]{white} Client %N attempted to use %s, blocked access." , callingCl, arg0); + StacLogSteam(GetClientUserId(callingCl)); + char fmtmsg[512]; + Format(fmtmsg, sizeof(fmtmsg), "Client %N attempted to use %s, blocked access!", callingCl, arg0); + StacNotify(GetClientUserId(callingCl), fmtmsg); + } + // https://github.com/sapphonie/StAC-tf2/pull/189 + // "Plugin_Continue will show "Unknown command" client side." + return Plugin_Continue; } } + GetCmdArg(0, arg0, sizeof(arg0)); + GetCmdArg(1, arg1, sizeof(arg1)); if (StrEqual(arg0, "sm_stac_checkall")) { @@ -93,6 +113,7 @@ void ShowAllDetections(int callingCl) || cmdnumSpikeDetects [cl] > 0 || tbotDetects [cl] > 0 || invalidUsercmdDetects [cl] > 0 + || stacProbingDetects [cl] > 0 ) { PrintToConsole @@ -100,13 +121,14 @@ void ShowAllDetections(int callingCl) callingCl, "\n\ Detections for %L -\ - \n Turn binds %i\ - \n FakeAngs %i\ - \n Aimsnaps %i\ - \n pSilent %i\ - \n Cmdnum spikes %i\ - \n Triggerbots %i\ - \n Invalid Usercmds %i\ + \n Turn binds - %i\ + \n FakeAngs - %i\ + \n Aimsnaps - %i\ + \n pSilent - %i\ + \n Cmdnum spikes - %i\ + \n Possible triggerbot detects - %i\ + \n Invalid Usercmds -%i\ + \n Attempts to see if StAC is running - %i\ \n", cl, turnTimes [cl], @@ -115,7 +137,8 @@ void ShowAllDetections(int callingCl) pSilentDetects [cl], cmdnumSpikeDetects [cl], tbotDetects [cl], - invalidUsercmdDetects [cl] + invalidUsercmdDetects [cl], + stacProbingDetects [cl] ); } } diff --git a/scripting/stac/stac_cvars.sp b/scripting/stac/stac_cvars.sp index 8a219201..3d917e78 100755 --- a/scripting/stac/stac_cvars.sp +++ b/scripting/stac/stac_cvars.sp @@ -370,7 +370,8 @@ void initCvars() ( "stac_prevent_connect_spam", "1", - "[StAC] (BETA DETECTION) use a \"leaky bucket\" algorithm to prevent the same clients from spamming connect requests to your server. temp bans clients for 60 minutes if they hit the limit.\n\ + "[StAC] (BETA DETECTION) use a \"leaky bucket\" algorithm to prevent the same clients from spamming connect requests to your server.\n\ + locks out clients for a bit (a minute or so) after they hit the limit.\n\ (recommended 1)", FCVAR_NONE, true, @@ -379,6 +380,20 @@ void initCvars() 1.0 ); + // + stac_print_to_admin_console = + AutoExecConfig_CreateConVar + ( + "stac_print_to_admin_console", + "1", + "[StAC] print StAC logging messages to the client console of all online admins.\n\ + (recommended 1)", + FCVAR_NONE, + true, + 0.0, + true, + 1.0 + ); initUsercmdCvars(); // actually exec the cfg after initing cvars lol diff --git a/scripting/stac/stac_globals.sp b/scripting/stac/stac_globals.sp index 4ed01dcc..12847faa 100755 --- a/scripting/stac/stac_globals.sp +++ b/scripting/stac/stac_globals.sp @@ -30,6 +30,7 @@ ConVar stac_silent; ConVar stac_max_connections_from_ip; ConVar stac_work_with_sv_cheats; ConVar stac_prevent_connect_spam; +ConVar stac_print_to_admin_console; /***** Server based stuff *****/ @@ -79,6 +80,7 @@ int bhopDetects [TFMAXPLAYERS+1] = {-1, ...}; // set to -1 to ignore int cmdnumSpikeDetects [TFMAXPLAYERS+1]; int tbotDetects [TFMAXPLAYERS+1] = {-1, ...}; int invalidUsercmdDetects [TFMAXPLAYERS+1]; +int stacProbingDetects [TFMAXPLAYERS+1]; // frames since client "did something" // [ client index ][history] diff --git a/scripting/stac/stac_onplayerruncmd.sp b/scripting/stac/stac_onplayerruncmd.sp index 623a6b96..4d87f9b3 100755 --- a/scripting/stac/stac_onplayerruncmd.sp +++ b/scripting/stac/stac_onplayerruncmd.sp @@ -668,6 +668,11 @@ void psilentCheck(int cl) { return; } + // TODO: TEST THIS + // if (!DidRecentlyDoInterestingAction(cl)) + // { + // return; + // } static float pSilentEpsilon = 0.1; // get difference between angles - used for psilent @@ -1070,6 +1075,7 @@ void invalidUsercmdCheck(int cl) StacLogAngles(userid); StacLogCmdnums(userid); StacLogTickcounts(userid); + StacLogMouse(userid); StacNotify(userid, "Invalid usercmd data! cmdnum or tickcount < 0!", invalidUsercmdDetects[cl]); } @@ -1109,6 +1115,7 @@ void invalidUsercmdCheck(int cl) StacLogAngles(userid); StacLogCmdnums(userid); StacLogTickcounts(userid); + StacLogMouse(userid); StacNotify(userid, "Invalid usercmd data! client buttons are >= (1 << 26)!", invalidUsercmdDetects[cl]); } @@ -1241,6 +1248,37 @@ bool isTickcountSanish(int cl) return false; } + +stock bool DidRecentlyDoInterestingAction(int cl) +{ + if + ( + // we did an attack on this frame, or the frame before, or the frame before that + // - this is when most of the cheats happen + clbuttons[cl][0] & IN_ATTACK + || clbuttons[cl][1] & IN_ATTACK + || clbuttons[cl][2] & IN_ATTACK + // we clicked attack2 this frame / prev frame / etc + // - should help catch anti airblast tbot + || clbuttons[cl][0] & IN_ATTACK2 + || clbuttons[cl][1] & IN_ATTACK2 + || clbuttons[cl][2] & IN_ATTACK2 + // we clicked attack3 + // - theoretically might catch people w/ bad cheats with aimkey on `midle mouse` or... triggerbotting mvm medics? + || clbuttons[cl][0] & IN_ATTACK3 + || clbuttons[cl][1] & IN_ATTACK3 + || clbuttons[cl][2] & IN_ATTACK3 + // we clicked reload + // - theoretically might catch people w/ bad cheats with aimkey on `r` + || clbuttons[cl][0] & IN_RELOAD + || clbuttons[cl][1] & IN_RELOAD + || clbuttons[cl][3] & IN_RELOAD + ) + { + return true; + } + return false; +} /* bool HasValidAngles(int cl) { diff --git a/scripting/stac/stac_stocks.sp b/scripting/stac/stac_stocks.sp index e4710bba..f32da815 100755 --- a/scripting/stac/stac_stocks.sp +++ b/scripting/stac/stac_stocks.sp @@ -118,7 +118,11 @@ void StacLog(const char[] format, any ...) PrintToServer("%s", colored_buffer); - PrintToConsoleAllAdmins("%s", buffer); + // checking if the convar exists at all before we actually check the value + if (stac_print_to_admin_console && stac_print_to_admin_console.BoolValue) + { + PrintToConsoleAllAdmins("%s", buffer); + } } void StacLogDemo() diff --git a/updatefile.txt b/updatefile.txt index c6492152..2e486177 100755 --- a/updatefile.txt +++ b/updatefile.txt @@ -4,11 +4,11 @@ { "Version" { - "Latest" "6.1.2" + "Latest" "6.1.4" } - "Notes" "Changes in 6.1.2" - "Notes" "- Add generalized ban messages and enable them by default." + "Notes" "Changes in 6.1.4" + "Notes" "- https://github.com/sapphonie/StAC-tf2/pull/190" "Notes" "===== StAC is always looking for feedback! Got an issue, request, or wanna discuss? Open a GitHub issue and join the Discord server linked in the README, on GitHub! =====" "Notes" "===== Thank you for using StAC. -sappho ===== " }