Concept: Client Anti-cheat possible?

Honestly, Client Anti-cheats are required. Completely. Server anti-cheats usually damage performance and make gameplay more difficult.

The greatest issue with Client Anti-exploits is that it is easily bypassable, but is it really? There are a few ways to deal with exploiters using purely client anti-exploits.

  1. You require a vLua module (The one I use), or, whenever Roblox allows load string for clients.
  2. You need one script in ServerScriptService and a Localscript in StarterPlayerScripts.
  3. Make a RemoteFunction in ReplicatedStorage.
  4. In the server script, make an array of different script snippets as a string. Preferably one’s that return unpredictable values. Then send it to client via RemoteFunction.
  5. In the client, use the vLua in step 1, and send the info back to server.
  6. Have the server check this information, and kick the player if the info is not possible without hacking.
  7. Also insert some random snippets in the code, that return something random, and kick the player if it doesn’t return correctly. So the exploiters can’t predict what snippet they will get. (Making it difficult to spoof)

This is just a theory, it may take some time for me to apply it fully. The exploiter could also spoof the checked values, but that adds a huge layer of complexity that would hamper all but the most experienced exploiters.

13 Likes

What would prevent someone from SaveInstancing the place, going into the decompiled client side anticheat script, removing everything in the script not relevant to telling the server that the client anticheat is still working, saving the script in a text file, loading up the place again, deleting the client side anticheat, and executing their spoofed version?

Remember that RemoteSpy exists too. Experienced exploiters could also write their own script to require a virtual module, execute whatever the server throws at them, and send it back to the server. They could then delete the client anticheat and the server would have no idea. These people aren’t banging rocks together.

The server has all the programs. If they saveinstance, they will only get the client. If they disable the script. The remote function would return nil, instantly exposing the exploiter.

Experienced exploiters could, but they usually won’t spend the time doing that unless they are absolutely obsessed with the game, and willing to spend hours testing the remote. Not to mention that they would’ve been able to bypass any other anti-cheat much more easily if they already have that sort of skill. vLua is extremely difficult to remake without extensive scripting knowledge

Experienced exploiters absolutely do become obsessed with games and will absolutely spend hours testing the remote. Never underestimate these people. This setup could hold people off for a few months in a game with over 2,000 players, and then someone would come out with a script, the dam would break, and overnight you’d have hundreds of exploiters in your game. Not good.

7 Likes

Indeed, that is certainly true; But that holds the same for any other anti-exploit. This one is honestly preferable because you can easily change the script snippets in the array every update. Making it much more difficult to bypass.

Maybe I’m missing something regarding vLua. What’s stopping an exploiter from just requiring that same vLua module and running anything the server sends through it, and sending that back to the server as a way to spoof?

I’m assuming vLua returns a function which can be executed, if so, an experienced exploiter could easily bypass this but, assuming that’s not the case, the client still has complete control over their Roblox instance and, there are many other functions/methods they can use to bypass the anti cheat described above.

Remote events can also be easily exploited using function hooks and metamethod hooks and an exploiter could then spoof the value returned; assuming they’d need to execute the code to get some value, they could even spoof other values so they can find out the correct value to return by checking if some event was fired by an exploit.

Because they can’t predict what the value is for. Making it impossible to know the exact one to use. vLua interprets code, not converts it to english. They could access the bytecode, perhaps, but then they need to do bytcode hooks for every possibility. While developers can just make thousands of tiny checks and etc.

Indeed, if they spoof the value, they can have a free ban. It is incredibly difficult to parse strings to the point where you’d know what the code is looking for. The script could look for Jumpspeed for one fire, and look for walkspeed next. Then, every once in a while, it spams a “return true” loadstring to ensure that the client didn’t hook the remote.

They could execute some code in a sandboxed environment so it doesn’t actually affect their real game environment.

