How to find exploits in your own game

This is a tutorial on the creation, usage and patching of exploits in your games. The tutorial will go in-depth into attempting to find things that are exploitable, attempting to patch those vulnerabilities, and what that takes you to do so.

However, first things first:

Disclaimer

First things foremost: We will not be using a REAL Roblox Client Luau executor; they’re overall a bad fit for the following reasons:

  • We are game developers; we are better off not fueling the cheat industry.
  • REAL executors are not guaranteed to be safe; most of them are heavily protected and packed, featuring protections, authentication, etc.
  • There are too many exploits, but far too few of them are actually decent for anything other than ;ws 200 on Infinite Yield (if it runs at all, that is).
  • To test our ‘cheat,’ we will have to constantly publish, rejoin, and execute. Some client cheats are far too unstable for this, crashing on re-joins.

For those reasons, I’ll be limiting myself to RbxStu V4, which is a Roblox Studio execution tool and nothing else. You may follow along with whatever tooling you like. However, remember I do not encourage using client cheats for this tutorial.


Finding exploits

First of all, let us begin with how an exploiter finds vulnerabilities in our game. Exploiters have two workflows they can go off of when developing exploits:

  • Dynamic Analysis (Can only be done on Internal exploits)
  • Static Analysis (Can be done on most exploits)

First of all, what we are going to do is a form of reverse engineering. We are going to be taking apart our own code’s behavior to find potential flaws in it. This is the core piece of everything in exploiting, picking apart the behavior of it and finding things that may lead us to something that:

  • Breaks our game, making it unplayable (fills it with errors, a Denial of Service if you will)
  • Data Rollbacks (Breaks datasaving functionality)
  • Allows for our client to affect other clients (FilteringEnabled ‘bypass,’ also known as an FE bypass)

These categories are oversimplifications; however, they help us simplify and categorize the issues we may face and how we can fix them.

During this tutorial, we will be mostly making use of dynamic analysis to find exploits. Static analysis is supported by my tooling; however, it is not as entertaining or as fun to do as the first, as we are simply decompiling the game to get an (almost) accurate representation of our game code and attempting to find anything from just it alone.

What is Dynamic Analysis

Dynamic analysis is when we run the game and we attempt to analyze its behavior. We are basically debugging the game outside of the studio tooling, if you will. The focus in finding exploits is mostly just throwing stuff and seeing what happens. Most, if not all, games communicate using RemoteEvent or RemoteFunction instances. With them, we can send the server information to represent changes in state and update the client (i.e., replicating visual effects, sounds, …).

And we can keep track of all these event calls using hooks, but what is a hook?

Exploit Functions

Exploiters have a variety of functions that can be used to modify the state of the game; many exploits integrate the ‘Unified Naming Convention,’ also known as UNC. It is a way of naming functions and taking arguments set forward by the developers of a now defunct exploit called “Script-Ware V2.” The convention integrates a wide variety of functions and some libraries that you will need to perform dynamic analysis. Some other aspects of the convention focus on sections such as cryptography and the file system, which can be used in a number of ways, such as making a script whitelist or saving configurations.

One key function is hookfunction and hookmetamethod, the latter being a specialized version of the first. The key takeaway here is what it does: it modifies the function you give it to call another one. This allows it to intercept arguments and manipulate return values; here is an example of it:

local function hookTarget()
    return "Hello, I'm hookTarget"
end
local originalHookTarget
originalHookTarget = hookfunction(hookTarget, function()
    return "I'm no longer hookTarget..."
end)

print(hookTarget())         -- I'm no longer hookTarget
print(originalHookTarget()) -- Hello, I'm hooktarget

The function returns a new function that represents the original one; it can then be called to execute the original function if needed.

hookmetamethod is a specialized hookfunction, targeted at hooking the metamethods inside of metatables. You may read about these two functions on pages that contain the documentation for these functions.
RbxStu has its documentation for its functions in the following places:

sUNC (sort of continuation for UNC) has the documentation for these functions in the following places:

I encourage you to read some of these documentation pages, mostly sUNC’s, as they’re used on mainstream cheats and can give you an idea on the way they function and their features.


