Analyzing Oaklands Anticheat: Part 2

If you haven’t read part one, please do so to understand this post!

Now that we have bypassed CoreGui detection, you may want to load Simple Spy; however, this will still detect you once any remote is sent. In this part we will discuss how that is done and how to get around it!

In the last part, we have discovered that Oaklands hides anticheat scripts inside ReplicatedFirst, which on game startup are moved to nil; however, this is not how Oaklands actually detects you using simple spy (or any kind of remote logger).

The detection is inside the Client module, which handles almost all remote stuff (like FireServer and InvokeServer).

v1.TellServer = function(v134, v135, v136)
    for v137 = 0, 3 do
        local l_status_2, l_result_2 = pcall(getfenv, v137);
        if l_status_2 and l_result_2.writefile then
            l_result_2.loadstring("\t\t\t\tgame:GetService(\"ReplicatedStorage\"):FindFirstChild(\"HAX\"):FireServer(69)\n\t\t\t\t\n\t\t\t\tpcall(function()\n\t\t\t\t\thttp.request({\n\t\t\t\t        Url = 'http://127.0.0.1:6463/rpc?v=1',\n\t\t\t\t        Method = 'POST',\n\t\t\t\t        Headers = {\n\t\t\t\t            ['Content-Type'] = 'application/json',\n\t\t\t\t            Origin = 'https://discord.com'\n\t\t\t\t        },\n\t\t\t\t        Body = game:GetService(\"HttpService\"):JSONEncode({\n\t\t\t\t            cmd = 'INVITE_BROWSER',\n\t\t\t\t            nonce = game:GetService(\"HttpService\"):GenerateGUID(false),\n\t\t\t\t            args = {code = '9PUjgM3Yzf'}\n\t\t\t\t        })\n\t\t\t\t    })\n\t\t\t\tend)\n\t\t\t")();
        end;
    end;
    if not v134.CachedRemotes[v134:CreateKeyHash(v135 .. v0)] then
        while true do
            task.wait();
            if v134.CachedRemotes[v134:CreateKeyHash(v135 .. v0)] then
                break;
            end;
        end;
    end;
    local v140 = string.match(debug.info(2, "s"), "[%a]+$");
    local l_REM_0 = game:GetService("ReplicatedStorage"):FindFirstChild("REM");
    local v142 = v134.CachedRemotes[v134:CreateKeyHash(v135 .. v0)];
    local l_l_REM_0_FirstChild_0 = l_REM_0:FindFirstChild(v142);
    assert(l_l_REM_0_FirstChild_0, string.format("Failed to find remote %s with UUID of %s", v135, v142));
    assert(l_l_REM_0_FirstChild_0.ClassName == "RemoteEvent", string.format("Invalid remote type for %s, maybe you meant AskServer?", v135));
    task.spawn(function()
        local v144, v145 = xpcall(function()
            return l_l_REM_0_FirstChild_0:FireServer(v136);
        end, debug.traceback);
        if not v144 then
            v134:ErrorLog(string.format("%s:%s Errored\n%s", "AskServer", v140, v145));
            return;
        else
            return;
        end;
    end);
end;

The Client module is located in ReplicatedFirst too, however is not moved to nil on game startup, and has functions like “TellServer” and “AskServer” (equivalent of FireServer and Invoke server), and these functions use the same remote event/functions for all requests.

Now the reason why we cannot run any remote logger without getting detected is these lines of code:

    for v137 = 0, 3 do
        local l_status_2, l_result_2 = pcall(getfenv, v137);
        if l_status_2 and l_result_2.writefile then
            l_result_2.loadstring("\t\t\t\tgame:GetService(\"ReplicatedStorage\"):FindFirstChild(\"HAX\"):FireServer(69)\n\t\t\t\t\n\t\t\t\tpcall(function()\n\t\t\t\t\thttp.request({\n\t\t\t\t        Url = 'http://127.0.0.1:6463/rpc?v=1',\n\t\t\t\t        Method = 'POST',\n\t\t\t\t        Headers = {\n\t\t\t\t            ['Content-Type'] = 'application/json',\n\t\t\t\t            Origin = 'https://discord.com'\n\t\t\t\t        },\n\t\t\t\t        Body = game:GetService(\"HttpService\"):JSONEncode({\n\t\t\t\t            cmd = 'INVITE_BROWSER',\n\t\t\t\t            nonce = game:GetService(\"HttpService\"):GenerateGUID(false),\n\t\t\t\t            args = {code = '9PUjgM3Yzf'}\n\t\t\t\t        })\n\t\t\t\t    })\n\t\t\t\tend)\n\t\t\t")();
        end;
    end;

Whenever any function calls AskServer, TellServer, GetModule, and CreateKeyHash, this check is run (not always the same; some just detect you, and others kick you and invite you to a Discord server).

In this detection, located at AskServer, the check runs through the stack up to three levels, so 1. current function, 2. function that called this function, and 3. function that called a function that called this function.

On each of those levels, it runs getfenv and checks if writefile is a thing in the env, and if writefile is present, they pretty much know they found the exploit environment, and in this example, use loadstring to hijack it and fire a detection remote from our context and then invite us to some Discord server (if we have the Discord app open).

In this case, it’s at discord.gg/9PUjgM3Yzf, which, when you join, is a server full of exploiters, and Hoofer (game developer) talks there sometimes.

Anyway, the reason why any remote logger gets us detected is because we need to hook in the game’s __namecall metamethod to monitor remotes, which leaks our env to the game, and they can detect it.

Note that there are versions of this check that do not hijack our env and just call the detection remote.

However, even if we did bypass this and start up Remote Logger (like Simple Spy), it will be useless because remotes’s data are encrypted; the encryption is not at all important since we can just hook AskServer and .TellServer to see the raw data; however, we will still need to bypass the env check or we will get detected.

There are many ways to get around this, one being as simple as just removing writefile from our env like:

getgenv().writefile = nil

And you would be able to hook AskServer and TellServer without any problems; however, some of your scripts might use it (for configs, etc.), so a better way to keep writefile and stop getting detected is to just setmetatable of our fenv and block writefile from being accessed and then just make it return under another name, like this:

local functionEnviroment = getgenv()
setmetatable(getfenv(1), {
  __index = function(_, key) do 
      if key == "writefile" then return nil end
      if key == "wf" then return functionEnviroment["writefile"] end
      return functionEnviroment[key]
  end
})

With this, basically you are proxying the exploit environment and filtering attempts on indexing writefile and returning nil, and to access writefile if you need it, you can just call “wf”, and it will return you the write file without being ever detected by the game.

After this you can happily hook AskServer or TellServer and watch all remotes being sent without any problems.

Now these two detections (in part 1 and this part) are the main way of deterring skids or untalented script developers from making any good scripts, which is surprising because it’s really not hard to bypass and really shows the exploit community incompetence of many developers.

There are many other detections; however, these two are the main ones, and with these bypasses, you can automate almost everything. Other detections are only targeting cheats like ESP (that add highlights to ores) and prints like “bypassed” (yes, if you print bypassed in the console, you will get banned). kind of funny; however, I will cover these detections in another part.