Analyzing Oaklands Anticheat: Part 3

In the last two parts, we went over the main detections, and today we will go over the miscellaneous detections Oaklands has to detect cheaters. Part 3 will be split into smaller sections representing each detection. Keep in mind there is too much to cover everything in one part.

Detection Remote Integrity Checks

Oaklands employs multiple ways to make sure you will get detected, and one of their way is ensuring the “HAX” remote wasn’t moved, in one of their AC scripts, they connect to Heartbeat and run this code:

game:GetService("RunService").RenderStepped:Connect(function()
    if l_HAX_0.Parent ~= game:GetService("ReplicatedStorage") then
        task.spawn(function()
            task.wait(1);
            for _, v6 in pairs(game:GetService("ReplicatedStorage"):GetDescendants()) do
                if v6:IsA("RemoteEvent") and v6.Name ~= "HAX" then
                    v6:FireServer({
                        val = true
                    });
                end;
            end;
        end);
        v3:FireServer({
            val = 16
        });
        l_HAX_0:FireServer(16);
    end;
end);

On each execution, they check if the HAX remote is indeed located in ReplicatedStorage, and if not, they start a new thread that every second goes over all descendants of ReplicatedStorage, and if the object is a RemoteEvent and isn’t named “HAX”, they fire it; practically, they are finding any remotes to fire, and when they do, they get fired, and you get detected.

Additionally they fire a v3 RemoteEvent, which is also a remote event but not choosen here, we will get to that too. Also in hopes that you have just moved the HAX remote they will try to fire it.

Let’s talk about the v3 variable being fired: where does that come from, and how is it set? Well, at the start of each AC script, they run a loop that sets the variable, and here we can see how it does it:

local l_Children_0 = game:GetService("ReplicatedStorage"):WaitForChild("REM", 1.0E99):GetChildren();
task.spawn(function()
    task.wait(1);
    if #l_Children_0 == 0 then
        while true do
            l_Children_0 = game:GetService("ReplicatedStorage"):WaitForChild("REM", 1.0E99):GetChildren();
            if not (#l_Children_0 <= 5) then
                break;
            end;
        end;
    end;
    while not v3 do
        task.wait();
        v3 = l_Children_0[math.random(1, #l_Children_0)];
        if not v3:IsA("RemoteEvent") then
            v3 = nil;
        end;
    end;
end);

After one second they will check if the folder “REM” in ReplicatedStorage has 0 children (is empty), and if it does, they enter an infinite loop that sets the Children variable to the new list until it has more than 5 remote events.

After this happens, they enter another loop; this time it will loop until v3 (which is the variable used in the Heartbeat loop) is not nil. In this loop they select random children, and if the child is not a RemoteEvent, they remove it so they can continue in the loop until they find a valid RemoteEvent.

This ensures that every time we play, we will get a different remote event to fire, which might make it harder to block; however, considering the fact that all of them are parented to the REM folder, you could easily check for that and block all events to them.

This pretty much covers everything they do to make sure if you trigger any detection, it will be sent to the server (weak too, I know), and if you are smart enough to never trigger any detection (which isn’t hard to do), you don’t have to worry about this.

Console Logs Detection

Oaklands also decided to employ a detection that checks for any suspicious prints; this may catch any exploiters that love to be edgy with their “successfully bypassed” prints, which may be effective but funny seeing people fall for this, so let’s go over this detection.

game:GetService("LogService"):Connect(function(v5, v6)
    if v6 == Enum.MessageType.MessageError and string.find(v5, "with 'oldfuncteehee'") then
        v3:FireServer({
            val = 3212
        });
        Hax:FireServer(3212);
    end;
    if v6 ~= Enum.MessageType.MessageOutput then
        return ;
    else
        if string.find(v5, "bypassed") then
            v3:FireServer({
                val = 14
            });
            l_HAX_0:FireServer(14);
        end;
        if string.find(v5, "Correct Key.") then
            v3:FireServer({
                val = 320
            });
            l_HAX_0:FireServer(320);
        end;
        if string.find(v5, "Anti Ban Active.") then
            v3:FireServer({
                val = 420
            });
            l_HAX_0:FireServer(420);
        end;
        if string.find(v5, "Toggled nil") then
            v3:FireServer({
                val = 31
            });
            l_HAX_0:FireServer(31);
        end;
        if string.find(v5, "Invalid Token") then
            v3:FireServer({
                val = 89
            });
            l_HAX_0:FireServer(89);
        end;
        if string.find(v5, "LuaWare Loader Succes") then
            v3:FireServer({
                val = 93
            });
            l_HAX_0:FireServer(93);
        end;
        return ;
    end;
end);

Their AC script connects to MessageOut, which is fired every time any script calls any of these functions: print, warn and error. In the callback, they first check if the type is error (so any error log), and if the log contains 'oldfuncteehee', they send a detection, and you’re cooked.

We also see attempts to detect specific scripts like “LuaWare” and others that use prints like “Anti ban active.”, “Correct key” and much more. We also see usage of our variable v3, which is the randomly chosen remote event we discussed above.

Overall, this might be an effective and easy way to patch some detections but is really temporal, as any competent cheat dev will find this and can either stop printing or disconnect the AC connection, like this:

for _,connection in next, getconnections(game:GetService("LogService").MessageOut) do 
  if connection.Function and islclosure(connection.Function) then 
     hookfunction(connection.Function, function() end)
  end
end

All that we are doing is getting all connections to the MessageOut signal and making sure the connect callback is there and is a Lua closure (basically, it is a function defined in Lua) because we do not want to disconnect any internal connections Roblox might have, which would be in C. If we wanted to be even more specific, we could simply get constants and check for any specific one and then hook it.

You may be asking why I just don’t call connection:Disconnect() or connection:Disable() The reason is simple: they could easily make a loop that checks if the connection is connected or enabled; this way they have no way of knowing.

Service Detections

Many untalented script developers might go the way of using VirtualInputManager and VirtualUser services to automate things like clicks; however, they will get detected too. How? Pretty simply, let’s look at the code that does this:

local _ = game:GetService("RunService").Heartbeat:Connect(function(_)
    if (not not game:FindService("VirtualUser") or game:FindService("VirtualInputManager")) and not game:GetService("RunService"):IsStudio() then
        v3:FireServer({
            val = 73
        });
        l_HAX_0:FireServer(73);
    end;
end);

Again, their AC script connects to Heartbeat, and every time it is executed, they use the FindService method to see if a specific service is created, and because by default both VirtualInputManager and VirtualUser are not created, the only way FindService would return them is if exploit created them; they also check if we are running in Studio, and if yes, they do not send any detection.

However in normal scenario, a detection gets sent and you are banned.

Thanks for reading, and make sure to not trigger that detection remote 👽