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

Yes! It also goes around saveinstance() which is pretty neat for hiding the anti-cheat script itself from the client.

6 Likes

Well that’s good, appreciate it!

5 Likes

Did you create TCP Handshakes, with SYN, ACK, FIN, and RST? Nice!

Now, I got my Computer snarkiness out of the way.

Background about me:
Currently, studying for my SANS GFACT Certification with the NCSF (National Cyber Scholarship Foundation), an entry-level certification to understand cybersecurity terminology and methodology. I am also, studying for CCNA and CompTIA NET+.

Anything on the client side can always be stolen, decrypted, exploited, etc. because the client needs to decrypt and intercept data (from packets, and frames) to display stuff on the screen/display properly.

Client-side is anything the end user or client sees. The way attackers exploit or infiltrate is by understanding the application layer. This can be XSS (Cross-Site Scripting), SQL injections, XMF, LFI, RFI, etc.

If the server side doesn’t sanitize/serialize data, you have a potential vulnerability. If you ever get into offensive security, if you find an error. Then, you exploit it to see what you can get.

But, a major flaw with client-side anti-cheats is their aggression. Every program/code, function, etc. uses CPU (or processor) cycles, the more your run the program/function, the less power the CPU (or processor) has. Don’t be aggressive while trying to battle cheaters/exploiters, it only harms the gameplay or performance of other users.

A few years ago, I had the same idea, thought, or intention as you. Thinking anti-cheats will fix x, y, and z is a fair idea, but how you execute it is where it really comes into effect. If you create an anti-cheat that checks if a player is not on the ground for x seconds, and you run this every second, sounds fantastic, at first. Until, you realize if a player jumps off this high building, cliff, or block, it triggers the anti-cheat and kicks them (or however you handle it). Checking if they are flying every second wastes time and processing power. Instead, check if they are flying when they kill someone or when they get points/rewards.

If you are still reading, I want to think about the following? Why does the same exploit work on other games and not just yours? The exploiter uses a program that affects Roblox’s engine, not your just game. 99.9% of the exploit used wasn’t developed for just your game.

I want the development community to switch gears (and their mindset) on how to handle exploiters/cheaters. Think passively and not actively. If the cheater kills someone on the other team at their spawn? Then the cheater gets no points. Don’t waste time or processing power on small groups of people, instead focus on improving the gameplay and adding new features to keep the community engaged.

16 Likes

Could you break this down? A bit confusing, this is roblox!

FireServer? This is roblox

3 Likes

I am aware that this is ROBLOX, but when you mentioned Handshakes. My brain automatically thought of TCP handshakes used on the Transport Layer (Layer 4) of the OSI Model to connect applications to port numbers. If you don’t understand the reference, then skip it. It is not crucial information for the post.

SYN = Synchronise
ACK = Acknowledge
FIN = Finish
RST = Reset

In the TCP 3-way Handshake, it does the following:
SYN, ACK-SYN, ACK

8 Likes

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.

7 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.

10 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: