Note: I will still add more, I’m just too busy to focus on this right now
Hello, I’m dand, I’ve been scripting for over a year on roblox and have a specific interests in exploits and how they work. So I want to share my knowledge here.
Introduction
I’ve seen a lot of posts on the dev forum recently about exploit related topics not knowing much about them themselves.
So I suggest reading this small little guide to help people understand.
(If you’re looking for a more in depth guide this isn’t the post for you)
What is an exploit?
Well there a few definitions, the one that mainly fits roblox’s definition is,
“A 3rd party program that can alter a client’s behaviour to give an unfair advantage against other players”.
Pretty simple. Most of these exploits allow for people to execute scripts and code on the client with the use of Injectors. Many famous injectors are Krnl, Synapse X and many more some better than others. This is possible because roblox loads a lot of data onto the client to give the in game experience you get to see.
What is a client?
A client is your PC. When you join a roblox server the server gives you an associated Player
Instance. Data is then loaded onto your PC so you can walk, run, chat and interact with the world around you. Of course having this loaded onto your PC is a dead giveaway that people can tamper and mess with it. And you can’t blame them, it is their PC after all. Local scripts run on the client
Now how do you combat with people changing stats and values on their PC?
As far back as 2017? Or later (someone correct me on this), roblox added a new property to workspace called FilteringEnabled which you could toggle on and off. (It is now since deprecated)
What is FilteringEnabled?
When toggled on FilteringEnabled stopped client sided changes from replicating to the server. (So if you changed something on your client, noone else would see the effect), This was a lot better but limitted communication between Client and Server, which introduced Remotes which could send data between the two.
This was a massive breakthrough as any games that enabled FilteringEnabled wouldn’t be as affected by exploiters as before. This is also why most old games that don’t have it enabled have so many exploiters.
How do exploits work then?
Well if you delete a part on the client and it doesn’t change for anyone else, whats the point?
Server sided: Some aspects are still replicated to the server from the client, such as the following;
-
Character movements still get replicated from client to server and since the movements were handled on the client this allowed for exploits to create Character Physic Based exploits, which included flight, speed, jump, noclip and fling exploits.
-
Remotes, Used to send data to the server from the client and vice versa, was also exploited by many and poor remote security is the main cause for exploits that could affect other players and give the exploiter an unfair advantage.
Client sided: some of these fool the client into sending false data to the server;
-
Input based exploits allow for the client to basically run like a robot. Mainly used in FPS or any kind of shooter game, such as aimbot which is unfair to the rest of the human players.
-
Environment, Since the client controllers everything loaded onto it, it can also make unwanted changes, a main example is a hitbox extender, making a head bigger so its way easier to hit. Another example is deleting walls so you can get to a locked off area.
ServerSides
Most Server sided exploits can be detected with some extra security. The server is different to the client and cannot be modified by a client since it is not loaded onto someone’s PC but kept in roblox’s HQ to communicate data between clients to make a game run. Server Sided are normally the case of bad security especially with remotes, lets take an example.
Lets say you have a FPS game and you have a gun that fires an event called Fire to tell the server to shoot from the barrel of the gun to the mouse position.
LocalScript
local mouse = game.Players.LocalPlayer:GetMouse()
local Gun = script.Parent
local FireRemote = game.ReplicatedStorage.Fire
Gun.Activated:Connect(function() -- When gun is clicked
FireRemote:FireServer(Gun.Barrel.Position, mouse.Hit.p) -- tell server to fire
end)
Tells the server to fire from the barrel to where the mouse is aiming
ServerScript
local FireRemote = game.ReplicatedStorage.Fire
FireRemote.OnServerEvent:Connect(function(player, origin, aim) -- when told to fire
-- (not going to include raycast calcuations since its off topic
if hit and hit.Parent:FindFirstChild('Humanoid') then
hit.Parent.Humanoid:TakeDamage(20) -- damage player by 20
end
end)
Now if you are experienced with scripting you’ll know this is really vunerable to exploits with a basic script
Exploit Script
local player = game.Players.LocalPlayer
local event = game.ReplicatedStorage.Fire
while task.wait() do -- loop
for i,v in pairs(game.Players:GetPlayers()) do -- go through each player
if v ~= player and v.Character then
-- tell server to shoot player directly from above
event:FireServer(v.Character.HumanoidRootPart.Position + Vector3.new(0,3,0), v.Character.HumanoidRootPart.Position)
end
end
end
This basically is visuallised like so
Shoot from above down onto the player.
How could we fix this?
Simple server checks
- Remove the origin parameter since that can be spoofed easily by the client.
- Replace it with a gun parameter since its harder to spoof something that already exists on the server.
- Check these with a simple if statement to make sure the gun exists and is equipped.
ServerScript
FireRemote.OnServerEvent:Connect(function(player, gun, aim)
if gun then -- checks if it exists on the server
if player.Character:FindFirstChild(gun.Name) then -- assure the gun is equiped
local origin = gun.Barrel.Position
-- do code
end
end
end)
LocalScript
FireRemote:FireServer(Gun, mouse.Hit.p)
This is just simple server checking and should be normalised when handling events to keep them secure from exploiters
Rate protection
This part is a bit more advanced as it consists of further scripting knowledge.
This method using time tracking methods tick()
to detect if a remote is being fired way more than normally. Of course if you have a local script that fires remote events rapidly, this is useless.
local event = -- define event
local minimum_time = .1
local last_times_fired = {}
event.OnServerEvent:Connect(function(player)
local last_fired = last_times_fired[player] -- gets last time player fired
if last_fired then -- if fired before
local since = last_fired - tick() -- time since then
if since < minimum_time then -- if time since is smaller than the min allowed
return player:Kick('Remote rates too high') -- kick player
end
end
last_times_fired[player] = tick() -- set this for the next time its fired
end)
ClientSides
Client sides are when anti exploits are kept on the client to detect changes and unexpected behaviour to determine if someone is expoiting or not.
This can be very useful though because of its effectiveness against unexperienced exploiters who are mainly refferred to as “skids” or script kiddies. These people just copy scripts online with no knowledge about coding at all and use them in games. This suprisingly is a wide majority of the exploiting community.
While this is all good DO NOT RELY ON IT. If your entire anticheat is based on the client any experienced exploiter could easily destroy it since exploits has complete control over the client and the scripts that run on it.
Examples
What you need to remember is that when WalkSpeed gets changed the property doesn’t change on the server but since (as stated above) movement is handled to the client and replicated to the server. So whilst it might say 16 WalkSpeed on the server, if on the client its 72 it will move like its 72.
(blue is client, green is server)
LocalScript
local player = game.Players.LocalPlayer
player.CharacterAdded:Connect(function(char)
local hum = char:WaitForChild('Humanoid')
hum:GetPropertyChangedSignal('WalkSpeed')
if hum.WalkSpeed > 16 then
player:Kick('hacking')
end
end)
end)
You could use this for any property you don’t wanna be changed but it can be easily bypassed.
Such as deletion
Memory Usage Method
A widely used and known method is detecting Memory Usage spikes.
Basically how these work is by detecting if the Memory Usage randomly spikes too high. This is useful since when exploits are injected they take up a lot of memory at once, spiking the MemoryUsage.
Client memory usage can be found by pressing F9 when in game.
The method includes .RenderStepped since it needs to check very frequently
local RunService = game:GetService('RunService')
local Stats = game:GetService('Stats')
local spike_limit = 200
local old_usage
RunService.RenderStepped:Connect(function()
local usage = Stats:GetTotalMemoryUsageMb()
if old_usage and usage - old_usage > spike_limit then
game.Players.LocalPlayer:Kick()
end
old_usage = usage
end)
This is useful since it detects on injection before the player can modify its client so they can’t delete the script.
BE CAREFUL
If your game is intense on part count or is very big it is bound to have memory spikes which can cause false positives when a player can be kicked for no reason.
The War on Anti-Cheats
Since there has been a war between exploiters and game developers for ages a lot of methods have been created by exploiters to combat anticheats and most of these methods are the reason Client-Sided anticheats are useless. Most of these methods are quite advanced so I would suggest skipping if you’re not experienced with lua.
Hooking
In simple terms hooking is just altering the behaviour of Instance’s metatables.
If you don’t know metatables then I suggest skipping this part, but basically they are tables that give seemingly empty tables unique behaviour that are attached to one with setmetatable(table, metatable)
. This behaviour is easily altered by exploiters and can disable Events, Spoof properties and hide instances
Since I’m not too experienced with this, theres a really good guide that goes into a lot more detail.
Custom Functions
Injectors support many types of functions that aren’t in roblox or even in lua, some of these functions are to make it harder to create an effiecient ClientSided anti cheat and in some cases, ServerSides aswell.
One of the most used methods is getnilinstances()
which isn’t in luau but is implemented into exploits like KRNL and Synapse. This method returns an array of Instances which parent’s are set to nil. This also includes Destroyed Instances since they also get pareneted to nil until being deleted by the GC. This is why anti cheats that parent to nil seem effective to hide from the exploiter, but instead it is made useless by this method.
There are more methods like getrawmetatable()
that return the metatable of any userdata or table even if the __metatable metamethod is set to something else to misdirect the client. This is used for Hooking which is mentioned above.
There is also GetObjects()
that return all the objects on the client into one array in no particular order which also makes hiding anti-cheats useless.
I would send a link to more documentation but they lead to shady sites that surround the exploiting community and I will probably get flagged.
Backdoors
Ah yes backdoors, the number one reason free models are frowned upon and probably the most dangerous exploits to have in your game. But is also really easy to avoid.
So what are backdoors?
Backdoors are the roblox version of a Trojan virus. They hide in public models (mainly free models) until aded into a game and are told they are important to the model and run malicous code to destroy servers to give the client access to the server.
An example could be a tree with a script inside that says it makes the leaves blow about realistically bu in reality it creates a remote so a client can join and send code directly to the server to be executed.
ServerScript – inside of model
local remote = Instance.new('RemoteEvent')
remote.Name = 'exe'
remote.Parent = game.ReplicatedStorage
remote.OnServerEvent:Connect(function(player, code)
return loadstring(code)() -- allows string to be turned into code and ran
end
ExploiterScript
game.ReplicatedStorage.exe:FireServer('workspace:ClearAllChildren()')
This would run workspace:ClearAllChildren()
on the server and the entire server would be affected.
Luckily backdoors have become less of a danger due to plugins such as RoDefender and studio’s new prompt when inserting a model with scripts inside. Basically just be careful with free models.
Conclusion
Some good tips to take from this.
- Never trust the client.
- Check free models.
- Add server security where you can.
This is my biggest post so far so please correct me on any mistakes and feel free to add your own methods/opinions below!
Thanks for reading,
dand
P.S: I will edit this post from time to time so please suggest stuff to add