How To Anti-FunctionHook

Hello! Today I’d like to openly share my research about functionhooks and how to protect a function from a function hook attempt.

What Are Function Hooks?
They’re basically part of the exploit-level library that is inaccessible to Roblox Studios level LUA code. Functionhooks are used to hyjack functions and make them return an augmented value.

hookz

For example, a functionhook would be used to set the taser receive function of Prison Life’s localscript to nil and allow the player to become immune to being tased.

What a Functionhook looks like:
Below is an example of a higher security context level functionhook:

--FunctionHook
--Grabs the target function from the garbage collector, 
--locates the localscript => function => name of function => hooks it and returns nil.
for k,v in pairs(getgc()) do --loops in the garbage collection for the functions and lScripts
    if type(v) == "function" and getfenv(v).script == (game:GetService("Players").LocalPlayer.PlayerGui.LocalScript) then --targetting a function method in a script path
        if debug.getinfo(v).name == "antifunctionhook" then --targets our function's name 
            hookfunction(v, function(...) end) --hooks that function & sets it to return nothing
        end
    end
end

What an Anti-Functionhook looks like:
To protect a vulnerable function, you’d want to integrity check it.

--Victim Function
--Our function that is getting function hooked by the cheater
function antifunctionhook() --the name of the function being hooked
    local x = 19 -- the expected/normal output
  return x--the normal value being returned when function's called
end 

--Anti FunctionHook
--Integrity check the vulnerable function:
coroutine.resume(coroutine.create(function() 
    while wait(1) do
        if antifunctionhook() ~= 19 and typeof(antifunctionhook()):lower ~= "number" then --integrity checking the returned value
            game.Players.LocalPlayer:Destroy();--welp they hooked it and it's not returning what it should return, it's hooked
        end
    end    
end))

After detecting you can kick, destroy, crash, delete the script itself if it’s a core script, etc.:
functoinhook

Disclaimer: Sanity checks and server-sided anticheats are always preferred and should be your most powerful, important and, final line of game security. The above method is possibly bypassable regardless as it’s done on the client.

**It’s advisable to keep your client-sided anticheats embedded in a core/integral localscript so that if that script is tampered with, that’ll also impact the cheater’s capabilities too.

14 Likes

For future readers, this does not mean your game will be function hook safe, considering the exploiters can simply return 19 as it is in the function.

This is also partially false, they can simply copy your localscript, remove the anti exploit and run it again.

4 Likes

That part is actually pretty helpful.
What do you mean by they can simply copy, remove the anti exploit and run it again? Pretty sure that’s impossible, if they decompile the script it will have tons of errors thanks to Luau.

1 Like

@Place_Reboot
Or even simpler, they can just stop the coroutine’s thread.

This is a decent resource for those who don’t know what a functionhook is, but it doesn’t fit the definition of tutorial. It is more useful as a reference than a guide for those who want to try protecting their code.

2 Likes

I wouldn’t say they would have tons of errors, only that it makes reading harder (Variables, functions and etc are renamed). If the script had tons of errors, it would not function. Other than that, yes you’re capable of copying the source and modifying it and pasting it without the anti exploit.

This is what the decompiled code roughly looks like:

local v8 = game.ReplicatedStorage.data:WaitForChild(game.Players.LocalPlayer.UserId):WaitForChild("jump")
local v68 = game
local v10 = v68.Players.LocalPlayer
local function addChar_1(p1)
	local v14 = p1:WaitForChild("Humanoid")
	local function v16(p2)
		local v18 = v8.Value
		local v55 = 1
		if v18 ~= v55 then
			local v19 = false
		end
		v19 = true
		v55 = v8
		v18 = v55.Value
		local v56 = 2
		if v18 == v56 then
			v18 = pairs
			v56 = p1
			local v20, v21, v22 = v18(v56:GetChildren())
			for v23, v24 in v20, v21, v22 do
				local v30 = string.find(v24.Name, nil, true)
				v30 = string.find
				local v35 = v30(v24.Name, nil, true)
				v35 = string.find
				local v40 = v35(v24.Name, nil, true)
				v19 = true
			end
			if not v40 then
			end
			if not p2 then
				if not v19 then
					v20 = math.random
					v21 = 1
					v23 = 57
					v40 = v8.Value
					local v57 = 2
					if v40 == v57 then
						v24 = 128
					else
						v24 = 0
					end
					v22 = v23 + v24
					local v42 = v20(v21, v22)
					local v43 = 1
					if v42 == v43 then
						v42 = wait
						v23 = math.random
						v43 = v23() * 0.2 + 0.2
						v42(v43)
						v14.WalkSpeed = 0
						v24 = math.random
						wait(v24() * 0.1 + 0.05)
						v14.WalkSpeed = v14.WalkSpeed
						return 
					end
					v47 = math.random
					v49 = 1
					v44 = 35
					v57 = v8
					v40 = v57.Value
					if v40 == 2 then
						v48 = 96
					else
						v48 = 0
					end
					local v51 = v47(v49, v44 + v48)
					local v52 = 1
					if v51 == v52 then
						v52 = v14
						v51 = v52.JumpPower
						v52 = v14
						v52.JumpPower = 0
						v52 = wait
						v52(0.2)
						v14.JumpPower = v51
					end
				end
			end
			return 
				if v35 then
			end
			if v30 then
			end
		end
	end
	v14.Jumping:Connect(v16)
