Universal RemoteSpy Detection

Please scroll all the way down before making a reply/criticism; I do not like ignorant people

Hey folksters. I’d like to showcase a method in detecting practically every public remotespy out there. Fundamentally, EVERY remotespy should be detected by this, as the behavior of a remotespy naturally should open the script up to this detection.

Watch this video and notice the behavior of a mimication of what a remotespy would do. See the results that occur.

What happened? Somehow the gcinfo difference in the before and after jumped dramatically from less than 10 to a little over 100. Later on, it drops down to somewhere around 30-60, but it only needs to detect you once.

This behavior can be taken advantage of by using this snippet of code:

local remote = Instance.new("RemoteEvent", game:GetService("ReplicatedStorage"))
local tbl = {}
for i = 1, 300 do tbl = {tbl} end

while task.wait(2) do
    local oldGcinfo = gcinfo()
    pcall(function() remote:FireServer(tbl) end) -- should error but that's not important for this case
    -- checking difference of gcinfo
    if gcinfo() - oldGcinfo > 2^6--[[ == 64]] then
        warn("Remotespy Detected")
    end
end

The best part about this detection? It will never false. Normally, the gcinfo difference always remains at, or below 10, and it only jumps up because there is a Lua argument handler present that is hijacking the FireServer and translating the args into something safe for the exploit to use.

This is the code that was used in the video, it merely serves to demonstrate the difference between a normal and remotespy-hijacked attempt to fire to the server:

-- code snippet used in the video
-- performs the original fireserver then mimics the behavior of a remotespy

local MaxLuaStackSizeReached = {}
for i = 1, 300 do -- second number could be bigger if you want to make the gcinfo difference greater
	MaxLuaStackSizeReached = {MaxLuaStackSizeReached}
end

local remote = Instance.new("RemoteEvent", workspace)

local FireServer = function(...)
	pcall(function(...) remote:FireServer(...) end, ...)
end

print("Running normal version")
for i = 1, 3 do
	local oldGcinfo = gcinfo()
	FireServer(MaxLuaStackSizeReached)
	local newGcinfo = gcinfo()
	print("Gcinfo difference: " .. newGcinfo - oldGcinfo)
	task.wait(1)
end

-----------------------------------------------------

-- function from simplespy
-- fundamentally you cannot simply call table.clone on a table, as there could be
-- values in that table that are being weaktable checked. if you do table.clone
-- you hold a reference to those values, and that opens you up for a separate
-- detection (you could also make the table so big this function stack overflows)

local function deepclone(args: table, copies: table): table
	local copy = nil
	copies = copies or {}

	if type(args) == 'table' then
		if copies[args] then
			copy = copies[args]
		else
			copy = {}
			copies[args] = copy
			for i, v in next, args do
				copy[deepclone(i, copies)] = deepclone(v, copies)
			end
		end
	--[[elseif typeof(args) == "Instance" then
		copy = cloneref(args)]]
	else
		copy = args
	end
	return copy
end

local args = {};

local function BadArgHandler(arg, numTabs)
	numTabs = numTabs or 0
	local str = string.rep("\t", numTabs).."{\n"
	
	for i, v in pairs(arg) do
		str = str .. BadArgHandler(v, numTabs + 1) .. ",\n"
	end
	
	str ..= string.rep("\t", numTabs).."}"
	return str;
end

task.spawn(function()
	-- example of remotespy cache for arguments
	repeat task.wait(1) until args;
	task.wait(5)
	for i, arg in pairs(args) do
		print(BadArgHandler(arg))
	end
end)

local old = FireServer -- example deter
FireServer = function(...)
	table.insert(args, deepclone(...)) -- clone used because a script could hold a reference to the table being fired
	print(...)
	return old(...)
end

-----------------------------------------------------

task.wait(2)
print("Running deterred version")
for i = 1, 3 do
	local oldGcinfo = gcinfo()
	FireServer(MaxLuaStackSizeReached)
	local newGcinfo = gcinfo()
	print("Gcinfo difference: " .. newGcinfo - oldGcinfo)
	task.wait(1)
end

Enjoy. The client is a great defense tool and to say it should be thrown away and discarded in favor of the server would not be something I suggest you do. :wink:

17 Likes

Cool but isn’t this prone to false positives? and what’s stopping me from just disabling the LocalScript.

2 Likes

Are you sure this won’t cause false positives? Did you test on other devices?

2 Likes

stupid question, its not like anyone has a bypass for that

1 Like

won’t work across multiple games and possibly device, the whole point of the garbage collector is to collect garbage I.E strings or tables that are no longer in use which can vary quite a bit across games.

If a game wanted to implement this they’d likely have to check the values for false flags before every update push aswell as modify the values anyway.

1 Like

That wasn’t a question

2 Likes

mild ragebait, just dont say stupid things next time

1 Like

There are ways to hide the script and keep it running. It would just delay the inevitable for an experienced exploiter to figure out a way to disable it.

As for false positives I think that could be easily tested on your end. And if you do find them causing false positives. Then you could show that here. ^^

1 Like

Nah this is solid since it seems natural. I’d say 7/10 but it’s too common of a ragebait so a solid 6

Im not really experienced in my understanding of Garbage collector. But if I understand you right because of the game garbage collecting so much other instances it can cause the GC data to be foggy and have a higher prone to false positives. Do please correct me if I understood it wrong!

Thanks for clarifying. The truth is that I don’t really know that much about these type of exploits in general so I was just wondering if something like that was doable

1 Like

the hyphon check method

0/10 stolen from hyphon im reporting this to kicksboy

#GG

If you dont mind me asking what’s Hyphon?

south bronx anticheat made by kicksboy

uses a trillion tick and gcinfo checks

1 Like

Memory check in a dynamic memory language. What could possibly go wrong?

8 Likes

everything is prone to false positives, don’t assume and ban players like that. flag them or even prevent the action they trying to do if it’s something like interacting with an object or driving a vehicle.

also nothing stops you from disabling the LocalScript, this is just for fun i think (or to annoy people who don’t know Studio)

you can simple uncheck .Enabled property of the local script and it’s disabled, so no worries.

1 Like

The more important question here is how frequent are the false positives. The only example he provided showing off this system was in a place file which appears to be dedicated specifically to testing it.

How are we supposed to know this is reliable to use in a real scenario where the garbage collection is likely to be far more unpredictable and the frequency of false positives outweighing any potential benefits?

That’s close enough, the more strings and temporary objects being created the more it will increase.

2 Likes

@XoifailTheGod and I had a personal bet on whether the first 3 replies would contain something along the lines of “the client is weak” and “the script can just be disabled”. You fell right into that bet.

The gcinfo increase happens IMMEDIATELY after firing to the server. I don’t think there would be anything else in that millisecond of time of firing to the server where the gcinfo could magically jump up by 2 thousand or something like that. Besides, if you’re concerned about false positives, just increase the number from 300 to a bigger number like 1000, and change 2^6 to a corresponding maximum.