Client Sided Anti Cheat System

Tutorial on how to Anti Cheat on the Client Side, I guess.

Decided to make a tutorial show casing something nobody seems to be talking about. Hope you find it useful I guess.

Mechanism

There's an unintended feature(? more like a bug) for when you use modules. If you require a module script and disable or delete the script that's running it,

Whatever you put inside the module script will continue to run.

Why, IDK, it was a bug I ran into a few years back that I just so happened to have remembered after someone brought up the topic about anti-cheats.

Anyways, this allows you to have a Client-Sided anti cheat system that can’t be disabled.

Example of it being done on Server Side:
Module Script

while true do
	task.wait(1)
	print("Running")
end

return nil

Server Script in workspace

task.wait(5)
print("Requiring ModuleScript")

task.wait(1)
require(script.ModuleScript)

Video showcasing Script: Don’t mind the lag spike when deleting, my laptop is garbage

In short, just make your anti cheat on a module and require it on a client-sided script

Usage

Usage

How you use this knowledge really depends on how your game works. Though to begin with, we can make an instance detector to detect when an instance is spawned. To make this we need
  • A remote function (to check if instance exists in server)
  • Server sided script (to return the function)
  • Client Sided script (the anti cheat detector, I guess)
  • Module Script (just read the Mechanism on why we need it)

Im gonna assume you know how remote functions work and not explain anything about the remote function.


Server Script Code placed inside ServerScriptService:

local RemoteEvents = game:GetService("ReplicatedStorage").RemoteEvents
local toServer = RemoteEvents.toServer

toServer.ObjInServer.OnServerInvoke = function(plr,obj:Instance)
	return obj
end

What this script does is gets the RemoteFunction, in this case it’s in ReplicatedStorage.RemoteEvents.toServer where RemoteEvents and toServer are just folders for organization, then detects when the RemoteFunction has been invoked checking to see if an obj(any instance of sort) is replicated on the server. If it is replicated on the server it’ll return the instance, if not, it will return nil.


Local Script Code placed inside StarterPlayerScript:

local ModuleScript = require(script:WaitForChild("ModuleScript"))
game:GetService("StarterPlayer").StarterPlayerScripts.ClientDetector:Destroy()
script:Destroy()

What this does is require the Module Script which is parented under the Local Script, we require the module script because it’s where we’ll be writing our anti cheat code. Why we write it in the ModuleScriipt? Well go read Mechanism, if you haven’t. Anyways, we later destroy any trace of the Script so the exploiter doesn’t have any clue we have an anti cheat system.


Gonna be explained in sections so it’s easier to understand
ModuleScript Code Parented inside the Local Script:

local toServer = game:GetService("ReplicatedStorage").RemoteEvents.toServer
local ObjInServer = toServer.ObjInServer -- RemoteFunction

local plr = game:GetService("Players").LocalPlayer

Gets the RemoteFunction, player, and character of the Client.

local function ObjExsist(obj:Instance):boolean
	if ObjInServer.Parent == nil then -- Checks if the RemoteFunction has been deleted
		plr:Kick("Exploiting")
	end
	if not ObjInServer:InvokeServer(obj) then
		task.wait(.2) -- prevents Parent error happening on "obj"
		obj:Destroy()
	end
end

What this function first does is, check if the RemoteFunction ObjInServer is parented is nil, basically checks if the Client has deleted the RemFunc, if so, kick the player, since if the Client has deleted the RemFunc, that’ll tell us it’s an exploiter.

Then it checks if the obj exists on the Server
if not ObjInServer:InvokeServer(obj) then will either return the obj if it exists on the Server or nil if obj doesn’t exists on the server. So basically, if object not in server then do. Anyways the next line of code deletes the object since it doesn’t exists on the Server, and therefore, we’ll know it was spawned in the Client.

workspace.ChildAdded:Connect(ObjExsist)

Pretty short and self explanatory, if something gets added to workspace, it calls the ObjExsists function (Not the RemoteFunction) and deletes it if it doesn’t exists on Server.

Entire ModuleScript Code
local toServer = game:GetService("ReplicatedStorage").RemoteEvents.toServer
local ObjInServer = toServer.ObjInServer -- RemoteFunction