…Sorry? I don’t quite understand. It’s not possible to make a sandboxed environment. The only possible bypass is to spoof the Values if it comes from a localscript. Besides that, allow me to clarify that I don’t expect this anti-exploit to cover all possible exploits. This is simply a huge limitation against less experienced exploiters, and extremely time-consuming for experienced ones.

They could have a virtual workspace using tables and metamethods (e.g. __index).
Example:
Replacing the global game with a table which has a metatable; when game is indexed, __index is called (assuming it’s a function and, a property wasn’t indexed for) which will return another fake object (a table) which can also be indexed.

game.Workspace... -- since the global 'game' has been replaced with a table, we aren't actually indexing game
local Metatable = {}
Metatable.__index = function(self, index)
    ...
end
local game = setmetatable({}, Metatable) -- a separate table could be used to store other tables which will represent the children of `game`, these separate tables could use the same metatable as game.
game.PROPERTY = SOME_VALUE -- if a script indexes 'game', we'll check if it indexed for a property first

I do agree, it could be a little time consuming at first but, once an exploiter has made a module to create a virtual workspace, they won’t need to create it again for each individual game since it should be reusable in almost every other game.

I illustrated why I still think this won’t work very well.

3 Likes

I just realized actually that you meant you would run the checks in the sent loadstring.

I guess that is pretty effective but now I’m concerned about it’s usability.

It would work good for walkspeed and force injection exploits, but what about deleted parts? Noclipping? You would be sending a huge function every 60 seconds to every single player? Is this viable? Maybe it is.

Yes, deleted parts can be done through connections and Noclipping.

We aren’t exactly sending “Functions” per se. We are sending scripts that will be parsed.

They need to account for every check. I’ve actually made an exploit bypass before with an executor. You have to make a bypass for every possible string.

I’m not very knowledgable with metatables. In this situation, is there a way for the client to check if game has a metatable set to it, or to overwrite the current set metatable?

1 Like

Not possible. The security level for localscripts are too low. Most exploits are security level 6, and 7-8 in rare cases (Synapse X or Script-Ware)

It would indeed be useful if developers were able to have higher security permissions. Still, it would be a security issue if we can access metatables (Force purchase gamepass, etc)

Yeah, it’s extremely easy for exploiters depending on the “level” of their exploit, one of the most popular exploit also known as Synapse has many functions and some of them allow you to overwrite metatables by unlocking them and locking them.

I think you read his question wrong, pretty sure he’s asking for how to detect it as a developer.

and in Synapse X, the code would look like this:

-- // Metatable \\ --
local RawMetatable = getrawmetatable(game)
local OldNameCall = RawMetatable.__namecall
setreadonly(RawMetatable, false)

RawMetatable.__namecall = newcclosure(function(Object, ...)
    local NamecallMethod = getnamecallmethod()
    local Arguments = {...}
--Stuff here

    return OldNameCall(Object, unpack(Arguments))
end)

setreadonly(RawMetatable, true)

Whenever I go about making an anti cheat here’s the first thing in mind

Step one:
Understand that exploiters are going to bypass any anticheat

Step two:
With step one out of the way, what can I do to create an anti-cheat that can expose the basic exploiters.


Personally I like to do a “fake remote value”

local adminRemote = ReplicatedStorage.AdminRemote

local admin = false
local command = "kick"

adminRemote:FireServer(admin, command)

on the server

adminRemote.OnServerEvent:Connect(function(player, hacker, command)
    if hacker then
        -- ban them
    elseif player:GetRankInGroup(123456) > 10 then
     -- do command
    end
end)

Personally this is what you should be striving to do, and you can catch some people in the process :stuck_out_tongue:

3 Likes

Alright, so our new problem is fake workspace detection.

Is it performant to attach .DescendantAdded and .DescendentRemoving to the DataModel, and .Changed events to every single instance in the workspace to keep this table maintained and up to date?

If it isn’t, that leaves a possibility to change some random model’s name somewhere, ask the client if they saw it, and if they didn’t, bingo. Fake workspace.