Insights on exploit prevention

Absolutely not. I am a strong advocate for server-only checks because it teaches not only good practice but helps you with catching failures as well (checks don’t just stop exploiters, they also regulate the client as a whole). I personally do not put any kind of checks on any of my client-side code because I feel that it’s a waste of time. I value productivity over trying to stop the client on their own device.

1 Like

You can disconnect any RenderStepped connection if the exploit has a function for it.

1 Like

You don’t need to delete the script to stop an anti-exploit. It is possible to stop any and all client protections, and as colbert said it is not a good idea to shove everything into one LocalScript to “stop exploiters.”

I’m not exactly fond of the example you’re using for server authorization. That’s not to say server authorization isn’t important; server authorization is the very culmination that will make or break exploiters and it seems that in the example you gave you are doing the former. I’m not sure if this was because of the simplicity implied by the example or I misread, but I’d like to maybe clear something up regarding your example should a reader pass by and attempt to apply it to their game.

Where you stated your server example, I noticed that there is not a check that actually verifies if the points to spend is matching with the strength that is being requested by the client. Hence I would devise a slightly more secure option in which the client would request a strength upgrade via RemoteEvents (or RemoteFunctions if you’re feeling that route) and requesting like so:

RemoteEvent:FireServer("Strength", 1)

or for RemoteFunctions:

RemoteFunction:InvokeServer("Strength", 1)

This code would indicate that the player would like to increase their strength by 1 point and from there we would move onto your first step which correctly verifies the Strength trait. From there I would deviate from your method and rather calculate the cost for 1 strength point on the server rather than the client via storing the price for each strength point on the server (e.g. StrengthPrice * 1; or something of the sort). This would ensure that the price of the strength is not being manipulated by the client and rather the server evaluating the cost. From there we would then check if the player has enough points to spend on such a strength upgrade and if they do we can then process the request as per the rest of your bullet list. If they do not it is up to the programmer on what they want to do with the request (e.g. return via RemoteFunction “You don’t have enough points!” or fire some notification event to the client with the same content).

Apologies if I’m coming off as nit-picking or I totally misread something in your post, just something that caught my eye. Just looking to improve. :smiley:

1 Like

It’s most likely the simplicity of my example that might’ve caught you off guard, or a misconception. Almost exactly what you posted is what I was suggesting should happen. In regards to adding attributes, it’s a more simplistic system than actually having a >1 cost value. If you’ve had any trouble interpreting my response, let me know and I’ll try to clear up what I intended to say.

