Anticheat Methods

Anticheat Methods

Hey fellow developers!

I decided to release these detections because of this topic and the fact that exploiting is quickly dying, meaning nobody will buy Roblox anti exploits anymore.

Protect MetaMethod Calls

This is made to protect namecall calls such as MyPart:GetChildren() from being hooked like this:

local oldNC = hookmetamethod(game, "__namecall", newcclosure(function(...)
    if getnamecallmethod() == "GetChildren" and ... --[[blah blah]] then
        ...
    end

    return oldNC(...);
end))

Because getnamecallmethod() will instead return something along the lines of GetChildren\000\000\0004154565.1561216 (\000 being null bytes, which are string terminators in C++, meaning the rest will be ignored) which is NOT equal to GetChildren.

--// Init
local GetCallerFromXpcall = function(BaseFunction)
    return select(2, xpcall(BaseFunction, function()
        return debug.info(2, "f")
    end))
end

local ProtectMethodString = function(SObject: string)
    return string.format("%s%s%e", SObject, string.rep("\000", math.random(1, 6)), os.clock() ^ 5 + 0.1)
end

local SafeCFakeNamecall, SafeCNewIndex, SafeCIndex do --> Methods for Roblox-Made classes like Enum and Instance
    local __newindex = GetCallerFromXpcall(function() game.____ = nil end)
    local __index = GetCallerFromXpcall(function() return game.____ end)

    SafeCFakeNamecall = function(self, Method, ...) --> Does not invoke __namecall, but will invoke __index instead.
        return self[ProtectMethodString(Method)](self, ...)
    end

    --> These two closures will invoke the C __index and C __newindex of the Roblox-Made classes
    SafeCNewIndex = function(self, Index, New)
        return __newindex(self, type(Index) == "string" and ProtectMethodString(Index) or Index, New)
    end

    SafeCIndex = function(self, Index, New)
        return __index(self, type(Index) == "string" and ProtectMethodString(Index) or Index)
    end
end

--// Example
local GameName = SafeCFakeNamecall(workspace:WaitForChild("Baseplate"), "GetFullName") --> Returns: Workspace.Baseplate
SafeCNewIndex(workspace, "Gravity", 0) --> Setting gravity to 0.
SafeCIndex(workspace, "Gravity") --> Returns: 0

Detecting GUIs

These methods detect a large number of GUIs, including Rayfield and Infinite Yield, and EVEN SirHurt!
Here we abuse how the garbage collector works in Lua by checking if CoreGui was referenced or not. This is already a known method by some, but I just want to make sure everyone knows about it.

:warning: - Keep in mind that you CANNOT declare a variable with the value of the CoreGui service, else the check will false positive!

--// Init
local IsReferenced = function(Object)
    local Table = {}
    local WeakMT = setmetatable({ --> This could be shuffled
        Table, 1, "String", Object,
    }, {
        __mode = "kv"
    })

    --> Clean up
    Table = nil
    Object = nil 

    repeat task.wait(); until (not WeakMT[1]) --> Wait until garbage collector gc the values
    task.wait(.2)

    if #WeakMT ~= 3 or rawlen(WeakMT) ~= 3 then --> Table tampering (eg. table.clear) / 'Object' is still referenced 
        print(WeakMT)
        return true
    end

    return false
end

--// Example
if IsReferenced(game.CoreGui) then --> This should be in a loop
    print("Detected - CoreGui referenced without cloneref")
end

--> KRNL Gethui detection connection
--> Fun fact: this method used to work on scriptware for a short time!
SafeCFakeNamecall(SafeCIndex(game, "DescendantAdded"), "Connect", function(Object)
    local Success, Out = pcall(function()
        return SafeCFakeNamecall(Object, "GetFullName")
    end)

    if Success and string.find(Out, "^CoreGui") then --> Make sure it is the Actual CoreGui Instance, and not just the name
        local Success, IsFromCoreGui = pcall(function()
            return Object:IsDescendantOf(game.CoreGui)
        end)

        if (not Success) or IsFromCoreGui then --> Double check
            print("Detected - KRNL gethui")
        end
    end
end)

Raw Game Metamethod Hooks (Includes CMD-X Detection)

This will detect changes in the game’s metatable object like this:

local mt = getrawmetatable(game)
local oldNC = mt.__namecall

setreadonly(mt, false)
mt.__namecall = newcclosure(function(...)
    if ... then
        ... --> Random code blah blah...
    end

    return oldNC(...)
end)

setreadonly(mt, true)

Only bad exploit scripts detours like this, such as the old synapse init script or CMD-X.

--// Init
local __namecall = GetCallerFromXpcall(function() return game:____() end)
local __newindex = GetCallerFromXpcall(function() return game.____ = nil end)
local __index = GetCallerFromXpcall(function() return game.____ end)

--// Example
while true do
    --> Check if the metamethods have changed (getrawmetatable hooks)
    if
        __namecall ~= GetCallerFromXpcall(function() return game:____() end)
        or __newindex ~= GetCallerFromXpcall(function() return game.____ = nil end)
        or __index ~= GetCallerFromXpcall(function() return game.____ end) --> This specifically detects CMD-X's __index hook.
    then
        print("Detected - Raw metamethod hooks")
    end

    task.wait()
end

Basic Hook Detection (hooks using the closure returned by the detour function)

:warning: - This code snippet (the detection) is unoptimized and should be throttled in order to prevent performance drops.

