Hello fellow Roblox Developers!
As an anticheat developer, I often saw games that had awful anticheats practices.
This really frustrates me, as a developer, to see beautiful games being ruined by exploiters.
Warning: This post is for developers that already have a client and server anticheat.
Table of Contents
Common bad practices
1: Using :FireServer
or :InvokeServer
to signal to the server the client is being tampered with
In the majority of games, I kept seeing in opensource exploit scripts (and even in an AntiCheat from Community Resources!) that the anticheat system used a Simple Ban Remote that once fired, often wipes the player’s progress, and/or just straight up bans the player
It’s a very bad practice as it can just be bypassed by this simple code snippet:
local DummyRemote = Instance.new("RemoteEvent") --> Or RemoteFunction if the anticheat uses a RemoteFunction
local ACRemote = Path["To"].AntiCheat["Remote"]
local OldFireServer; OldFireServer = hookfunction(DummyRemote.FireServer, newcclosure(function(self, ...) --> Or :InvokeServer if the anticheat uses a RemoteFunction
if rawequal(self, ACRemote) then --> Safely checks if self is the AntiCheat remote
return --> Return nothing (Doesn't fire the remote)
end
return OldFireServer(self, ...)
end))
Instead, use a remotefunction to signal to the server the client is being tampered with!
You should use a remotefunction instead since exploiters won’t be able to spy on it, except on the beta version of synapse, preventing a large number of script developers from spying on the anticheat remote.
You can set it to an OnClientInvoke callback and the server will regularly check that the client isn’t being tampered with:
-- ClientSide
local IsClientTampered = false
-- Blah blah detections
task.spawn(function()
while true do
if Blahblah.Something.HelloJeremy and Foo.Bar.ImBroke then
IsClientTampered = true
end
end
end)
MyACRemoteFunction.OnClientInvoke = function()
return IsClientTampered
end
-- ServerSide, assuming we are in a loop
if MyACRemoteFunction:InvokeClient(MyPlayerSumthing) then
MyPlayerSumthing:Kick("Stop!")
end
You could even check if the client isn’t responding after 60 seconds for example, which means the client is trying to bypass the anticheat:
local PlayerData = {}
Players.OnPlayerAdded:Connect(function(MyPlayer)
PlayerData[MyPlayer] = {
IsTampered = false,
LastStarted = os.clock()
}
while true do
PlayerData[MyPlayer]["LastStarted"] = os.clock()
local IsClientTampered = ACRemote:InvokeClient(MyPlayer)
PlayerData[MyPlayer]["IsTampered"] = IsClientTampered
task.wait()
end
end)
Players.PlayerRemoving:Connect(function(MyPlayer)
PlayerData[MyPlayer] = nil
end)
while true do
for Player, Data in pairs(PlayerData) do
if Data.IsTampered then
Player:Kick("Stop the haxx!!!!!")
end
if os.clock() - Data.LastStarted >= 60 then --> 60 being the timeout in seconds
Player:Kick("Stop yielding the remote you noob")
end
task.wait() --> Avoid Script Exhaustion Timeout
end
task.wait(2)
end
However, this can be spoofed too, since there isn’t any encryption nor any spoofing checks (see the section about encryption)
2: Making physics checks on the client
This sounds pretty obvious right? To anomic devs it doesn’t
As you can see from this code snippet from V3rmillion, it checks for a constant: NoclipChecking
. This shows that the check is on the client.
This is very bad especially for a game as big as Anomic since there will be a 99% chance that a hacker will be in the game flying around in a rainbow avatar…
There are multiple ways to spoof noclip checks, for example just spoofing the position like this (again, very simple example):
local OldIndex; OldIndex = hookmetamethod(game, "__index", newcclosure(function(self, Index)
if
OldIndex(LocalPlayer, "Character")
and OldIndex(LocalPlayer, "Character"):FindFirstChild("HumanoidRootPart")
and rawequal(self, OldIndex(LocalPlayer, "Character"):FindFirstChild("HumanoidRootPart"))
then
-- Puts checks here to check if the calling script is the anticheat one, depends on the anticheat
return Vector3.zero
end
return OldIndex(self, Index)
end))
Now, some of you might be asking why exploiters would return Vector3.zero
or any random Vector3
value: Noclip checks usually sets a variable to the old position of the HumanoidRootPart
, waits for a set amount of seconds (depending on the anticheat, usually 0.5 or 1 second) and then Raycasts from the Old Position of the HumanoidRootPart
to it’s new position. If the raycast function (workspace:Raycast
) returns a RaycastResult
and it’s Instance
field is not equal to nil
, it means that the player noclipped.
HOWEVER, if done on the client, to presumably reduce the server workload (which I can understand considering Roblox’s gameserver tickrate is 15 hz), It can be bypassed by just returning Vector3.zero
(or a Position where there aren’t any part in it) when an exploiter detects that the AntiCheat is trying to get the Position of the HumanoidRootPart
. If the position is being spoofed to Vector3.zero
, the check would just raycast from Vector3.zero
to Vector3.zero
, therefore bypassing the noclip check (since the raycast wouldn’t return anything/or wouldn’t have the Instance
field).
3: Using functions such as rawequal
in your client anticheat
If not necessary (using this outside of a table that has a metatable), avoid using rawequal
, rawget
and rawset
for detections since exploiters can just spoof the result like this:
local oldrawequal; oldrawequal = hookfunction(rawequal, newcclosure(function(self, SecondObject)
if (not checkcaller()) then --> Checks if the game called rawequal and not the exploit
if oldrawequal(SecondObject, SomethingThatCanBeUsedToDetectTheExploits) then
return false --> Spoofed
end
end
return oldrawequal(self, SecondObject)
end))
or they could just spy on the function:
local oldrawequal; oldrawequal = hookfunction(rawequal, newcclosure(function(...)
warn("Got arguments:", ...) --> That is detectable lol, but you get the idea
return oldrawequal(...)
end))
How powerful are exploiters?
This is a question that I saw on the devforums in various forms:
- Can exploiters modify my script?
- Can exploiters modify my variables?
- Can exploiters do …?
and the list goes on.
The answer: it depends on the executor they are using, on the game, their level, and who they are.
Why the executor?
Not all the executors are the same! Most of them have their own custom functions, but the majority follows something called UNC (Unified Naming Convention, which makes exploits that adopt this convention have the same aliases to an exploit function, for example using cloneref on Fluxus would also work on Script-Ware), aswell as different methods of execution, some being better than others.
For example on Synapse, there is this exclusive function called syn.trampoline_call
which spoofs the function caller information that is being returned from debug.info
(and debug.getinfo
exploit-wise, which is a vanilla lua function), such as the source
and function
to whatever the exploiter desires! Though, it can be detected if not used correctly.
But on an executor such as Celery, which is a basic executor, you won’t find such complex functions.
Exploiters can hook functions and can set a function’s upvalues and constants (for example bypassing a cooldown on the client by setting a number constant from 10 seconds to 0)
Why the game?
Some games are more vulnerable than others, and have different mechanics and goals
For example a game like Arsenal, you will be powerful by having an Aimbot and ESP Script,
But for a game like Pet Simulator X, you will be powerful by having an autofarm.
Why the level and who they are?
It depends on these two because usually a consumer (a user that runs a script they bought/got from a youtube video) is usually someone with no experience in exploiting and are more likely to be detected by the ingame anticheat (if there is one) or just having trouble making their own script for a game. But on the other hand, a script developer will have experience in exploiting and most likely will bypass anticheat measures, some being harder to bypass than others (depending on the game).
[BONUS] Handshakes and Encryption
This is probably why you read this post in the first place: the guide on how to make a handshake.
A handshake in anticheats often is basically the server checking if the client anticheat is working, and optionally passing in the handshake what detection got triggered.
A simple handshake with a basic encryption system is typically is the following:
-- ClientSide
local MyCipherFunction = function(String) --> Could be any kind of encryption, such as AES, or even a homemade encryption system, you shouldn't use this though, as this is just a placeholder of what it could be
local Characters = {string.byte(String, 1, #String)}
local CipheredCharacters = {}
for Index, Character in pairs(Characters) do
CipheredCharacters[Index] = Character + 1
end
return string.char(unpack(CipheredCharacters))
end
ACRemote.OnClientInvoke = function(StringToCipher)
return MyCipherFunction(HttpService:JSONEncode({
TamperedWith = IsBeingTamperedWith,
CipheredResult = MyCipherFunction(StringToCipher)
}))
end
-- ServerSide, in a PlayerAdded
-- This of course should have error handling (pcall, checking if response is decipherable, if its valid json, etc) and such, this is just an example
while true do
local Success = pcall(function()
local OriginalString = tostring(math.random())
local Response = HttpService:JSONDecode(MyDecipherFunction(ACRemote:InvokeClient(Player, OriginalString)))
if Response.TamperedWith then
Player:Kick("Client has been tampered with")
break
end
if OriginalString ~= MyDecipherFunction(Response.CipheredResult) then
Player:Kick("Ciphering has been tampered with (Possible Handshake Replay Attack?)") --> Stops exploiter from returning the same thing everytime
break
end
end)
if not Success then
Player:Kick("Tampered with the handshake in an attempt to bypass the client anticheat")
break
end
task.wait(5) --> Wait 5 seconds before next handshake
end
This is a basic example and you can add any check you want! For example: returning a multiple of pi to the power of os.clock()
The end
Thank you for reading my first community tutorial on the devforum!
Let me know if you have any issues!
And if I made any grammar mistakes please let me know, since I made this at 3 AM
You should also take a look at this tutorial by my friend @CodedJer, in which he explains in simple terms how to make a good base for your client anticheat in addition to your server one
And you should also take a look at my replies on this anticheat topic as it also shows in a real world scenario how exploiters could bypass your anticheat