Client Anti Cheats: Aren't as bad as you think!

Except for the confusing parts (that make no sense for Roblox), I agree with your post!

2 Likes

Hey there,

About your reference to TCP Handshakes with SYN, ACK, FIN, and RST, those are basic ideas in networking, but they are different from the context of client-side security you talked about. They play an important role in establishing and terminating connections between network devices and allowing reliable data transmissions. However, the topic of application security you talked about focuses more on web application vulnerabilities and protecting against attacks like XSS, SQL injections, and others.

You’ve already mentioned the importance of a well-designed anti-cheat system and how it affects overall gameplay and user experience. And about not being overly aggressive in detecting cheats, as it could negatively impact legitimate players. A balance between effective cheat detection and minimal performance impact is a challenge that game developers always face.

The idea of thinking more passively when dealing with exploiters and cheaters is a good idea. A proactive approach that focuses on improving gameplay and adding new features can improve the game and reduce the appeal of cheating. Rewarding fair play and discouraging exploiters through game mechanics, allows developers to spend more time actually developing.

9 Likes

That’s the entire post jeremy :slight_smile:

2 Likes

Thats a smart solution indeed.

But about the pcall solution for the local kick, if the script finds out the player has blocked a kick, what will it do? It can’t do much itself with the hacker hooking remotes.
And then there’s the handshake. Hackers could easily figure out the delay between each handshake using tick(). Even if there’s a pattern, it can be detected as the pattern will eventually repeat itself. So even if a localscript knows a player is blocking kicks, it can’t tell a script as the remotes are hooked and the handshake is sent and received by the hacker.

Creating anticheat for Roblox really can be complicated… sigh But the solution will be there, eventually.

You could crash the user instead, before you go all
“oh but ScriptContext:SetTimeout” there are crash methods that don’t get affected by it

2 Likes

Then thats a good solution. You really seem to know alot about this, keep it up!

2 Likes

Quick question about performance though, on the luau’s performance page, getfenv() deoptimizes the entire environment, wouldn’t this bad?

Yes, but it barely affects the script itself, at least, I, never see an impact when developing anti-cheats using getfenv.

2 Likes

Finally, someone addressing encryption. Literally everyone is like “oh my god secoour remote!1!1” and never tell you how nor do they ever talk about encryption.

7 Likes

Isn’t using just script:Destroy() a better way? Also thanks god someone finally mentioned this as lack of information about that very useful solution made me create another topic.

Is anyone aware of any resources to help with creating a simple client anti-cheat + handshakes?

1 Like

Does this bypass “getscripts()”, and also can exploiters hook the Player:Destroy() function which is an alternative way of kicking the player right now, I have tried to find an answer but couldn’t find any, if they can what’s the best way of kicking exploiters?

1 Like

Yes. It does indeed not showup in getscripts.

Either using a RemoteEvent for the server to kick them or straight up crashing them with something like while true do end (Before anyone yells about me that SetTimeout bypasses crashes, there are some crash methods which are unaffected by this)

1 Like

Honestly, we have no reason of doing anything with encryption. Encryption is for man-in-the-middle attacks, meaning someone other than the recipient of the data is attempting to access the data.

Unfortunately, the recipient of the data is the exploiter, which means they are also given the key to decrypt.

There’s an argument here that the recipient is the Roblox client, but Roblox runs on the user’s device because permission is given for Roblox to do so. Anything ran on the user’s device can be altered by the user.

Every EULA I’ve seen blatantly prohibits the user from modifying the software in any way. Think about it, why even have this section of the EULA if it can be completely prevented? Because it can’t.

The user controls their device; not you, not me, not even Roblox can stop someone from running what they want to on the device they own (including altering the Roblox client itself).

The good news is the title of this topic is still true. For example, an attack cooldown should be on both the client and the server. The client-sided cooldown can dramatically reduce the amount of network traffic, while the server-sided cooldown prevents exploitation.


Securing the remote isn’t talking about securing the data in transit. What they are talking about is filtering the data received by the remote as well as cooldowns for the remote. Here is a quick example of how to secure a RemoteEvent:

Note: every filter will be a bit different depending on the expected data
Server

local Players = game:GetService("Players")
local remoteEvent = game:GetService("ReplicatedStorage").RemoteEvent
local remoteCooldown = 5 --> only allows remotes from the user every 5 seconds
local playerRemoteCooldowns = {}