local plr = game:GetService("Players").LocalPlayer

local function ObjExsist(obj:Instance):boolean
	if ObjInServer.Parent == nil then -- Checks if the RemoteFunction has been deleted
		plr:Kick("Exploiting")
	end
	if not ObjInServer:InvokeServer(obj) then
		task.wait(.2) -- prevents Parent error happening on "obj"
		obj:Destroy()
	end
end

workspace.ChildAdded:Connect(ObjExsist)

Video the entire script working I guess:
https://youtu.be/AujNr5B8QcE

My Version of Usage

My version of Usage

I HIGHLY recommend making your own and using maybe some of my code. This is due to the fact that your game probably uses different mechanics than mine. Which may result in the AntiCheat system deleting something that you wanted to stay.

I won’t be explaining in depth how mine works either since it’s very long and I don’t want to get into it. There’s comments if you plan on using it and I’ll just list the main facts,

  • It checks if player has tampered with their Humanoid’s WalkSpeed, JumpPower, HipHeight, and PlatformStand and sets the properties back to it’s ServerSide
  • It checks if any object is added to client and deletes it if it isn’t in the Server. It also Kicks the player if the obj was a Local Script.
  • It checks if any basepart’s cancollide has been turned off and sets it back to it’s server side.
  • It makes an Attribute “CanCheck” (Boolean) on literally everything so the script doesn’t repeat itself and cause additional Calls on RemoteFunction. This may lag your game depending on it’s size.
  • There’s a ignoreTbl that makes the AntiCheat ignore whatever’s in there.

Here’s my entire Module Script Code I used, you can just replace the old one if you were for some reason following along with the tutorial.

My anti cheat module
--[[
									RULES, I guess,
	- If object or property wasn't replicated on server, the object will get deleted or property set back
	- Adds a Attribute "CanCheck" (boolean) to literally everything so scipt doesn't repeat, so may cause
		lag upon joining depending on your device 
]]
local toServer = game:GetService("ReplicatedStorage").RemoteEvents.toServer
local ObjInServer = toServer.ObjInServer
local CheckProperty = toServer.CheckProperty

local ignoreTbl = {} -- anything in ignoreTbl will get ignored by anti cheat
local plr = game:GetService("Players").LocalPlayer
table.insert(ignoreTbl,workspace:WaitForChild("Camera",5)) -- prevent camera glitching

-- Checks if object is replicated on server
local function ObjExsist(obj:Instance):boolean
	if ObjInServer.Parent == nil then
		task.wait(.2) -- prevents Parent error happening 
		obj:Destroy()
		plr:Kick("Exploiting")
		return false
	end
	if not ObjInServer:InvokeServer(obj) then
		if obj:IsA("LocalScript") then
			plr:Kick("Exploiting")
		end
		task.wait(.2) -- prevents Parent error happening 
		obj:Destroy()
		return false
	end
	return true
end

-- Checks if Humanoid and sets up functions if it is
local function CheckHumanoid(obj:Humanoid):boolean
	if obj:IsA("Humanoid") then
		obj:GetPropertyChangedSignal("WalkSpeed"):Connect(function()
			if CheckProperty then
				obj.WalkSpeed = CheckProperty:InvokeServer(obj,"WalkSpeed")
			else
				plr:Kick("Exploiting")
			end
		end)
		obj:GetPropertyChangedSignal("JumpPower"):Connect(function()
			if CheckProperty then
				obj.JumpPower = CheckProperty:InvokeServer(obj,"JumpPower")
			else
				plr:Kick("Exploiting")
			end
		end)
		obj:GetPropertyChangedSignal("HipHeight"):Connect(function()
			if CheckProperty then
				obj.HipHeight = CheckProperty:InvokeServer(obj,"HipHeight")
			else
				plr:Kick("Exploiting")
			end
		end)
		obj:GetPropertyChangedSignal("PlatformStand"):Connect(function()
			if CheckProperty then
				obj.PlatformStand = CheckProperty:InvokeServer(obj,"PlatformStand")
			else
				plr:Kick("Exploiting")
			end
		end)
		return true
	end
	return false
