Introduction
You might be thinking what is this dude talking about? In general, there are vulnerabilities most developers don’t know about, and they aren’t really documented. Basically, it’s UTF-8 and has an Infinite number of vulnerabilities, which if exploited can cause hackers to have infinite currency in your game or rollback/corrupt their data which they can use for dupes and other data-related exploits.
(NOTE THAT I WROTE THIS PRETTY RUSHED SO PLEASE EXCUSE ANY SPELLING MISTAKES)
How do they work
UTF8 Unicode
Let’s start with the UTF-8 Unicode exploits, just to set a scene we have Billy and Timmy both have a game with pets and want to add renaming, but Billy uses proper practices against these Unicode Exploits, and Timmy doesn’t lets see what happens when a hacker figures this information out.
The remote event sent might be something like this:
{
["PetID"] = 0,
["Name"] = "Name"
}
For a normal request it’ll send the name in as a normal string, but an exploit might send a request like this:
{
["PetID"] = 0,
["Name"] = "\255\231"
}
Seems like a normal request right? Well, not those data are Unicode that Roblox will automatically translate, when I print that exact string I get:
This is pretty bad because it’ll hit the maximum data save size for Roblox and error and not save their data at all. (Scary right)
The above explanation could be exploited, if you cache the player data and save it at selected intervals and use the cached data for any edits and save on the player leaving/certain intervals (basically ProfileStore/ProfileService) so it won’t corrupt instantly until you save on a datastore. So the hacker could send a trade with the data with the malicious Unicode saved, and trade the pet they want to duplicate to an alt or such. Then they’ll leave to trigger that save which will corrupt their data rolling them back to before the trade meaning both parties have the same pet.
Billy’s Approach to fix this:
So let’s go back to Billy how Billy’s system works, is that it checks the string for any Unicode character above 127 bytes and if it is he sanitizes it from that Unicode and then saves the data. (Example Modules below), this works because if you detect the specific Unicode characters that’ll break Roblox, then you’re safe as long as you remove them before saving.
Timmy’s Approach to fix this:
Well since Timmy on the other hand doesn’t know how the dupe works, he might just disable trading from his game until he figures out, or maybe even indefinitely (which will lose him engagement), or maybe he’ll get scammed out of tons of Robux by exploiters telling him they’ll fix the exploit. This might go two ways, they fix it, and boom no more dupe but Timmy doesn’t know how it works still or they just take the money and scam him.
Infinite Numbers:
Let’s go onto how infinite number exploits work you have a system in which you buy Eggs or whatever for your game, but your game also sends the amount of eggs to purchase (which an explorer can put in a negative infinity to exploit).
The remote might look something like this:
{
["EggID"] = "BasicEgg",
["Amount"] = 3
}
A remote and exploiter might send in would be like this:
{
["EggID"] = "BasicEgg",
["Amount"] = -1/0
}
More examples of numbers exploiters might use:
0/0
inf
-inf
NaN (or any variant)
0/1
math.huge()
So why do these cause infinite numbers? Well most of the examples like -1/0 or 0/0 cause division errors, which Roblox takes in as infinite numbers (weird but whatever). The inf one is what Roblox uses for the math.huge()
function if you ever tried to print it you’ll see that. And NaN means “Not A Number”, which is caused when errors with math (like the said division one) are inputted.
Why do these work? Think about it like this if you are doing multiplication to get the total cost of the eggs and a negative infinite is sent then your price is negative and it infinite, so when it checks the price it’ll 100% be less than or equal to the amount of money the exploiter no matter what if not dealt with properly. So it passed the price check now we gotta remove the price from their money, and guess what happens when you subtract a negative? It becomes positive so it’s like you running += math.huge()
on the hacker’s currency. Boom they now have infinite money, all because they sent in a negative infinite value.
Billy’s Approach to fix this:
So our good friend Billy and his good practices might do one of two ways, he will either check if the amount is negative and make sure to use the absolute value, or he might want to detect the hacker so he’ll just do the same thing, but also use whatever his logging system to log negative values.
But what if Billy’s system needs negative values? Then Billy can just test for infinite values like this (modules sourced below) he can take the absolute value of the number, and test if it’s equal to math.huge()
Timmy’s Approach to fix this:
Well, Timmy over here might not know how all of this works, so what he might do is run a check on the amount of currency if its NaN or such when the player joins (hopefully not on the client), but let’s say Timmy the beginner scripter is doing that and sending a remote to the kick the player, well that sucks for Timmy because guess what his client-sided code will be bypassed by 90% of exploiters. They can either block the remote from firing, change the value they are checking on the client, or so many more bypasses (this is the reason you shouldn’t check for these types of stuff on the client.
Code to help you fix it!
(Please note I made these very quickly with little testing make sure to test before using them in your game production)
UTF8 Translator
UnicodeTranslator.rbxm (934 Bytes)
What this does is that it takes unicode and encodes them from “\255” into “/255” and so on so you can save it as a string, then be able to decode it into Unicode (great if you want to save names and want support for other languages/non-ASCII characters (like Chinese).
Source Code
local UnicodeTranslator = {}
function UnicodeTranslator.Encode(str)
local encoded = str:gsub(".", function(c)
local byte = c:byte()
if byte >= 127 and byte <= 255 then
return "/" .. byte
else
return c
end
end)
return encoded
end
function UnicodeTranslator.Decode(str)
local decoded = str:gsub("/(%d+)", function(num)
local n = tonumber(num)
if n and n >= 127 and n <= 255 then
return string.char(n)
else
return "/" .. num
end
end)
return decoded
end
return UnicodeTranslator
Assertions Module
Asserts.rbxm (1.1 KB)
This module helps you validate UTF8 and Infinite Numbers, basically for the number validation you input the number to validate, and it should return a bool if infinite or not (True for infinite, false for not)., for the String validation it first returns a Bool (true if Unicode over 126, or stuff that’ll break the data, false if not), and then the sanitized string (hopefully I didn’t test this part properly)
Source Code
local AssertionModule = {}
function AssertionModule.ValidateString(input: string): (boolean, string)
if type(input) ~= "string" then
return false, ""
end
if utf8.len(input) == nil or #input > 255 then
return false, ""
end
local sanitized = {}
for i = 1, #input do
local byte = string.byte(input, i)
if byte >= 32 and byte <= 126 then
table.insert(sanitized, string.char(byte))
end
end
return true, table.concat(sanitized)
end
Function AssertionModule.ValidateNumber(input: number): (boolean)
assert(type(input) ~= "number","Inputted value is not a number")
if input ~= input or math.abs(input) == math.huge then
return false
end
return true
end
return AssertionModule
EVEN WITHOUT THESE TECHNIQUES YOU SHOULD ALWAYS FILTER USER-GENERATED TEXT
made with by sfgij