Hooking to Victory

Hooking to Victory, if you will, is what we are going to be doing, but we need to get to know a little bit of the internals of the engine, although they’re really high level.

  • All engine structures have a unique metatable.
  • When you obtain an instance (i.e., game.Workspace), the instance is returned from a cache, which we can manipulate to point to other instances.

This means that, by hooking the metatable of game, we can instrument (that is, track) all calls, indexes, and other things that go on with all engine objects. This is also true for non-instance objects, such as EnumItem.

With this information, we can, say, track when the WalkSpeed property of a Humanoid instance is changed, and if so, block it.
Example

local old 
old = hookmetamethod(game, "__newindex", function(self, idx, newValue)
    -- Argument validation (Important to prevent errors)
    if typeof(self) == "Instance" and self:IsA("Humanoid") and idx == "WalkSpeed" then
        return -- Do nothing; We don't want to allow anyone to set the WalkSpeed of ANY humanoid instance
    end
    return old(self, idx, newValue) -- Call original/ other hooks made before this one
end)

I remark that this is an example hook; you may detect it fairly simply by, for example, creating a fake Humanoid, changing its WalkSpeed, and retrieving it. Then it must be equal to what you set it to, for example.

You may notice I explicitly denote argument validation. Metamethods can be called with anything, including no arguments, garbage arguments, etc.

Thankfully, most implementations of hookmetamethod contain an argument guard, which is a ‘safety’ feature that prevents calls into your hook if the arguments appear to be invalid for it; however, erroring inside of the hook with a wrong error exposes us to being detected, as then it will appear with a different error instead of the normal one.
We use this to our advantage in a detection for these kinds of hooks, in fact! However, that is later down the line.

Tracking Client <–> Server communication

Using metamethod hooks, we can hook into both __index and __namecall, modifying their behavior to snoop into everything that the game sends to the server. This is what remote spies such as Hydroxide and SimpleSpy V3 do: they keep track and generate code for you to replay those events of being fired. This way you can replicate a game event without depending on the game code at any point.

There are a myriad of examples of vulnerable games out there, yet to be discovered because they assume that the event will only fire at a specific time, which is not true for cheaters! They can fire the event at any point with any arguments.

For this aspect, and because it is the simplest to explain, let me begin with a Data Rollback.


Data Rollback Exploits

Data rollback exploits occur when a cheater (or you yourself if your code is rubbish) begins telling the server to try and save invalid data into the server. The invalid state can be a myriad of things, but here are a few:

  • 0/0, also known as nan or NaN.
  • Invalid UTF-8 strings (which you validate using the utf8 library methods).
  • most userdata objects.
  • Strings that are massively long.
  • Massive tables (REALLY big tables).

Most of these occur due to poor validation; however, we have come up with solutions for these kinds of issues, namely DataStore Session locking.

DataStore Session Locking fixes this issue by, very figuratively, holding the data of the user until it can be saved, locked in one server. Because of this, if you join another server and your data is ‘locked,’ you can do some of the following:

  • May not save
  • Will refuse to let you play
  • Join you back to the original server.

However, the module par excellence for the sole purpose of DataStores is ProfileStore by loleris, the continuation of ProfileService. The module uses the previously mentioned measures to prevent data loss, which is essentially what a data rollback attempts. By feeding a remote that may manipulate user data invalid arguments, you can mutate the data that is saved to be invalid, thus failing and not succeeding. It is such the basis of data rollbacks; it is simply forcing the data save to fail, which allows you to then make changes that will not save, at least until it stops being an issue.

Speaking with a fellow developer @cheapsk9, he himself already had an idea after jogging his mind about the strings issue, he argued that you needn’t use an executor, you could simply input invalid UTF-8 or a really big string and perform a rollback on something like pets. This is why it is important to re-iterate, you must not trust the clients inputs, always assume their inputs are nuclear waste.


Game Breaking

If the game is sloppily written and, perhaps, a RemoteEvent mutates data without validating that the data is appropriate, and then another thread that acts as a game loop reads the information and uses it, but because it is flawed, it crashes the game loop. This causes the game to stop working, as the game loop for your experience is now gone, the server instance is now, basically, useless.