local function FilterRemote(player, data)
	--Assume your remote only accepts numbers 1 - 1000000 with a 5 second cooldown in between
	local currentTime = workspace:GetServerTimeNow()
	local userId = player.UserId
	if not playerRemoteCooldowns[userId] then print("No table entry for this user") return end --> For piece of mind
	if currentTime < playerRemoteCooldowns[userId] then print("Remote still on cooldown for this player") return end
	if type(data) ~= "number" then print("Datatype is not a number") return end --> Nil isn't a number, so this also solves that problem as well
	if data ~= data then print("Data is NaN") return end --Not needed since NaN is treated as an infinite number and will be caught on the next line
	if data < 1 or data > 1000000 then print("Data is outside number range") return end
	if data % 1 ~= 0 then print("Data is not a whole number") return end
	--guaranteed to be a number between 1 and 1000000 by the time it reaches here
	--Reset the cooldown
	playerRemoteCooldowns[userId] = currentTime + remoteCooldown
	print(data)
end

local function OnPlayerAdded(player)
	--Add their id to the list of active players
	playerRemoteCooldowns[player.UserId] = workspace:GetServerTimeNow()
end

local function OnPlayerRemoving(player)
	--Remove their id to prevent memory leaks
	playerRemoteCooldowns[player.UserId] = nil
end

Players.PlayerAdded:Connect(OnPlayerAdded)
Players.PlayerRemoving:Connect(OnPlayerRemoving)
remoteEvent.OnServerEvent:Connect(FilterRemote)

Client

local remoteEvent = game:GetService("ReplicatedStorage"):WaitForChild("RemoteEvent")
remoteEvent:FireServer() -- Nil
remoteEvent:FireServer("A") -- Character
remoteEvent:FireServer("1") -- Number character
remoteEvent:FireServer(Instance.new("Part"))-- Instance
remoteEvent:FireServer({10000}) -- Table
remoteEvent:FireServer(function() data = 1 end) -- Function/undefined data
remoteEvent:FireServer(-1) -- Negative
remoteEvent:FireServer(0) -- Zero
remoteEvent:FireServer(1/0) -- NaN
remoteEvent:FireServer(1e10) -- Scientific notation/large number
remoteEvent:FireServer(1.1) --Decimal
remoteEvent:FireServer(math.huge) -- Large number
remoteEvent:FireServer(2) -- is accepted by the server
remoteEvent:FireServer(2) -- Not accepted, remote is on cooldown


--[[	OUTPUT

Datatype is not a number
Datatype is not a number
Datatype is not a number
Datatype is not a number
Datatype is not a number
Datatype is not a number
Data is outside number range
Data is outside number range
Data is outside number range
Data is outside number range
Data is not a whole number 
Data is outside number range
2
Remote still on cooldown for this player

]]

That’s enough to get you started, but it is not a complete package; especially when it comes to the cooldowns. You’ll notice the server will say the remote is still on cooldown if the following is done:

remoteEvent:FireServer(2)
task.wait(5)
remoteEvent:FireServer(2)

The easiest way to solve this problem is to make the client have a slightly longer cooldown than the server. The harder method involves the server sending the cooldown-end time and possibly offset it depending on lag on the client.

Another way to expand on this security is by kicking players that are obviously attempting to lag the server through your remote. Remote queue exhaustion happens from the size of the sent packets, not how many times it is sent.

Be cautious here though as users with satellite internet are prone to lag spikes which last for several seconds and can look like they are attempting to lag the server when their connection is resumed. Factor this in and you can safely kick those that are way outside the norm.

11 Likes

Also, I have 1 small question, about Replicated First, couldn’t the exploiter in theory use something like :ClearAllChildren(), with auto execute, and delete the scripts in Replicated First before they even run, or the scripts hide themselves before they even have the chance to do that?

I would be thankful if this was answered. :smile:

Client sided anti cheats are uselless to secure games, However heres a good use you can give them.

Make a simple client sided anti cheat.
Wait for a cheat dev to release a script to bypass it.
Then just dont patch the anticheat, just change it a bit so the cheat script doesnt works anymore.
Cheat dev will easily be able to fix their script to get it back working, but guess what all the script kiddies will need to wait till he fixes the script. Meaning you can keep slightly changing the anti cheat to keep breaking the cheat script over and over again without actually much effort, essentially wasting their time.

1 Like

Well. It is indeed a cat-and-mouse game, we, as the developers, patch the cheats, the cheat developer bypasses our patch, and we patch their bypass and loop.

1 Like

hackers can’t be banned using kick

Sorry for late replay, but an alternative way of kicking the player is by using Player:Destroy() and they will get kicked because of a Data Model error or something like that, I don’t know if Player:Destroy() can get hooked but yeah, you can use that alternative way.

1 Like

I’ll see if it can be hooked. I think not, as changes in the workspace done by a localscript stay visible for only the client, meaning the changes dont have to be replicated to the server.

Nevermind, I was able to hook the Destroy() and disable it.