Understanding method hooking and keeping your game protected

(august 2018 revision: updated this post to be less… annoying, also moved to learning resources)

intro

script injectors are programs that allow exploiters to run their own scripts inside of roblox’s lua environment. this is bad for a lot of reasons because exploiters can:

  • steal pretty much any asset that appears on the client
  • abuse devs’ uninformed network coding practices to exploit FE games
  • acess and edit the source of any script or module on the client
  • download the entire clientside datamodel as an rbxl

as roblox grows, exploting games is going to become more of a problem. not only will the amount of people using exploits increase, so will the amount of competent people working to make new exploits. since the problem stems from lua itself as a programming language, and its unreasonable to expect roblox to completely restructure the entire security system from the ground up, it’s up to us developers to use more responsible coding practices to keep our games safe.

protecting your assets

because these id7 exploits have become so accessible, from now onward, you should make all of the following assumptions:

any asset that appears in a game published on the site is no longer safe
many of these injectors include some sort of function that allows exploiters to save instances to the disk, any asset in the clientside datamodel can be stolen. personally, i dont think leaked assets are that big of a problem because most of us have the integrity to not use assets stolen from other devs’ games, but others may find it much more upsetting. to lower of the chances of massive asset leaks, store all of your models or maps in the server storage rather than in replicated storage. for models that need to be accessed by the client, clone all of the assets in the replicated storage, delete the originals, and hold references to the cloned models in the script. accessing the cloned models in the scripts memory will be out of the question, as the small amount of competent v3rmies actually capable of doing so probably dont care enough to try. anyone trying to steal your assets wont have any choice but to wait until they actually appear in the workspace

any code in a game published on the site is no longer safe
the same methodology for stealing assets applies to stealing scripts. to keep your code safer ( that is, a lot more inaccessible, but not completely safe), you can place all of your main code in modules. after you require the modules, delete them; the code will continue to run as long as you dont delete the script used to require the modules. this should keep your code more protected, but the lua bytecode can still be decompiled, albeit in a much less readable format. to take this a step further, you can use an obfuscation program to make your code even harder to read (but still not absolutely safe).

while we can make our games less vulnerable with better network code, there’s nothing we can do at the moment about stolen assets. in the future, i think roblox should penalize devs who are caught with stolen assets, but that involves a whole different slew of politics that i dont want to get into right now. at the moment, asset stealing really isnt a problem. if it becomes a problem in the future, we can hope roblox will step in with new policies to prevent it from happening.

method hooking and other ways script injectors are used to exploit FE games

preface: a brief intro to metamethods and metatables

metamethods are special functions that are invoked when specific operations are preformed on a given table. for these functions to be invoked and treated as a metamethod, they must be assigned to a specific index inside of a metatable. that metatable is then assigned to a table with the setmetatable() lua function.

the __index metamethod

the one particular metamethod we care about in this case is the __index metamethod, which is called when a value is indexed from a table that doesnt exist. if the table’s index metamethod returns a value, rather than returning nil, the index operation will return the value that was returned from the function:

local mt = {
  __index = function(self, index)
    print("__index invoked")
    return index.." made money off of the iraq war"
  end
}

local our_boy_table = {
  ["george bush"] = "an alright boy"
  ["al gore"]     = "certified goodboy"
}
setmetatable(our_boy_table, mt)
print(our_boy_table["dick cheney"])

> __index invoked
> dick cheney made money off of the iraq war

(this is a simplification for the sake of readability)

the instance metatable

every instance in roblox shares the same metatable. (why? idk. ask urist or something). from my understanding, this metatable has a generic __index metamethod for returning the properties, events, and methods of instances:

metatable detouring (method hooking)

under normal circumstances, the instance metatable is completely inaccessible with the normal script security identity level. unfortunately for us, the .dll CE injection “scythe localscript exploit [PATCHED]” videos circa 2012 have now been replaced by annoying 15 year olds with bad hair that talk a little bit too loud showing off their latest and greatest very terrible murderprogram they just bought from a v3rmillion back alley using the dominus they scammed off of some other just as obnoxious preteen. because the metamethods of the instance metatable can be overwritten, exploiters can replaced the __index metamethod with their own function, which is called whenever any member, is indexed from any instance, in any script throughout the entire client.

the following code block is a hypothetical exploit written for the hypothetical game Gunshooter: Online. the script causes the exploiters weapon to automatically kill the closest enemy to him, as well as negate all incoming damage. gunshooter online only uses a single remote to handle all of the network code, and distincts between different events by passing a string through the first argument of the remote. the hypothetical exploiter figured this out by placing listeners on the remote events in the game with method hooking prior to writing this exploit. he could have also read the client code to better understand what the specific arguments of the remote events were doing