end
addChar = addChar_1
addChar_1 = v10.CharacterAdded
addChar_1:Connect(addChar)
local v65 = v10.Character
if not v65 then
	v65 = addChar
	v68 = v10.Character
	v65(v68)
end

this is a simple function that just randomizes humanoid properties but the decompiled version still has some errors (mainly because of the upvalues). Doesn’t really matter how good at scripting one may be, restoring a decompiled core localscript is basically impossible.
Though unfortunately, there are many other methods they could use to bypass this anyway.

A friend of mine with a software in which I will not name (for obvious reasons), was capable of removing parts of my script and rerunning them with minor alterations. (Which is mostly what I was talking about)

what is the point of this? why would an attacker want to replace your function with a blank one that returns nothing? if someone wanted to intercept your function they could easily hook it and either call the original one to tamper with the return values/modify its parameters and tailcall the original one. that’s just not how people use hookfunction; here’s a more realistic example of what would actually be done with your function:

for i, v in pairs(getgc()) do
	if whatever then
		local original; original = hookfunction(v, newcclosure(function(...)
			-- anything
			return original(...)
		end))
	end
end
4 Likes

yeah you are 100% right.

more often than not, anyone using a “function hook” understands that they should return the original value of the function call. (similar to how anyone making a proxy table with metatables for an instance understands that they should return the original value for something if they don’t have their own for it).

also this “anti-hook” does not work at all if your function is supposed to return dynamic values such as the player’s current ammo, etc. because they can just return any number they would want to.

1 Like

I suggest you remove the meme before someone flags the post.

There’s nothing wrong with being funny occasionally. Memes like these can keep a hook on someone’s post and it’s not prohibited to use memes for the most part.

like what everyone else said above, this will only work on hooks that don’t return the expected value. if you simply return a call to the trampoline function, your “anti-function hook” is basically rendered useless.

So! Not a perfect solution, yet it gives more insight about functionhooking and one way to detect it. Surely others can come up with a better way or resort to server-sided altogether.

Exploiters effectively have the ability to modify scripts, the only reason there will be errors is due to differences in the environments, but, that’s not exactly an issue for exploiters, they can just stick in the correct variables, such as script and simulate a lower context level if they really need to. Most exploits offer that as a feature.

You should not be having any form of client sided antiexploit.

@Matrice
Additionally, this only works if the exploiter is looping over the gc in the first place. An exploiter can simply reference the function in a more direct way, or, hook the global metatable to create “traps” so they can execute code somewhere in your script where they can “know where they are” and thus find your function that way.

None of your gameplay code should be on the client like in Prison Life, that should be completely server sided, and, the server should take physics control of the player’s character when it needs to. If you aren’t doing these sorts of things on the server, exploiters can and will get around them.

They can modify upvalues and constants using a custom debug API which comes in exploits. Them decompiling it, changing some characters, running it again and it magically working is impossible.

I never said you should have an antiexploit on the client side tho, that should definitely be done on the server.

1 Like

Them decompiling it, changing some characters, running it again and it magically working is impossible.

This is pretty possible, you just need to take into account which variables the script references. Some exploit libraries have the option to change the thread’s security level so its possible to simply set the security level to 2, change the script reference, and, fix any decompiler issues with things like loops. I’m not saying its a necessarily easy thing to do, what I am saying is that its something that can be done quickly by someone experienced.

And, generally, the only reason the script will error if reran is due to the game’s state being incorrect, such as instances being in the wrong place, or modules relied on by the code having the wrong states. It depends on the structure of the game and the programming style of the person who made the game, but, its very possible that doing this will just work and I’ve seen it been used before successfully lots of times. If it doesn’t, it’d just take some figuring out on the exploiter’s part and they are basically done.

And, something else, no exploits have done this yet, but, it’d be very possible that an exploit could introduce a feature to patch the bytecode of running scripts if they chose to. It’s not out of the realm of possibility though its unlikely that exploits will do this do to the complexity it would involve.

Regardless of any of that, exploits do effectively have the power to modify scripts as if they had direct access to the source code, it doesn’t matter what the method they use is or how difficult it is, they do pretty much have this ability and it’s bad to try and rely on how difficult something is when its coming between someone and money.

Also, the rest of that post was in response to Matrice, not you, sorry.

2 Likes