Anti Cheat Megathread #1

There are a lot of cheats in GMod, and not a lot of resources that can teach you how to fight them. For the most part, cheaters have no idea what they’re doing, blindly download hacks, and join random DarkRP servers. Unfortunately, not everybody is like that. There is the 1% who know what they’re doing and are able to create their own cheats and circumvent whatever you have in place. I made this in the hopes that it will at least teach people how to make a decent anti cheat. Obviously you can’t stop 100% of all cheating, so please, don’t post saying that this thread is dumb/pointless. I know it’s impossible to stop all cheating, thank you. Another thing I want to add is that I’m not perfect. I don’t know all the rules, I don’t know all the quirks, and I’m no master of Lua. I haven’t even actually created an anti cheat, I’ve just been in the GLua business for a while. If you see something that’s wrong or could be improved, please, tell me so I can fix it. You can even contribute your own stuff and I will add it to the OP.

The point of this thread is to teach you how to stop experienced cheaters: the ones who are above the average skid but aren’t quite yet at the level of being able to say that they are fairly advanced at cheating. Although this guide may teach you to catch even cheaters that are at the top, you’ll probably be catching the medium level cheaters, as I said before. Furthermore, this thread assumes you know how to work with Garry’s Mod. I’m not going to hold your hand and teach you about Lua. With all of that jargon out of the way, let’s get started.

Loading First

Your first objective when creating an anti cheat is to load first, that is, before any other file has loaded. This step is optional, but I highly recommend it, as it vastly increases the security of your anti cheat.

The very first file to be ran is garrysmod/lua/includes/init.lua. This file is core to GMod and your game would break if it didn’t load, since it loads every other core GLua feature. A nifty little thing you can do is actually replace init.lua with your own version - without actually replacing the file. To do this, you simply mimic its file path in your addon. I’ll name my addon hat-trick, and will use it throughout the rest of this guide.

As you can see in the image, I mirrored the path to the file in my addon. If you were eager enough to launch your server after I just told you about this little trick, you’re probably wondering why your server just shit itself.

Since we overrode the init file, the core functions of GMod never load, and your server goes kaput. Remember, this is a SHARED file, so the server runs it too. To overcome this obstacle, simply all we need to do is copy and paste all the code from the original init file to our init file. Yes, it’s that simple. The only other step you need to do is to add your own code to the bottom of the file. Since you only really need to load first on the client, your code should look like this.


-- The original init file's contents

if (CLIENT) then
    -- Anti cheat stuff for the client to load
end

We haven’t yet created a file for the anti cheat to load, so we should probably get on that now. Go ahead and create a folder in the same place as init.lua. In the folder, create a lua file. This is how I’ve done it.

And here’s the init file.


-- The original init file's contents

if (CLIENT) then
    include("hat-trick/hat-trick.lua")
end

Congratulations for making it this far. Now, it’s onto the next section.

Garbage

So we’ve got our anti cheat loaded first before any other addon, but unfortunately, this isn’t enough. There exists methods to load even before this. Although only possible through cheats created in C++, it is still a threat nonetheless. If they’ve loaded before your anti cheat, they can take measures to deceive it. Such is the never ending struggle between cheat and anti cheat. For those that don’t take measures against your anti cheat (aka 99.9% of cheaters), here is your first step to catching them. When a script loads, the amount of memory Lua uses increases. There exists a function, collectgarbage, to retrieve the amount of memory in use by Lua. The function actually serves multiple purposes, but for our objective, we specifically want to use it for this purpose. We can get the amount of memory (in kilobytes) by executing the count command in the function.


local memory = collectgarbage("count")

You’re probably wondering why this is useful, but I bet some of you keen readers are understanding where this is going. Since executing a script increases the amount of memory in use, we can check to see if any foreign scripts have loaded before us. Something as simple as printing the number 1 will change the value returned by the function, although very insignificantly. Take a look at this example.


local memory = collectgarbage("count")

-- The original init file's contents

if (CLIENT) then
    include("hat-trick/hat-trick.lua")
end

On my server (not client), the value of memory is 711.787109375. This number is, in my experience, consistent. Although I am unsure of this myself, the number for your server or client may change, so I don’t recommend banning someone over a billionth of a byte being higher than what you expected. Another thing you may want to do is actually receive the amount of memory both before and after loading the core init script, or maybe even after loading your anti cheat’s file. Doing this is very simple, but this stuff is entirely up to you.


local memory = collectgarbage("count")

-- The original init file's contents

memory = collectgarbage("count") - memory

if (CLIENT) then
    include("hat-trick/hat-trick.lua")