local instance_meta = globalgetmetatable(game) -- the exploiter uses the function in their script injector's enviornment to get the instance metatable
local old_index     = instance_meta.__index

local remote_event = game.ReplicatedStorage.RemoteEvent

--- godmode ---
local spoof_event = Instance.new("BindableEvent")
remote_event.OnClientEvent:connect(function(...)
  local event = (...)[1]
  if event ~= "TakeDamageClient" then -- prevent the take damage from being fired
    spoof_event:Fire(...)
  end
end)

instance_meta.__index = function(instance, index)
  --- instakill ---
  if index == "FireServer" then
    return function(...) -- replaces FireServer in the script it was called from
      local event = (...)[1] -- first argument
      if event == "NewBullet" then 
        local nearest_head_pos = ConvenientFindNearestEnemyHeadPositionFunction()
        local bullet_origin    = nearest_head_pos + Vector3.new(0,1,0);
        local bullet_direction = Vector3.new(0,-1,0)
        local bullet_speed     = 10000
        local bullet_damage    = 10000
			
        local FireServer = old_index(instance, "FireServer")
        FireServer(event, bullet_origin, bullet_direction, bullet_speed, bullet_damage)
      end
    end
  elseif index == "OnClientEvent" then
    return spoof_event.Event -- reroute incoming client events to our bindable spoof_event for godmode
  else
    return old_index(instance, index)
  end
end

the insta-murder part of this code can be rewritten to run in only 13 lines (i wasted a lot of space to make sure the code was more readable). a year ago i thought using FE was the end-all solution to exploiters, im not sure how many others still have similar biases, but i can say for sure that FE is definitely not that. in the next section im going to try to help establish some network coding best practices that will keep your games safe and away from ending up in a situation similar to gunshooter online.

method hooking quick fixes

you need to know: the solution is not keeping exploiters from method hooking. the client can not be trusted, and any solution that is implemented fully on the client can, and will eventually be bypassed. despite this fact, there are a few things we can do to make it more difficult for exploiters:

  • using syntax like this will allow you to call methods without invoking any hijacked __index metamethods. however, if the method is already hooked prior to setting the function to a variable, (for example, if you reset your local scripts), this method won’t do anything.

local FireServer = Instance.new("RemoteEvent").FireServer -- and whenever a remote needs to be fired FireServer(AnyRemoteEvent, Arg1, Arg2, Arg3, ...)

  • quick checks for detoured functions
local FireServer = RemoteEvent.FireServer -- no detour, original function
-- time passes
if FireServer ~= RemoteEvent.FireServer then
--personally i dont reccommend actually doing anything here noticable for reasons
--explained later
end
--=================--
-- some detour functions, like the example i used, return a new instance of a 
-- function every time they're called, which allows us to do this
if RemoteEvent.FireServer ~= RemoteEvent.FireServer then
end
-- this is super easy to bypass though, because all the exploiters would have to do 
-- is not pass a new anonymous function every time

good networking practices: authoritative server

the server authority model is where the server has the ultimate jurisdiction over the current game and client states. when a player wants to preform any kind of action in the game that would influence the game’s state, the player’s state, or the states of any other players, the server has to first verify if that action is valid before making any changes to any other players. in a survival game example, if a player wants to drop an item from their inventory onto the ground, the server first has to make sure that item exists in the players inventory, and then updates states accordingly to reflect that change.

server and client states: parallel, but not the same

there are right ways to do server authority and there are wrong ways. for the sake of simplicity, i’m going to keep using my example of the survival game inventory.

for foolproof reasons, let me cover the most wrong way to handle this. the client relies on its own logic to decide when items are added to the inventory. the server doesnt keep track of the client’s inventory state, and when a player drops an item this interaction occurs (c and s are client and server, respectively)

c: i want to drop a stick, here's the data of all of the items in my inventory s: you have a stick in your inventory. you can drop the stick s: there is a stick here now

this is bad for obvious reasons because the client can either change the data of his inventory or spoof the information being sent to the server and following situation could occur

c: i want to drop 1000 gold, here's the data of all of the items in my inventory s: you have a 1000 gold in your inventory. you can drop the gold. s: there is 1000 gold here now

the first step in ensuring that this doesnt happen is to keep track of the players inventory on the server

c: i want to pick up this stick on the ground s: the stick exists on the ground in this game state, and you are close enough to the stick to pick it up, and you still have enough inventory space to pick it up. you can pick up the stick s: <add a stick to player's inventory> s: for all players to whom the stick may concern: the stick is gone