end

-- Checks if BasePart and sets up functions if it is
local function CheckBasePart(obj:BasePart):boolean
	if obj:IsA("BasePart") then
		obj:GetPropertyChangedSignal("CanCollide"):Connect(function()
			if CheckProperty then
				obj.CanCollide = CheckProperty:InvokeServer(obj,"CanCollide")
			else
				plr:Kick("Exploiting")
			end
		end)
		obj:GetPropertyChangedSignal("Anchored"):Connect(function()
			if CheckProperty then
				obj.Anchored = CheckProperty:InvokeServer(obj,"Anchored")
			else
				plr:Kick("Exploiting")
			end
		end)
		obj:GetPropertyChangedSignal("Parent"):Connect(function()
			if CheckProperty then
				if obj.Parent == nil and CheckProperty:InvokeServer(obj,"Parent") ~= nil then
					plr:Kick("Exploiting")
				end
			else
				plr:Kick("Exploiting")
			end
		end)
		return true
	end
	return false
end

-- Checks if part added is replicated on server and does it again for every new child added
local function partAdded(obj:Instance)
	obj:SetAttribute("CanCheck",true) -- Sets this so RemoteFunctions don't run more than twince
	task.spawn(function() -- checks what class it is and sets it I guess
		CheckHumanoid(obj)
		CheckBasePart(obj)
	end)
	obj.ChildAdded:Connect(function(child)
		if ObjExsist(child) and not obj:GetAttribute("CanCheck") then
			partAdded(child)
		end
	end)
	for _,stuff in obj:GetChildren() do
		task.spawn(function()
			partAdded(stuff)
		end)
	end
end

-- Starts checking if obj and any object added in obj, if client has messed with it
local function mainFunc(obj)
	task.spawn(function()
		for _,stuff in ignoreTbl do
			if stuff == obj then
				return
			end
		end
		if ObjExsist(obj) and not obj:GetAttribute("CanCheck") then
			partAdded(obj)
		end
	end)
end