end

If the memory is higher than expected, you may set a global for your anti cheat to use. How you handle this is entirely up to you. Remember, I did this for my server, not my client. Use proper if statements. I am running the code on the server only as an example. Use proper if statements to make it run only on the client. Don’t check for super tiny decimal places, check for more significant increases in memory. As a reference, the original value for my memory’s difference was 338.4326171875 and was increased to 338.947265625 after I simply printed the number 1. Beware of GMod updates increasing the amount of memory on startup, and thus, causing false positives.

**


The memory returned will be different on different operating systems. You can use these functions to check which operating system the client is running, and compare accordingly.

**

system.IsWindows

system.IsLinux

system.IsOSX

Function Integrity

Making sure the functions you’re using are not detoured is an important thing. Detoured functions usually mean either that an addon has detoured it or the player has detoured: probably to cheat or find out how your AC functions. It’s important you don’t ban everybody just because Wiremod or whatever decided to detour RunString, so take into consideration the fact that detouring functions is a legitimate thing if you’re a legitimate developer. Here’s a few simple methods on making sure your functions are safe.

First off, save your functions. At the top of your anti cheat file (hat-trick.lua for me), save a local copy of functions you want to use. That way, you’ll (hopefully) have access to the original functions. Cheats won’t be able to simply detour your functions then. I’ll use RunString as an example.


local RunString = RunString

Another thing to note is that you may want to simply save a copy of the global table using table.Copy. I’m not sure what the ramifications of this are, but I’m sure there’s something wrong with it and someone will probably post about why it’s a bad idea, but that’s one thing you are able to do.

Second of all, pretty much all detours look like this.


local RS = RunString

function RunString(...)
    -- New code

    return RS(...)
end

They may specify an amount of arguments, they may not return anything, or they may place their code after the original function is called. It all varies. The point is, it looks something like that. Fortunately for us, there’s a way to detect this fairly easily using debug.getupvalue. Upvalues are basically external local variables for functions. See how the original function is external to the detour’s code? That’s an upvalue, and since the original version of RunString has no upvalues, if you check RunString and it has an upvalue, it’s detoured.


if (debug.getupvalue(RunString, 1)) then
    -- It's detoured
end

Be aware, however, that some legitimate functions have upvalues. hook.Add, for example, has an upvalue for the internal hook table.

Third of all, the classic source checking. Use debug.getinfo to check where a function came from. Really though, everything this function returns is useful, but for now, the source is what we want. Since RunString is a C function, its source should be listed as [C] or =[C], depending on whether or not you’re checking short_src or source from the return value.If it has been detoured, its source won’t be [C] unless you’re dealing with the 1% of cheaters (or have some addon detouring it). source and short_src will change depending on the identifier used to load the script. In most cases, this is simply the name of the file where the function was detoured.

Fourth of all, and probably the most interesting, is offset calculation, which only works with C++ functions. Each function has an address (or an identifying number in some cases), and these addresses change every time you load the game. The great thing is that their offsets stay the same, so one address will always be the same distance from another address. We first need a base function: a function that will serve as the base for our offset calculation. This can be almost any function. I’ve chosen to use the Msg function. Printing out RunString looks like this.

Again, this will change every time you load the game. We can use string.format to easily get the address of something like a function.


local function GetAddress(func)
    return string.format("%p", func)
end

This returns a number for the function. First, we need to get the address for the Msg function.


local baseFunction = Msg
local baseAddress

local function GetAddress(func)
    freturn string.format("%p", func)
end

baseAddress = GetAddress(baseFunction)

The baseAddress variable now contains 340483752, which is the decimal form of 0x144b5ea8. Next we need to write a function to calculate the offset.


local function GetOffset(func, base)
    local funcAddress = GetAddress(func) -- Get the address of the passed function.
    local baseAddr = !base and baseAddress or GetAddress(base) -- Either use another function for the base or use the one at the top of the script.

    return math.abs(baseAddr - funcAddress) -- Return the distance between the two addresses.
end

We can combine these to get this.


local baseFunction = Msg
local baseAddress

local function GetAddress(func)
    return string.format("%p", func)
end

local function GetOffset(func, base)
    local funcAddress = GetAddress(func) -- Get the address of the passed function.
    local baseAddr = !base and baseAddress or GetAddress(base) -- Either use another function for the base or use the one at the top of the script.

    return math.abs(baseAddr - funcAddress) -- Return the distance between the two addresses.
end

baseAddress = GetAddress(baseFunction)

We’re all set! Now let’s test it out. Add this to the bottom of the script.