while this is a step in the right direction, it’s still a flawed approach. we want our game to be responsive and safe. if someone is playing with 250 ping we dont want them to have to wait a split second every time they try to pick up an item. we need to allow the client to have a state that is parallel to, not reliant on, the server. heres how we do this

c: i picked up this stick c: <adds a stick to its own inventory> s: all of the conditions for you to pick up the stick have been met, you can keep the stick. s: <add a stick to player's server inventory> s: sorry to tell you this boys-- we're all outta sticks!!

client was still able to pick the stick up in their game state, but on the server the conditions aren’t met (the client is too far away, another player picked it up first, etc) we don’t add that stick to the server version of the client’s inventory, and we send an update to the client that their stick needs to be removed from their inventory. if an exploiter bypasses the update so they can keep the stick, they won’t be able to do anything with it. this is because we have our authoritative version of the client’s inventory on the server, and any actions the exploiter tries to preform with the stick will be invalid. this method of allowing the client to lean on its own logic and correcting it when it makes mistakes, as opposed to being fully reliant on responses from the server, allows us to keep the apparent effects of latency to a minimum while not compromising any safety. while this is all well and good for a simple survival game, more difficult problems arise when we increase the complexity of the systems in our game.

195 Likes

tl;dr: don’t trust the client

22 Likes

trust it most of the time but when iiHackYouXD kills the whole enemy team with a single mg36 clip with 100% accuracy headshots maybe somethings fishy

16 Likes

“Exploiters? No they’re just better then you.”

https://gyazo.com/8c0241d4482344311a26d03ac6157088

16 Likes

Edit: Meant to reply to @juddily

I don’t really have an issue with exploiters because I use authoritative server programming style. Client never tells you anything. Only asks. Haven’t had an issue with anything.

The only issues that get past this are speedhacking and noclipping which are fairly easy to detect and remove.

8 Likes

I use this. It crashes the program every time RC7 is opened with Roblox:

 game:WaitForChild("Script Context").Name = os.time()*math.random(-os.time(),os.time())
24 Likes

Are you saying this blocks the exploit? Or is this just a line that crashes a client after they run the exploit (that you detect using other code)?

No idea. Someone I know who exploits a lot told me this almost always crashes either the client or RC7. It may be outdated (I got it like 2 weeks ago) but even if it is, worst case scenario I block outdated versions of the exploit.

1 Like

That code isn’t that bad.


I’ve actually seen the RemoteEvent hijacking a week or so ago on a Discord server.
A few days ago I started making a non-game-breaking anti-exploit thing, but one of the rare kind that works.
It does regular checks like RobloxLocked, but should also be able to detect global instance metatable altering.
(and I scan the whole game for REs/RFs and execute the checks on them too)
The server also kicks the player if no ping (that gets sent every second) isn’t received for 5 seconds.
(when the player joins, the player has immunity for a minute, plenty of time to replicate and run the localscript)

It isn’t 100% spoofing proof, but only a very small amount of people (I don’t even know if my super skilled scripting buddies are included) would be able to hijack REs/RFs without me being able to detect it. (of course they can always connect to the event and listen, can’t stop that)

1 Like

there are a lot of little vulnerabilities in rc7 like this that can break the exploit but those are all filed under ‘quick fixes’, they can be patched over at any time and stop working, and remeber that rc7 isn’t the only id7 exploit and the others are a lot better when it comes to vulnerabilities like these
the only real solution to exploiting is reducing the authority the client has over the states of other players by imposing checks from the server on what the client is trying to do

1 Like

y tho

Not sure if excuse to use meme or actual question. If latter:

  • Make it effectively impossible for exploiters to run off with your clientside code in a game heavily dependent on it since they can’t adapt it to be used
  • Make server code effectively possible for exploiters to use in the event rare exploits like the one that could steal assets from the site occur and target your game

Prevents low-effort, high-quality competitors from popping up thanks to your own code.

'Twas an excuse to use the meme. I fully understand the use of obfuscation, and honestly even as an experience scripter I’d be hesitant to touch any of that code, which means it does its job.

Thanks for this post, it’ll be a nice source to refer to in other threads. :slight_smile:

It’s also nice to have another source that explains in practical terms why things like shared secrets as parameters in remote events/functions are definitely not a good security strategy, because these exploits can just intercept events/invocations and only change the variables that benefit the exploiter (i.e. the parameters for shared secrets can just stay the same).

it depends on how you handle your purchases. if they are all handled on the client side (authoritative client), then yes, that is a possibility. if you make checks with the server, depending on how you handle those checks, it could still happen. (however you should note that most exploiters will be targeting competitive games like phantom forces and will be trying to exploit the game logic to cheat rather than purchasing items. you should be more worried about exploiters ruining the experience of the other players in your game) this all comes back to the main principle of not trusting the client, or any information it sends.