plr:WaitForChild("PlayerGui",5).ChildAdded:Connect(mainFunc)
task.spawn(function() -- deletes any object added to Freecam script(script that's added by Default by Roblox)
	mainFunc(plr.PlayerGui:WaitForChild("Freecam",10))
end)
plr:WaitForChild("Backpack",5).ChildAdded:Connect(mainFunc)
plr:WaitForChild("PlayerScripts",5).ChildAdded:Connect(mainFunc)
game:GetService("ReplicatedFirst").ChildAdded:Connect(mainFunc)


for _,stuff in workspace:GetChildren() do
	task.spawn(function()
		for _,trackobj in ignoreTbl do
			if stuff == trackobj then
				return
			end
		end
		if ObjExsist(stuff) and not stuff:GetAttribute("CanCheck") then
			partAdded(stuff)
		end
	end)
end
workspace.ChildAdded:Connect(function(child)
	for _,trackobj in ignoreTbl do
		if trackobj == child then
			return
		end
	end
	if ObjExsist(child) and not child:GetAttribute("CanCheck") then
		partAdded(child)
	end
end)
--[[ Used to check whats in ignoreTbl feel free to delete
local UserInput = game:GetService("UserInputService")
UserInput.InputBegan:Connect(function(input,isType)
	if not isType then
		if input.KeyCode == Enum.KeyCode.E then
			print(ignoreTbl)
		end
	end
end)
]]
return nil

Here’s the model of the Anti Cheat System I made if you want to test it out:
https://create.roblox.com/store/asset/103466681912144/Anti-Cheat-System

Really the main take away is the Mechanism, you can, at least as far as my knowledge about exploiters go, have an Client Side anti Cheat without having the exploiters just turn it off.

3 Likes

i wish a method existed for the Player object that allows you to check if they are exploiter or not

game.Players.LocalPlayer:IsExploiter() -- returns a boolean
game.Players.LocalPlayer:GetExecutorThisPlayerIsUsing() -- returns a string that's either none or the type of executor like "synapsex" or "kiddions"
9 Likes

Client sided anti cheat is just not it. Everything can be spoofed so nothing actually will work. Kick the player on the client? No problem just spoof the method. Kick them through a remote? No problem just spoof the remote.

2 Likes

That’s perfect, i already gave my solution. They’d just need to cooperate with the enemy and add methods like IsExploiter() and
no
wait my plan
it’s all falling apart
due to a fatal flaw
nooOOOOOO

I guess we’ll just stick with server authoritative systems
Maybe add a official client-side lua executor on the F9 console so there is no point in using third party executors anymore and devs are more incentivized to trust the server :troll:

1 Like

Tried it and its a pretty viable idea. Combined with other methods this could help defend client scripts more.

Things I do with my client anti exploit:

  1. Random name & location in PlayerGui/PlayerScripts on runtime
  2. Two LocalScript system detecting each other’s destruction/disabling
  3. Heartbeat/sanity check system where the client sends a remote every second to the server
  4. Crash on kick
  5. Obfuscate them (for free) via a website

Despite the many simple ways of bypassing this, I actually recommend trying to create anti exploits on the client, it’ll help you understand what an exploiter is capable of.

1 Like

I highly disagree, in less than a month my anti-fly banned over 3,000 script kiddies. Client anticheats is still something you should have.

5 Likes

How can they spoof this? Or how can they spoof things in general? I’m not the smartest on how exploits work so I would appreciate if you could explain this

Anything on the client can be modified

2 Likes

Yeah its a mouse and cat game, but just because it can be spoofed doesnt mean that client sidded anti cheats are pure garbage. They can be really useful and you can (from what I have seen) do different creative methods to detect spoofs that have been done!

Note that I am no expert in security measures I just know that sanity checks are always super important to include on the server side.

2 Likes

This i agree on, skids will most definitely get banned BUT this is a short term solution. Once the “bigger fish” of the exploting community figure out the game and release exploit for the given game, the skids will start appearing again.

The same thing i said to dauser, short term solution.

Sanity checks are a must and should be considered always

1 Like

This is exactly what I’m doing using “my version of anti cheat,” since the script will continue to run even after being destroyed, you can set up checks on the Player’s Humanoid and check everytime the Humanoid has their walkspeed, runspeed, hipheight, or PlatformStand changed and set it back to what it is on the ServerSide. Allowing you to essentially prevent exploiters changing the values of those properties.

Client-sided sanity checks are not sanity checks, scripts can be “disabled” even when destroyed

You appear to have not watched the video in my “Mechanism” page. In the video, I disabled and even deleted the script and it still ran.

Its a smart solution but the problem is, thosse connections can be spoofed so hooking to thosse functions but also keeping an anti spoof intact is the best way to have a good measure in my opinion. Your doing really good with the idea behind the anti cheat, just continue with it and fail, learn and adapt and you will be good to go :> :+1:.

From my short research on spoofing, its when a client sends fake data to the server or other Client scripts to allow it to bypass stuff.

Below is a code similarly written in my AntiCheat that’s in a Local Script

Humanoid:GetPropertyChangedSignal("WalkSpeed"):Connect(function()
	if CheckProperty then -- CheckProperty is a RemoteFunction that returns a property of an obj
		obj.WalkSpeed = CheckProperty:InvokeServer(obj,"WalkSpeed")
	else
		plr:Kick("Exploiting")
	end
end)
	-- Extra code not shown

Based on my understanding of spoofing, the code
plr:Kick(),
if CheckProperty then,
obj.WalkSpeed =
Humanoid:GetPropertyChangedSignal("WalkSpeed")
would not work. I would like if you could clarify if they wouldn’t work, you don’t need to explain why it wouldn’t, just say if it wouldn’t work.

One more question, can spoofing change the values sent by Server? I believe it can’t but I’d like some clarification on this

Any client sided anti-cheat can and will be navigated around eventually, but they’re great for the majority of exploiters who don’t wanna figure out the anti-cheat’s ins and outs to make it work. They’re an effective bar of entry for cutting off the fat. Although, of course, it should always be complimented by server sided authority and checks no matter what. There’s nothing as effective and reliable as that and it’s a good last measure.