print(GetOffset(RunString))

If you’re using the Msg function for the base and RunString as the operand, hopefully you’ll see its offset is 1208. Perhaps it’s different, depending on your addons and such. Go ahead and restart the server - the number will stay the same! Something to note with this method is that some functions (like print for example) are “builtin” functions and will always return the same number. Using tostring on print will result in this.

Because of this, it is not possible to calculate offsets with print as either the base or the operand, unless both functions are builtin (since they’ll both have static numbers instead of just one of them having a static number). There are other “builtin” functions like print, so keep that in mind. As the name implies, the functions are built into Lua itself, so these are the functions that come with Lua and not GLua. Using offset calculation, you’ll be able to compare current offsets with expected offsets. If the offset is different, somethin’s fucky (or it’s those meddling RunString addons!). Changing your base function will change the base address, which changes the offset. If I change my base function to something else, 1208 will be meaningless to me because that’s the offset of RunString from Msg, not the offset of RunString from whatever other function. Different base function, different offset to whatever you feed the GetOffset function.


-- Assuming the base function is Msg...
if (GetOffset(RunString) != 1208) then
    -- It's detoured
end

Beware of GMod updates changing the offsets of functions. This will get you false positives if you’re not on top of this sort of stuff.

For the fifth and final method, hashing. string.dump can be used on a function to get its bytecode. You can then generate a hash using util.CRC. string.dump only works on functions that are made in Lua, so if it DOESN’T error while being used on a C function like RunString, we know it has been detoured.


local detoured = pcall(function()
    string.dump(RunString)
end)

If it’s a Lua function, you can simply compare the hash of the dump with a legitimate hash of the function. Beware of GMod updates changing functions, and thus, changing the hash of the function.

Concommands and Cvars

For cheats made in Lua, you can detect these by calling concommand.GetTable and checking for concommands either related to certain cheats or just checking if they have a specific string in it (e.g, aimbot, bhop, etc). The thing about this, though, is that it only works for concommands added through Lua. It doesn’t have any concommands added through C++. I actually just recently brought an idea up in another thread. Since most cheats made in C++ add some sort of concommand or cvar and concommand.GetTable doesn’t work with C++ commands and such, you have to detect these commands in a different way. Fortunately, it’s pretty easy. Since concommand.Add fails if the command was added through C++, we can see if a command or convar exists by attempting to add it ourselves.


local function CommandExists(command, callback)
    if (concommand.GetTable()[command:lower()]) then
        callback(true)
        concommand.Remove(command)
    end

    concommand.Add(command, function()
        callback(false)
        concommand.Remove(command)
    end)

    RunConsoleCommand(command)

    timer.Simple(0, function()
        if (concommand.GetTable()[command:lower()]) then
            callback(true)
            concommand.Remove(command)
        end
    end)
end

CommandExists("kick", function(exists)
    print("Does the kick command actually exist?", exists)
end)

CommandExists("cheat_command", function(exists)
    if (exists) then
        -- Somebody's been naughty!
    end
end)

It’s a very hacky method…but what’s an anti cheat without hacky methods?

Other Stuff

Your anti cheat should not only communicate with the server to ban the client, it should communicate with the server so the server can verify what the client is doing. If your AC only communicates with the server to ban the client, your AC can be prevented from running and the client is free to do whatever. You don’t have to communicate every single thing the AC is doing, you only have to communicate just enough so that the client will be detected if your AC is never loaded. You also preferrably want to do detections on the server, not the client, so that people attempting to reverse engineer your AC won’t be able to see what exactly
you are checking for. They can make assumptions based on what the client is sending, but this adds a layer of obscurity.

Speaking of obscurity, obfuscation. Obfuscation mangles your code into a nightmare, and can even reduce it to a single line. Most people, upon stealing a glance at spaghetti code, will throw up their arms and say they’re done. This makes your life easier because right off the bat they’re not even going to bother trying to bypass your AC. Mission accomplished. There are automatic obfuscators out there for you to use. Find out which one is right for you. Don’t even bother doing that shit by hand. Remember to save a copy of the non-spaghetti code so you can edit your anti cheat with the original, readable code.

Combining all of the tips in this thread will let you catch 99.9% of cheaters. You should err on the side of caution when it comes to detections, unless you know for a fact that they have done something they aren’t supposed to do. Something you should consider is flagging them and notifying all admins of what they were flagged for so innocent people don’t get banned in case you fucked something up along the way.

This thread will be updated as time goes on. Feel free to ask and answer questions, and again, improve the post or tell me if I said something wrong. Thank you, and good luck.

