Having trouble with exploiters? Unsigned can help!

Unsigned

Because game development is not anti-cheat development.

Notice: Unsigned is in heavy development currently, use with caution.

What is Unsigned?

Unsigned is a module that hides RemoteEvents and RemoteFunctions from the client. Unsigned makes all Remotes that it is given parented to nothing, and changes their name constantly so they cannot be indexed on the client after Unsigned has initialized.

Server vs. Client

As we know now, Unsigned skews the names and parents of the Remotes. The question is now though, how will this affect the existing scripts for Remotes? The answer, little to none! All that is required to adding the Remotes to the Obfuscation list. Hereā€™s a comparison of both the serverā€™s and clientā€™s view of ReplicatedStorage.

Client image

Server image

As you can see, the remotes are nowhere to be found, and cannot be indexed.

A live example of Unsigned in action is hosted here and is uncopylocked!

Installation

You can either download it from the releases section of the GitHub repo or the Toolbox

The Future

  • Unsigned will have many more capabilities in the future, such as GUI detection, which will include Dex.
  • Plugin System
  • roblox-ts Support
  • Full-Fledged Anti-Cheat

If you would like to follow the development or contribute, Unsigned is hosted on GitHub!

The documentation is hosted here.

6 Likes

Ok itā€™s a good idea and thought out but hereā€™s a situation where it might not work:

I have a gun system where I need to have a remote event fired? It would be a bit hard to find the remote and a exploiter could look at that code and find it should I use this module or have my own detection?

Unsigned mitigates this issue by using the UnsignedEvent object that you create with Unsigned:AddEvent().

-- Get Unsigned Module
local Unsigned = require(path.to.Unsigned)

-- Get Event
local Event_Object = game.ReplicatedStorage:WaitForChild("Event")

-- Create UnsignedEvent object
local Event = Unsigned:AddEvent(Event_Object)

-- Initialize Unsigned
Unsigned:Init()

No matter what happens to the Event, you will always have access to it. The only difference is you have to create a UnsignedEvent object in order to interact with it.

If you have any questions, please do ask.

1 Like

Ok and in this local script a hacker could view this code and activate the Unsigned event?

While exploiters may be able to view the LocalScript, they are not able to inflict any harm onto the script. So, you should be fine.

1 Like

very good then I would see this being used in a fps or simulator

This module is waiting to be exploited, besides, this is completely unnecessary if you know how to protect your remotes in the first place. You donā€™t need to ā€œhideā€ them (as stated here) if you just make server side checks which make it so firing the remote for exploitative purposes is rendered useless.
GUI detection is also a pointless endeavor, as you can just draw the GUI to the screen, making it impossible to detect, or it can be easily patched if anything.
The module is coded a little inefficiently, as the UnsignedLogger class literally has 3 almost copy-and-pasted functions, which is extremely redundant.

9 Likes

Really well thought out, this is very useful, Iā€™ll try it out

1 Like

Your better off learning how to actually protect your remotes instead of thinking that you are safe just because you are using this module.

4 Likes

I never said I ai t going to do it, I know that learning things yourself is always better than getting a ready-to-use product

Also, Iā€™m not an expert and I didnā€™t look at the code, but as you are an expert Programmer, what is wrong with this script?
(Asking this so I will learn in the future)

local logger = {}

function logger:Log(...: table)
    local message = ""

    if type(...) == "table" then
        for _, arg in pairs(...) do
            message = message..arg.." "
        end
    else
        message = ...
    end

    print(
        "Unsigned:",
        message
    )
end

function logger:Warn(...: table)
    local message = ""

    if type(...) == "table" then
        for _, arg in pairs(...) do
            message = message..arg.." "
        end
    else
        message = ...
    end

    warn(
        "Unsigned:",
        message
    )
end

function logger:Error(...)
    local message = ""

    if type(...) == "table" then
        for _, arg in pairs(...) do
            message = message..arg.." "
        end
    else
        message = ...
    end

    error("Unsigned: "..message)
end

return logger

This is quite literally 3 functions copy and pasted with a single line changed on each one (to specify if it is printing, warning, or erroring)

edit: Itā€™s also pointless, because looking through init.lua, it is used exactly the same as the print, warn and error functions, which means this is just overcomplicating a simple function call.

1 Like

Okay, how is this even useful?
I donā€™t understand, simply send an Argument to the function and check the Argument, if itā€™s one print, else warn, else error
(Thatā€™s how you do it right?)

Itā€™s not useful, itā€™s just clutter.

if self.Initialized then
        Logger:Error("Objects cannot be added after initialization.")
end

This line uses it exactly like error(), which makes the entire point of having some ā€˜custom loggerā€™ class redundant. The extra work to do this makes it way slower than doing it in a normal way.

It doesnā€™t help that this module obfuscates remote names on RunServiceā€™s Heartbeat function, and not to mention that an exploiter doesnā€™t need the exact name of a Remote, as you can just iterate through ReplicatedStorage and assign a variable to each Instance you find returns true on the :IsA call.

2 Likes

Okay, I understand now, I wouldnā€™tve been able to see those with my current experience :sweat_smile:

1 Like

I programmed these with the intention of keeping it clean, but it seems that yes, you are right. Also, Unsigned is not going to single-handedly be only about hiding remotes while it may seem redundant, it restricts the environment that the attacker could use.

Your completely fine. You donā€™t realize this stuff unless you have seen how exploits work and understand the methods behind how itā€™s done. It makes you think differently about networking and protection.

1 Like

I actually didnā€™t thought of this!
Itā€™s a pretty smart move to do this method

(Although Iā€™ve never been in this situation)
I completely agree and understand, if youā€™ve been a criminal and you join the police you are gonna be way better, because you know how criminals do their work, and you have a bigger view of the system

While this is a good idea, there are flaws that can come into play here.

Synapse X for example has an advanced system ā€“ such system allows this entire module to be bypassed. One such way is this: remote event names are static; using any remote logger will allow you to receive the name of the RemoteEvent and itā€™s arguments. All you need to do is use getnilinstances().

local instances = getnilinstances()

for i,v in pairs(instances) do
    if v:IsA("RemoteEvent") then
        print(v.Name)    
    end
end

This allows you to call the FireServer() method on the RemoteEvent with no checks whatsoever

After testing, it appears you parent the RemoteEvent to game.ReplicatedStorage; all I need to do then is have a .ChildAdded or .DescendantAdded hook to determine the correct remote event.

local r = game.ReplicatedStorage

local event
r.DescendantAdded:Connect(function(m)
    if m:IsA("RemoteEvent") then
        event = m
    end
end)

while task.wait(2) do
    event.Parent = game.ReplicatedStorage
    event:FireServer()    
end

Your module is a great idea, but it unfortunately canā€™t stop exploiters in this way.

2 Likes

Thereā€™s no way to index the remote (Unless I screwed up royally somewhere). I have tested it thoroughly, even using youā€™re method that you mentioned. The Remote is parented to nothing, not even the game. It is only parented for when it is needed to be called even so, it doesnā€™t last long enough to be indexed. While I understand the better practice would be to just make the server the authority figure over the client, I found it helped overall, due to it causing a roadblock for exploits by not being indexable.