i thought of a few wrong solutions to this and a correct solution, just to highlight some potentially unsafe practices
hypothetically wrong solution #1
whenever someone makes a purchase, we have a remote that sends the server the server a) the price of the item and b) the players’ money. this is dangerous for obvious reasons because exploiters can use method detouring to change either the total amount of money the server thinks they have or the cost of the item. instead of the client sending how much money it has, the server should have its own processes for keeping track of each player’s total money, and actions that add or remove money to that total should be processed by the server. if there are any processes which the client can gain money that aren’t reasonable to keep track of on the server, ones where the client would be required to fire a remote (selling an item to a vendor, completing some kind of local task, etc) the server should have processes to make sure that is valid. something like, if i try to sell an item to a vendor, the server checks first to see if i have that item in my inventory before updating my total money

hypothetical (less) wrong solution #2
whenever someone makes a purchase, use a remote to tell the server which item was purchased. we can keep a copy of the prices of every item on the server and update the player’s money accordingly, and then use a remote to send back a verification for their purchase. this solution is still wrong because we assume the client will always tell the truth about what item they’re buying. if the client tries to buy the 100,000 coins item, but tells the server its buying the 10 coins item, the server will still verify that purchase. one way this may try to be solved is to send back from the server which item was validated, and update the clients information accordingly. this still doesn’t work because detouring can also change the arguments of incoming client events, and make it seem like the server is telling the client that their 100,000 coins item purchase is valid.

because any events that change the clients state can be completely detoured, and any information sent from the client about their state can be spoofed, the only correct solution is to keep a version of the player’s inventory kept on the server that doesn’t rely on information from the client. if the exploiter validates their 100,000 coins purchase on their client with detouring, the version on the server shows that they have the 10 coin item in their inventory. if the player tries to equip the 100,000 coin item, the server stops them, because it’s not in their server inventory.

1 Like

Isn’t the simplest way to get around this to just not trust the client at all?

In my new game I don’t have anything on the client except the guis and local scripts, all the player’s stats are stored in ServerStorage. When they gain or lose something, the server will fire a RemoteEvent to tell the client what was updated and what the new amount was.

When the player purchases something, the server is told the item name and the player, it’ll then go check if they have everything required to purchase the item and then send a notification to the player to inform them if their purchase was successful.

If the client wants to check anything, they have to fire a RemoteFunction which will return the answer to their request.

All the Scripts are kept in ServerScriptService, besides a few (<20 lines each) which are in models in the workspace.

This should be unhackable except for the LocalScript and geometry stealing which can’t be avoided?

7 Likes

afaik the only way to check that is by doing something to compare against the original non-detoured function like

IsA = game.IsA --later on if IsA ~= game.IsA then -- detoured --nuke end
which doesn’t work because id7’s have autoexec functionality that loads the exploits into the environment before the actual client logic loads or doing something like

if game.IsA ~= game.IsA then
  --nuke
end

which only works if the detour function instantiates and returns a new a new anonymous function every time (the example i showed does) which can be easily bypassed by simply setting the function to a variable and then passing that instead

there really aren’t any easy end-all fixes to detouring. i don’t want to go into details about my module because the less that is known the more secure it will be but it essentially scrambles and obfuscates arguments so they can’t be understood at all by placing listeners on events, and traditional detouring functions can’t manipulate the arguments because they change position and are nonsensical. it does a lot more other Hi-Tek secret things to make it nearly impossible to bypass, and even when it is deobfuscated writing code to descramble the arguments into a usable format will be pretty challenging (but not nearly as hard as deobfuscating). the coolest part about it, in my opinion, is how it uses instance wrapping to replace :FireServer :OnClientEvent :FireClient :OnServerEvent with the functions in the module so that you only have to call a single function in any existing system to implement the anticheat. i started working on it before i really knew that method detouring isn’t a problem if you don’t give th,e client too much authority, and when i learned that it made me lose a lot of my motivation to finish it, so now it’s been sitting hash-less in my projects folder for a few months now, like 70% complete

2 Likes

That’s one way of detecting spoofed functions.
There are several ones I can think of that work.
Detouring them is possible, but very difficult.

yeah, that sounds safe to me!! personally, i do a mix of client and server checks which allows the client to be more responsive and less dependent on waiting from validation from the server while not compromising safety. for example, the client would buy something and if the purchase clears on the client then i would instantly display that result, but if it doesn’t clear on the server it would tell the client to update itself (along with keeping an authoritative version of clients inventory so that if the client detours the update, the server will know if they have an item that they shouldn’t)

well are you gonna tell us what they are?

2 Likes