In some typical RPG games, each level you attain gives you a few points (3-4 or whatever) that you can spend towards increasing a certain stat. Each point you spend subtracts from your overall balance and adds one point to that stat. The value of that stat is usually applied as a multiplier against a increment-per-point value to be added to the base, sort of like y = mx + b form where y represents the final, m represents the increment, x represents the points allocated to that stat and b represents the base value (usually, b is placed first though, so y = b + mx.

e.g. User starts off with 100 health (default Humanoid health). Per point allocated to Durability, they gain 10 extra health. Humanoid.Health = 100 + (10 * PointsInDurability).

The client-side is merely used for input and requesting that the server perform an action. The request is made via the remote and it passes the stat to upgrade as well as how many points to put into that stat. The server then processes that request and sees if it should be made, using checks on its own end. If the arguments pass those checks, the increment is performed.

1 Like

I understood the concept of what you were doing, just the method that you used when you sent the server request with the points to spend seemed a bit insecure. Reason being that the server should be storing the amount of points a player has and how many they should be spending on an upgrade. This led to my reply in which I said that you should only request the “Strength” and the amount of points you want strength to increase by and then the server would calculate the points to spend by using some kind of function relating to the cost of the strength points requested by the client (StrengthPrice * (Strength points requested via remoteevent)). More or less I was just stating that sending over the points across the client → server boundary could result in exploiters maliciously editing it and providing false information to the server on how many points to spend.

No, that’s not what my reply entailed. The client doesn’t have any authority over costs or balances, they simply put in a request of how many points to be allocated. The server handles everything regarding cost, up to storing the literal value and evaluating the cost should the request be processed.

The section of my example that showed the client sending a cost value is a request, not an authoritative communication toward the server. The client would only need to see the cost or balance for display purposes or for minimising client input delays.

The client can still request any number of points to be spent towards a certain stat. If they have enough, there’s no reason why the request should be declined.

3 Likes

Ah, it was a misunderstanding then on my party. I thought in your example the client was defining the cost for the point upgrade which made me reply.

2 Likes

I suggest that instead of scripts getting deleted, have anti cheat local scripts hidden like you know you could use remote functions to run codes inside of the local script and like to get the serverinvoke through the ServerScriptService, my idea may seem lame, but I think this should clean the path.

And btw, doesn’t people who dont have access in replying and only view. Can’t they also seem to know our plans? Im a bit frightened.

And btw, doesn’t people who dont have access in replying and only view. Can’t they also seem to know our plans?

Yep, and that’s why any kind of client-sided exploit detection is pointless in the long run. Focus on server-sided exploit detection and protect your remotes with sanity checks. This is not security through obscurity unlike the client-sided exploit prevention.

1 Like

You can’t really hide any code running on the client from exploiters

I don’t know what you mean. You cannot use loadstring on the client, and if you mean using a lua vm like rerubi or adonis loadstring, then it’s even worse because the exploiter could intercept the source and edit it as they wish (i.e. set to empty string)

It’s not like exploiters sit here 24/7 and check new replies to all exploiting-related threads lol. In fact, most of the time when they want to exploit a game, they start without any knowledge of what checks it has, whether they are clientsided or not, how the game system works, etc. and yet they still end up figuring something out so yea.

1 Like

Look, I have one anticheat on my client and its mainly to dissuade script kiddies.

The first step to making an effective anti exploit is to accept that your main goal is to remove script kiddies, big bois are taken down temporarily and likely wont be there unless your game is insanely popular.

Here’s what I do for my local anti exploit, the most I do is make sure the humanoid is not in an invalid state, then make sure no stats are wack. Maybe an anti fly, but that’s getting fancy and useless.

After that, I use a remote function that returns a simple value to the server that relays the local anti is alive. If it doesnt relay the value the player is kicked. On the server the remote function is pcalled and applied a wait of 12 seconds (9 seconds is how long the roblox client had to reply before the server determines it got disconnected, and the extra 3 seconds are there for safe measure).

Yes it’s most likely useless for big bois, but this should get rid of some smarter script kiddies.

Yes it’s a waste of time for some people, but I just wanted to be thorough as well as I usually have time to kill.

No this is not the main focus for my anti system, this always comes last before anything.

I can probably make it more thorough by having a very important local script check for its existence and do some sanity checks there, but that’s for when I have more time to kill, and it’s not very important.

You can skip the local anti, it’s not gonna be effective past script kiddies.

As for server side, of course the saying goes “server sanity checks” so do those.

This should ALWAYS be your first priority when making an anti cheat. You can probably skip local anti cheats all together.

For detecting false positives, a strike system is fairly effective, of course this can also be bypassed by only doing an exploit once in a while, but at that point its useless. Make sure to degen the strikes, and it should work “well”

After that you do you.

1 Like

This is exactly what I try to explain to people whenever they say “client-sided anti exploit is useless, you shouldn’t be doing it”.

Your anti exploit system seems good. The fact that you account for infinite yield in a client invoke and stuff.

I have made a client-sided anti exploit myself aswell. It’s more of a hobby/PoC thing since I don’t have an actual game I could use it for, but here’s how it works if you’re curious:

wall of text
  1. The entire script is wrapped in two anti-decompile methods
  2. It’s a ModuleScript which destroys itself after running, and has an infinite loop at the end. If the loop gets aborted, the localscript that required the module will notice it and take action
  3. It doesn’t use remotes to send to the server, unless all other methods fail
  4. It also has a ping check with remotefunctions + return check (do something with the argument passed by the server, check if it’s correct)
  5. Common anti-exploits detections, such as game[{}] in xpcall, and some less known ones
  6. Trap variables (__tostring, String too long error)
  7. Verifying constants (strings) with ("").char in a loop
  8. Memory checks in a loop:
    • Checking if tostring(func) == cachedTostringFunc
    • Checking if the bytes of the above match cached bytes in a table
    • Checking the return of some functions
    • Checking their env
    • Checking if they are lua closures
    • Calling them as metamethods to detect any yielding (attempt to yield accross metamethod/C-call boundary)
  9. Miscellaneous checks related to DataModel’s metamethods, Instance.new("RemoteFunction").InvokeServer(workspace, workspace) and whatnot
  10. Comparing error messages of various stuffs
  11. Checking if connections were disconnected
3 Likes

I’ve never heard of that, could you explain to me how those work. It could help OP as well if he decides to do some light local anti exploits

These are particularly weird and I’ve never understood them, do you perhaps use hopperbin? I’ve seen that instance used around, but I never really understood its purpose.

Anti decompile/anti unluac are methods which cause the unluac.jar to get confused and spit out malformed code or crash.
For example the first method that I know let’s you hide a function completly from its output. It’s also the most commonly known method.
The second one causes it to output while true do while true do while true do endlessly until it runs out of memory/bounds.

Idk what you mean. I meant memory checks as in checking if any of my functions have been hooked (replaced) by the exploiter.

1 Like

It’s relatively simple for an exploiter with a script executor to obtain any scripts, or locations of items, that are able to be found on the client. The simplest way would be to load a pre-made explorer script and then, using that, do a search for scripts (if possible, I don’t know how well explorer scripts work, I’m not exploiter, I just know they’re a thing since I’ve used one before on my own game when I was starting out), select and copy the script you want and then paste the script into studio. Then its just two clicks and you can see how that script works. Having separate scripts on the client, one for functionality and one for exploit-checking isn’t a good idea, in my opinion - if you have the exploit checking script integrated within the functionality script, it’ll cause players to be discouraged in relation to deleting the script, as by deleting it, they’re technically deleting the object that they wanted to access in the first place.

Also why you make all your main checks server-side and NEVER take checks from the client - if you must take client checks, make sure to communicate these checks to the server and double check it there as well.

1 Like

Yes… that’s basically what I said…

That’s uhh, not how it works. Exploiters need to decompile the script first, that is grabbing the bytecode and running it through unluac.jar to turn it back into lua code. They can’t copy/paste it to studio without setting clipboard’s metadata (or whatever studio uses) to the xml data of the script instance.

Well, it’s not like they can in fact “delete” a script in the first place. Once a script is running, there isn’t really a way to stop it. You can at most set its Disabled to true, disconnect its event connections, and erase its variables, to “emulate” it being deleted.
But you can do that to a certain part of a script aswell. That’s why it doesn’t really matter if the anti exploit is inside the main script or not, because exploiters almost never disable any scripts when exploiting a game anyways.

1 Like

That is how it’s work, I’ve tested a few of these scripts in the past in order to find out ways to counter-act them, as well as see if my current scripts do so, you can legitimately copy and paste the scripts in, you can also delete them using the explorer script, whether that stops the script or not depends on the content of the script and properties.

For clarification, when I say explorer script, I mean a script that replicates the abilities of the actual studio explorer as well as more features through the usage of buttons rather than keys, such as delete, copy, paste, etc, it also comes with a property script, if I remember correctly.

The script’s thread will get stopped, but the functions it created will still exist, including the anonymous ones used when connecting to events. That’s why i.e. RenderStepped:Connect(function() end) will continue to run.

Actually I can’t remember if roblox made all events pause when you disable a script, will have to check it later.

Again, Dex explorer CANNOT copy/paste instances to studio. Its copy feature uses a fake clipboard - a variable!!
It’s open source so you can check that yourself if you want

1 Like

Well it worked for me in the past, perhaps I’m just explaining it incorrectly? It was 2-3+ years ago, Roblox may have changed it since then, or I’m misunderstanding how it works.