Such is also the case when we allow the player to manipulate game state without limits. I recall this mention of a server crasher a friend found for a game he himself was pentesting, which relied in a remote allowing the creation of objects that were intensive to compute on the server. This way the server would simply just run short of resources to use during execution, hanging it, and simply crashing.

We can also consider game breaking if using it completely breaks the experience entirely. I recall an game which allowed you to play in pairs of two players. When you form a pair you can simply… spam the event. The event will basically fill the screen of other players with invitations, however due to the sheer amount of them, the client that gets the invitation begins lagging. The same can also be true for another feature of the game, that allows you to virtually ragdoll each and every player in the game, without validation. Not even checking if they were in your game team, something we can categorize a whole FilteringEnabled bypass, since the player does not need to do anything. Just by the mere fact the exploiter is present, the player can suffer from them in a way that completely breaks the experience.


Now; back on track we go with the explanation that we were originally at: static and Dynamic analysis.

This is a graphical representation of what we want to do:

In short, we want to test a system, formulate a hypothesis on how it functions, and if we believe it could be used in any way shape or form maliciously, then try to do so to document, patch or save the vulnerability.

However, I recommend you test lots of systems. Sometimes you can chain systems to bypass restrictions due to edge-cases that you may handle, I recall that of a game that had WalkSpeed validation, however due to the way the game works (where the player seats somewhere and you move at an unknown/ undetermined speed), the magnitude checks related with the WalkSpeed were disabled. Becuase of this, simply test, see and ask yourself how it could be misused for purposes that aren’t expected, this can be compared with the age old ‘Programmer and a Bar’ joke, where the programmer accounts for a bunch of weird cases, but misses the one that looks ‘unrelated’ to anything else. Of course it is meant to go as just a joke… right? Sadly that is not the case, there are many games on the platform which suffer from cases like these. Most of the stories (if not by saying all) that I have been saying throughout the thread are legitimate stories, of games that simply had these set of issues, and I have seen exploited on script hubs (basically, a collection of cheats for games), mostly rooted from mere incompetence of the developer.


Summary

Let us sum up everything we have seen on this thread:

  • We have attempted to understand the tools exploiters use.
  • We have seen when and how we can use the tools.
  • We have attempted to categorize these exploits in categories.
  • How we can apply this knowledge on our own games.

It is to be noted of course, that the simple fact we create our own game makes it so it is harder to test, since we already ‘know’ what it is about or how it is like. I have helped a few friends and after teaching a little bit of this methodology they have been able to fix bugs that they would otherwise perhaps not have seen, and could have been exploited on the wild, this is not helped by the fact that most script hubs and scripts are obfuscated, making the source unreadable and reversing them more complicated.

I hope this posts is useful to anyone trying to make a game, or if you are making a game and you have problems with exploiters how to perhaps deal with them. I hoped to include some content regarding general-purpose cheat detections, however @HexadecimalLiker has made a great job providing resources on the wild for this kind of thing on posts such as His guide on securing an anticheat, as well the post in which he releases some detections against cheaters.

Credits and Tools used:

Remember that all the contents of this post are done for the purpose of learning and spreading awareness. Many scripters think ‘oh it is easy’, and make a system with flaws etched deep within, such as in DataStores. It is understandable, however. Roblox themselves do not provide a good way to handle DataStores, leaving us, the users, to fix it. But if there is anything to take away from this post is the following

When you are programming, program defensively, never trust the users input or any information the user itself may provide to you. You should assume anything that comes from the ‘outside’ (the other party) is a trojan horse, for lack of a better term. Always check your event arguments.

Anyway, this is the way I wrap this community tutorial up,
Cheerio.

If you are a scripter, and your first instinct is to say “Cheaters can just delete the script”, don’t. You are not contributing anything to the post. There are more methods and ways to prevent these issues, such as simply not banning immediately, or following the tutorial Liker made on protecting anti cheat systems.

This time there is no quote :>

8 Likes