For someone who was planning to later develop his own personal anti-cheat this is very useful! Thanks writing this up!

I never found out how RunString, PCall and such can be used to cheat? Mind explaining me those?

If RunString is detoured, the client can log what is sent through it, which means code that you want to be private is exposed. pcall is completely unrelated, I just used it in that example to check if string.dump threw an error.

Global.CompileString is actually better to use than RunString, I just used RunString as an example. It is just as susceptible as RunString, it just works better because you can check for compiling errors and you can use a custom identifier.

Ahh, thanks very much!

It’s only consistent on each operating system, if you’re only checking one value (which would be the windows one) then you’re gonna get false positives for anyone using mac or linux.

Although this is a very good thread for beginners just keep in mind that all of these “anticheat” methods can not detect a c++ ESP or a proper C++ cheats and they never will thankfully.

I have had several experiences on many servers and none of the so called “anticheats” have ever worked against a pure c++ esp, I can’t say the same for other features such as speedhack, bhop ect even if they are in c++ but these will almost never be bypassed. You may ask why are you using a c++ esp? Simply for utility, to find players quicker and to make sense of the map, not to cheat but just because its useful for DarkRP and other servers that are similar ect.

H). However I don’t think there’s any method to use aimbots on CAC servers as it will kick you for the slightest modification to the eye angles, but I guess this is a good thing because aimbots can only be used for bad unless maybe HackvHack or if the person has a disability…,.

The only anticheat that is good in gmod is HAC (hex anticheat) it is amazing, even though that can’t detect a c++ ESP it still is an amazing anticheat that other anticheats have stolen detection methods from and if i were to make an anticheat I would read this code https://github.com/MFSiNC/HAC/tree/master/v13/HeX’s%20AntiCheat first.

Except that is cheating, you are getting an unfair advantage over other players.

Well it had to be done, I think its a FAIR advantage. and I’m not affecting other peoples experiences negatively so it’s fine in my opinion., , , , …,

Thanks, I’ve added that to the OP.

You could at least take the time to read a single paragraph at the very beginning of the post.


local memory = collectgarbage("count")
local RunString = RunString
...

Be careful if you are going to store these kinds of values on the client.
Remember that somes cheaters can be able to attack these values using:


debug.getlocal / debug.setlocal

You may want too to detour debug.getlocal / debug.setlocal to prevent cheaters finding/changing your variables.
(And then report to server if a cheater is trying to change your local variables using debug.setlocal)

Also, fenv things can be a risk if I’m not wrong.

Some dll has to be on the hl2.exe (thinking about inb4autorun.dll) folder to work, don’t forget you can use the undocumented “BASE_PATH” in file.Find’s second argument, so you can scan the entire gmod folder.

You’d think that someone who coined the term “Big Server Men” would recommend something from someone who didn’t prove time and time again to be a psychopath.

Can you stop posting we already know this you don’t have to flaunt your ~C++ undetectable esp~ that is most likely under 10 lines long including player iteration. While not detectable it can be rendered useless.

Thanks for this thread, I’ll finally be able to bypass those pesky anticheats!

There are Lua anticheats that are currently in development/in use that can detect C++ ESP’s, It’s just that not many people know about them.

Checking for bad metatables on objects like _G, net.Receivers, hook.Hooks and concommand.CommandList, where there shouldn’t be a metatable is very useful to detect cheaters. Cheats use this to hide added concommands and hooks.

debug.getlocal(func, index) can also be used to check function integrity - cheats may detour the function and change the parameter names, and debug.getlocal doesn’t show C-function’s parameters aswell.

Blue Kirby’s convar query module is very useful aswell, it’s only server-side (http://forum.facepunch.com/showthread.php?t=1311304).

“thankfully”. What? How is this a good thing?

99.9% of the GMod community can’t make their own proper bypass (or even an sv_allowcslua forcer).
The reason anticheats fail to detect pure C-based cheats is because there is barely any way to detect them. You’ll only detect them if they leave traces in the engine (cvars, concommands), or if they modify the lua_State badly. Speedhack is incredibly easy to detect but also prone to false-positives, along with bhop - even if done in C.
In what way is a pure C++ ESP a “utility to find players”? That isn’t an excuse at all.

CAC’s aimbot detection is off by default (iirc) and is known to be pretty bad.

Doesn’t Ley’s serverside AC have a really good aimbot detector?

Don’t know, I’ll change what I said in the original post.

Well, LAC randomly banned people for doing nothing for my server.
So I think there’s no really good aimbot anti cheat at the moment?