coroutine.wrap returns a wrapper function that resumes a coroutine from a given function. When calling it over and over (in this case 198 times), it will not error. But 1 more call will result in a C Stack overflow. A good detour function will normally NOT replace the function address, meaning you cannot do if currFunc ~= Func, so calling a function itself again and again will eventually result in a C stack overflow. If it errors, it means they obviously hooked the function, as “old” was used.

An example of a detectable hook with that method would be:

local old = hookfunction(gcinfo, newcclosure(function(...)
    return old(...)
end))
--// Init
local IsHooked = function(Closure)
    for _ = 1, 198 do
        Closure = coroutine.wrap(Closure)
    end

    local Success, Out = pcall(Closure)
    if (not Success) and string.find(Out, "C stack overflow") then
        return true
    end
    
    return false
end

--// Example
while true do
    if IsHooked(YourFunction_ThisCanBeAnyFunction) then
        print("Detected - Some function was hooked")
    end

    if IsHooked(AnotherFunction) then
        print("Detected - Another function was hooked")
    end

    task.wait()
end

Electron Detection

A few months ago, me and my friends dumped the Electron Init Script with Process Hacker and realized they referenced a Service (InsertService) that could detect electron (since it references it).

--// Example
while true do
    if IsReferenced(game.InsertService) then
        print("Detected - Electron Detected.")
    end

    task.wait()
end

“tostring(Argument)” detection

This will detect many bad hooks by exploiters (especially skids), as __tostring shouldn’t be triggered when calling Roblox Functions, meaning they did, for example, tostring(self) to check the name of the passed object.

--// Init
local IsTostringDetected do
    local __namecall = GetCallerFromXpcall(function() return game:____() end)
    IsTostringDetected = function(Func)
        local Called = false
        local Bait = setmetatable({}, {
            __tostring = function(self)
                Called = true
                return ""
            end
        })

        if Func == __namecall then --> If the given function is game's __namecall...
            game:GetFullName() --> Set the namecall method in the current thread to a valid one
        end 

        pcall(Func, Bait, Bait, Bait)
        return Called
    end
end

--// Example
while true do
    if IsTostringDetected(__whatevergamemetamethodOrAnyRobloxFunction) then
        print("Detected - tostring has been called on self or the function arguments.")
    end

    task.wait()
end

Please use your brain when replying to this post!

Replying with something stupid like “Yes but the exploiter can just disable the script” is just stupid and unproductive! Those are methods! It is up to you to protect them.

The end

Let me know if you want another method or detection on this topic by private messaging me and I’ll add it if possible!

:warning: - I will not detect physics exploits such as flying or noclipping.

Also please tell me if any of these methods false positives so I can fix them.

Edits:

  • 10/26/2023 - Added explanation of detections | @Doomcolp
  • 10/27/2023 - Added Electron Detection | @TheLikerYT
102 Likes

Nice work as always Liker, hopefully there wont be any stupid replies like “Yes but the exploiter can just disable the script”

14 Likes

Thank you Iceminister!

Yeah! That would be such a shame if that were to happen.

8 Likes

i wonder who could say something this stupid :clueless:

5 Likes

This seems interesting! Only problem is that I have no idea what is going on here lol

(good job)

8 Likes

Thank you!

No worries, I’ll add a proper explanation of what is going on in every detection.

6 Likes

Nice work, i like it!
That’s a good anti-cheat

5 Likes

Ok but game:Shutdown() ggs buddy

8 Likes

the exploiter can just disable the script

for _, x in pairs(game:GetDescendants()) do
if x:IsA("Script") or x:IsA("LocalScript") then x.Disabled = true end
end
6 Likes

how to make the client unplayable in a nushtell:

15 Likes

the exploiter can just disable the script

workspace.Parent.Workspace.Parent.Workspace.Terrain.AntiHack2023Real.Enabled = (not game == false) == false
6 Likes

This is cool and all but just wait until your “anti-cheat” meets my super ultra script disable button!!!

11 Likes

Then the server will get mad, and kick you out of the game automatically :TrollFace:

5 Likes

Seems like the people (other than one exception) above didn’t read the post :man_shrugging:

Thank you :slight_smile:

8 Likes

Yes but the exploiter can just disable the script

These are actually nice detections though and the top was a joke (notice exact quote)

6 Likes

literally never trust the client with anti cheat.

3 Likes

I would argue the client shouldnt be trusted (like everyone else here) but client-side anticheats are valueable against script kiddies and you can do things like have a handshake that has to be done every 10 or so seconds or the client is kicked.

To summarize the handshake,
There is a locked book with two keys
The server sends the client the first key when the game starts
– start the loop here
The sever sends a value to the client to initiate the handshake
The client unpacks the value with its key, then sends it back
if the value matches the client is good

You can add more steps to the handshake like packing a key inside the value to unlock more data, this makes reverse engineering harder.

Of course you cant trust the client but things like this are helpful for protecting clientside anticheats from tampering.

At some point we may have to make a tradeoff and put some anticheat code on the client (like a logger for example) and systems like the above can protect you atleast a little bit

3 Likes

I sure do love all the original comments talking about how you can disable the script and to never trust the client. Thanks guys for the new insight!

6 Likes

Please see this heading (aka “Please use your brain”).

Also, when we aren’t using getfenv(), your last argument quite is: Never trust the client. Then tell me kind sir, How you would detect Infinite Yield from the server

4 Likes

Yeah most of those are my friends lol

4 Likes