H6x - Secure Lua Sandbox for User Code (Early Prototype)

Disclaimer

This is a VERY early prototype of H6x. Because of this it’s full of security issues (a few of which are mentioned in the unsupported features/TODO), bugs, performance issues, and more.

It’s pretty messy, so, I don’t recommend using it yet, but, if you’re interested, I really encourage you to play around with its features and provide feedback! That’s why I am releasing this prototype, after all, the more feedback, the better. Any concerns, suggestions, or issues you have are greatly appreciated.

H6x - Script Sandbox

Hello! :smile: I previously created this older script sandboxing tool called H3x which had a lot of issues.

H6x is intended to be a friendlier remake of that tool, and, should feature a lot of nice utilities for games which want to run user code. H6x will be less focused on being a tool for “true to Roblox” sandboxing, and more focused on being a tool for user code.

This tool has a few requirements for me, mainly, I want an easy way to utilize people’s sandboxed code outside of the sandboxed environment, and, I want an easy way for people’s sandboxed code to utilize unsandboxed code without being able to accidentally introduce sandbox escapes.


Prototype

After quite a few months here, I finally have what I consider to be a stable enough prototype for release. This is somewhere around my 15th unique attempt, all previous attempts had fatal flaws in their sandboxing, bugs, or were too difficult to use.

You may have originally seen me write about a prototype of H6x close to the end of 2020 on the original H3x thread. The prototype mentioned then is not this one, in fact, I have gone through several prototypes since that point. I decided to hold off on releasing anything until I was fairly confident with something.

Download (Latest Release)

Use at your own peril!
H6x Prototype 5-3-21.rbxm (18.9 KB)
The above includes a script which runs H6x’s included test modules. These tests are pretty basic and don’t even remotely cover all functionality. If you find a bug, please let me know! Each bug I fix will likely get its own test module or two to make sure it doesn’t show up again.

Recommendations & examples for blacklisting:

This is an example of how I would recommend you implement blacklists. You should blacklist most things under a blanket, and, only allow what makes sense to allow for whatever you are sandboxing for.

sandbox:Blacklist(require)
sandbox:BlacklistClass("Instance") -- Due to the order blacklists are checked, BlacklistType will prevent you from excepting classes

-- Example of how to except certain classes
sandbox:ExceptClassName("Model") -- Only Model classes, but, not things like Workspace which extend Model
sandbox:ExceptClass("Folder")
sandbox:ExceptClass("BasePart")
sandbox:ExceptClass("LuaSourceContainer")

You can even do this, for example, though, this isn’t ideal since it can break their scripts:

sandbox:BlacklistClass("Instance") -- Blacklist all instances
sandbox:BlacklistType("function") -- Blacklist all functions (Even their own except when used only as locals/upvalues)

sandbox:Except(print)
sandbox:Except(warn)
sandbox:Except(error)
sandbox:Except(debug.traceback)

If you want to, you can also create an empty environment to work with and insert values into it externally:

local environment = {
	print = print,
	error = error
}
-- This will eventually get its own feature, but, you can do it manually like so:
sandbox.BaseEnvironment = nil -- Currently this is required, otherwise the old environment gets applied to a few things internally by the environment class (Technically a bug, but, its just a remnant of some previous changes)
sandbox.BaseEnvironment = H6x.Environment.new(sandbox, environment) -- Override the sandbox's base environment

-- Elsewhere you might assign values too:
environment.abc = 123

Features in the prototype

This prototype version of H6x contains a few core features, and, will likely differ greatly from later versions of H6x. Don’t rely on APIs remaining consistent.

Blacklisting

In H3x, you defined custom hook handlers to decide what happens to values, when, and how. H6x has greatly simplified this into a blacklist system. More control will be added for more custom behaviours, but, currently blacklisting is the only way to do this.

You can blacklist types, specific values, specific ClassNames, and specific class types of instances.

Value “poisoning”

In H6x, values become “poisoned” inside of a sandbox. These poisoned values always yield other poisoned values when used in code, and, they are proxies to the unpoisoned (real) values.

H6x relies on the idea that a script can only access outside data using inside data. When a script is ran using H6x, a sub environment is created using the environment the H6x module runs in. The sub environment automatically poisons all values requested from it, which, means that a script should only ever be able to access poisoned values no matter what, even if you try to return an unpoisoned value to the sandboxed code.

Sandbox Imports & Exports

A lot of other sandboxes used a similar approach, but, sometimes they want to load an unsandboxed value inside of the sandbox, so, sometimes exceptions are made so that external code can do unsandboxed actions.

H6x uses a system of “imports” and “exports” instead. You can import functions into a sandbox to make them compatible with the sandbox so that all values given to your external code become unpoisoned and all values you give back become poisoned.

You can export functions from the sandbox to make them compatible with use in external code, so, when using them in external code, all values you pass in are poisoned, and all values you get out are unpoisoned.

Currently unsupported features/behaviours on the TODO list:

Currently, the following features are intended to be in later versions of H6x, but, currently do not exist:

  1. pairs & ipairs for poisoned values. Currently the table will appear empty (since its just a proxy to the original). This will be solved by replacing pairs and ipairs with custom versions which will iterate over the unpoisoned version of the table and result in poisoned values and keys. (This was fixed due to a mechanism change, all functions initially in the global environment will work as expected. By default there is an explicit fix enabled in case this mechanism change is reverted in the future)
  2. Direct value redirection (E.g. directly redirecting any references for workspace.Baseplate to workspace.OtherBaseplate). This will be utilized by the above, and, will also make it easy to override specific functions just like you can blacklist specific functions (e.g. you might override Instance.GetChildren with a custom version. Since all instances reference the same function this would work for all instances) Values can now be redirected directly, or via a handler function.
  3. Activity tracking & name data (This should realistically come next update):
local RunService = game:GetService("RunService")

RunService.Heartbeat:Connect(function()
    -- Stuff
end)

Would be able to generate a data structure for lua code and/or something readable looking somewhat similar to the following pseudo example (Producing vanilla lua bytecode is also planned but probably won’t be implemented at the same time):

get game
get <Previous>.GetService
call <Previous>(<Previous-1>, "RunService") @ game:GetService("RunService")
get <Instance RunService "Run Service">.Heartbeat
get <Previous>.Connect
call <Previous>(<Previous-1>, <function 0xffffffff>) @ <Instance RunService "Run Service">.Heartbeat:Connect(<function 0xffffffff>)
  1. getfenv & setfenv protections. Currently, for exported functions (excluding functions executed in a runner) getfenv and setfenv can be used by malicious code to modify the environments of scripts outside of it. They can’t technically escape the sandbox, but, they can manipulate an explore your code this way and potentially gain access to H6x.
  2. Protections against sandboxed code accessing H6x functions. Currently sandboxed code can access H6x if you allow it to, and, this is bad because it would allow the sandboxed code to manipulate its blacklist, or, change settings, effectively removing sandbox restrictions from itself. This will be solved by marking any H6x values and instantly terminating when a sandboxed piece of code attempts to access them. (If a H6x function or table, or the sandbox itself gets poisoned, the entire sandbox shuts down immediately) This is introduced via the Restriction features.
  3. script variable emulation. Currently, the script variable will be H6x’s module, because, this is the script the environment is extended from. If H6x’s module isn’t blacklisted, a piece of code can call require(script) to access H6x. This is introduced as the Sandbox:SetScript() function, and, currently is just a value redirect under the hood but in the future may get support for .Disabled and other things.
  4. (This is likely fixed by the same mechanism change that fixed ipairs/pairs, but, I didn’t implement a test module so I won’t guarantee it yet) require for ModuleScripts. Currently, the require function isn’t automatically imported. This means that sandboxed code cannot require module scripts since the require function receives a poisoned copy of the module. It can, however, require modules by their id (e.g. require(12345)).
  5. In-Script Runner. Rather than using a ModuleScript for the runner, a regular Script would be used. This would allow Termination via the .Disabled property which is much more proper and would fix a lot of things with sub threads and event connections beyond what has been newly introduced.

Documentation

Sandbox

The Sandbox class is the only class you really need to use. The other classes in H6x are used by the Sandbox class and the Sandbox class currently exposes better ways to interact with those classes than they do.

Sandbox

Sandbox Sandbox.new(dictionary options) - Returns a brand new sandbox, optionally configured with the given options.

The options table (with the defaults) looks like this:

{
	NoRestrictionDefaults = false, -- Whether or not to apply default restrictions automatically (Can be applied manually with Sandbox:RestrictionDefaults())
	NoRedirectorDefaults = false -- Whether or not to apply default redirectors automatically (Can be applied manually with Sandbox:RedirectorDefaults())
}

function Sandbox:LoadFunction(function functionToLoad) - Applies the sandbox environment to the given function & exports it, returning the exported copy of the function
function Sandbox:LoadString(string code) - Loads a string of code using loadstring and calls Sandbox:LoadFunction()


void Sandbox:Restrict(Variant value, Variant error, boolean terminate) - (Similar to blacklisting) Restricts a given value with an error message (If unspecified the built in “No output from Lua.” message will occur instead of the given error message). Optionally terminates the whole sandbox if accessed. All primitive types which don’t use references are ignored. (E.g. thread can be restricted but string cannot)


void Sandbox:RestrictTree(table object, Variant error, boolean terminate) - Restricts an entire table and tree of values. This is used to restrict the H6x and its sub functions from being accessed. Currently for the sandbox itself basic restriction is used since using this would restrict every value the sandbox has access to. In the future the Sandbox’s class will be restricted and specific properties of the Sandbox will be restricted.


void Sandbox:EnableActivityTracking() - Currently just throws an error. In the future this will enable activity tracking.


void GenerateActivityReport(string format) - Generates a report of a script’s full activity. Valid formats are data, which returns a structured table (which can be manipulated by your code directly to change the internal activity history) or h6x which returns a readable format similar to the one mentioned above. luac is a planned format which will return vanilla lua bytecode (Not to be confused with “luac scripts” used in some old exploits)


Variant... Sandbox:ExecuteFunction(function functionToExecute) - Loads the function into the sandbox and runs it in a Runner script (Returns the unpoisoned results)


Variant... Sandbox:ExecuteString(string code) - Identical to the above (Except it loads a string)


function|table|userdata Sandbox:Import(function|table|userdata value) - Imports a value into the sandbox for use in the sandbox.

Imported tables are simply marked so their child values get imported. Imported functions take poisoned values, unpoison them, passes them to the original function, poisons the results, and finally returns them back to the sandbox.


function Sandbox:Export(function value) - Exports a function retrieved from the sandbox for use outside of the sandbox. Currently tables cannot be exported automatically.

Exported functions take unpoisoned values, poisons them, passes them to the original function, unpoisons the results, and then returns them back to you. It’s not recommended that you provide sandboxed code with access to exported functions as they may provide it a way to partially access unpoisoned values.


Variant Sandbox:Poison(table|userdata|function object, int errorLevel, int restrictionErrorLevel) - Takes an object and returns a poisoned copy of it for the given sandbox. If the object is a primitive its returned with no changes. Poisoned values should automatically poison anything accessed from them. (E.g. poisoned + unpoisoned, poisoned.Index, etc should all be poisoned values).

errorLevel can be provided to result in an error when a blacklisted object is accessed, if this argument is not provided the object will be replaced with nil. This level determines where in the stack tree the error happens (e.g. 1 = at the place you called it, 0 = at the root of the script)


void Sandbox:Redirect(Variant valueA, Variant valueB) - Directly redirects any tracked references for valueA to valueB. valueA must be non nil.

Note: Only one redirect or redirect handle can be set for valueA, repeated calls will overwrite the redirect.

This also cannot globally redirect a value, meaning it can be detected by ran code if they have untracked access to valueA since, its well, untracked. For example, if you redirect a string "abc" to "cba" the script can still use the string "abc" as long as they don’t store and then access it in a tracked location (such as in a local variable)

In the future there may be support for tracking locals and upvalues for executed/loaded string code by injecting tracker code around each. For example, injecting this to wrap any local or upvalue:

-- Before injects
local someValue = "abc"
doSomething(someValue)
-- After injects (A randomized tracker function is added in the global environment that the script would have to brute force to access intentionally)
local someValue = __track1DB8915C795447CC90BA8C71E18B7CE5("abc")
doSomething(__track1DB8915C795447CC90BA8C71E18B7CE5(someValue))

void Sandbox:RedirectHandle(Variant valueA, function callback) - Same as above, except, valueB is derived from the callback function. The callback function is passed the sandbox as the first argument and whatever valueA is as the second argument. (There is also internal behaviour to omit the sandbox as the first argument and call the callback in a “safe mode” but there is no way to utilize this yet. This is in case you want to utilize a function from a sandbox as the handle in a more performant way than wrapping the callback in a function)


void Sandbox:RemoveRedirect(Variant valueA) - Removes the redirect or redirect handle for valueA


void SetScript(Script newScript) - Sets the emulated script to the target script instance. (Currently this behaviour does not differ to redirecting the H6x module script to the target, but, in the future this may support things like syncing the .Disabled property with script termination)


void Sandbox:RedirectorDefaults() - Applies default redirects (See options arg in Sandbox.new())


void Sandbox:RestrictionDefaults() - Applies default redirects (See options arg in Sandbox.new())


void Sandbox:Blacklist(Variant value) - Prevents the given value from ever being accessed from a poisoned value, always returning nil instead. (E.g. Sandbox:Blacklist(game) prevents any reference to game and instead replaces it with nil)

If a primitive value is provided it will be rejected.


void Sandbox:Except(Variant value) - Creates an exception in blacklisting rules for the given value. This allows you to, for example, blacklist all types of Instance, but allow sandboxed code to only access script.


void Sandbox:BlacklistType(string typeName) - Blacklists the given value type. For example, “Instance”, “Vector3”, “CFrame”, etc.


void Sandbox:BlacklistPrimitive(string primitiveType) - Blacklists all primitives of this type (e.g. “string”). This isn’t very useful, but, it exists.


void Sandbox:BlacklistClassName(string className) - Blacklists a specific ClassName. For example, blacklisting Model but not WorldModel (which extends Model)


void Sandbox:ExceptClassName(string className) - Same as above, except, it creates an exception for that specific class


void Sandbox:BlacklistClass(string className) - Blacklists the given class and any class that inherits from it. (E.g. BasePart blacklists all part types)


void Sandbox:ExceptClass(string className) - Same as above, except, it creates an exception for that class type


void Sandbox:Unblacklist(Variant valueOrType) - Unblacklists a specific value reference or type name, or, if an exception was created for the specific value or type, clears it. Do not pass numerical values to this function if you blacklist primitives otherwise you’ll likely unblacklist different primitive types. (Primitive type are represented with an integer id)


void Sandbox:UnblacklistPrimitive(string primitiveType) - Unblacklists a primitive type.


boolean Sandbox:InBlacklist(Variant value, int errorLevel, int restrictionErrorLevel) - Checks if the given value would be blacklisted, or, optionally throws an error.

errorLevel can be provided to result in an error when a blacklisted object is accessed, if this argument is not provided the object will be replaced with nil. This level determines where in the stack tree the error happens (e.g. 1 = at the place you called it, 0 = at the root of the script)


Variant Sandbox:FilterValue(Variant value, int errorLevel, int restrictionErrorLevel) - The same as the above, except, if the value is blacklisted, nil is returned, otherwise, the value is returned. This is used by Sandbox:Poison() internally.


void Sandbox:Terminate(string terminationMessage) - Terminates the sandbox, disallowing any code from running within in (with the exception of basic for & while loops which are terminated automatically)

NOTE: This does not currently resume yielding code, so, threads can linger. Most threads will eventually be garbage collected (e.g. threads which permanently yield), but, it is possible for someone with malicious intentions to create a memory leak this way. This will be addressed in the future.


void Sandbox:CheckTermination() - Throws the termination error if the sandbox was terminated.


void Sandbox:Unterminate() - Unmarks the sandbox as being terminated. This does not resume any old code, that code would be dead, but, it does allow the sandbox to be reused.


Unfinished features

  • The environment class has a JavaScript style property descriptor system (E.g. Object.defineProperty & Object.defineProperties) This hasn’t been fully fleshed out yet. :get, :set, .value, .writeable, and .configurable are all valid properties for a descriptor. .configurable and .writeable are currently identical to eachother. For the getter and setter callbacks self is the equivalent of this from JavaScript (assuming you use the : notation).

Other classes (Need documentation)

  • Environment - A data structure for sub environments
  • Runner - A class which utilizes the Runner module to execute code in isolation with a Sandbox. This basically gives the target code its own script, stack trace, and completely isolates its environment.
  • Reflector - A class mainly intended for internal use which creates a metatable that reflects metamethods from a poisoned object to an unpoisoned object. This is used by the poison system to create proxy objects (And will import the metamethods by default excluding __index and __newindex)

Latest update

25 Likes

Legacy downloads

H6x Prototype 5-2-21.rbxm (15.1 KB)
H6x Prototype 4-12-21.rbxm (11.9 KB)

Status update

! REPORTING SECURITY ISSUES !

If you find a security vulnerability, please report it immediately over PMs! Security is a big concern of mine, so, finding these issues is very important. If you find a security issue it will get a test module crediting you for the find and if its a big enough concern I may discuss giving a decent Robux bounty.

Give me your feedback!

If there is anything that the current form of H6x lacks that you’d really want to see, ask. I want H6x to be easy to use and secure and most importantly I want H6x to be applicable to any game that’s already using a script sandbox as well as to future games that just want to run generic user code.

I want H6x to be acceptable not just for script builders and learning resources but also just generic games. The reason H6x exists in the first place is because I want to create something to utilize in my own game as an actual gameplay component. Almost no script sandboxing tools exist on Roblox and none that I’ve seen would generally be considered fit for a full fledged game, and, I would like to change that.

So, I really encourage you to make suggestions and be critical of H6x! All concerns are important to me.

May 3rd Prototype Update

  • Added lots of new tests and improved a few old tests, including a few bugged ones. Everything that’s tested against is verified to some degree.
  • Slightly changed some of the mechanisms behind how values are poisoned. This should improve security a little in some backup cases and should fully fix ipairs/pairs for poisoned values. By default a backup fix is applied anyways in case any of those mechanism changes are reverted.
  • Added value redirection. Used to replace one value with another.
  • Added value restriction. Used to semi-securely eliminate values from the sandbox’s access. Can be used to automatically terminate a script which references a restricted value or to error out the thread utilizing it. (This stops sandboxed code from accessing the H6x module by default and partially stops sandboxed code from accessing its own sandbox variable if either are somehow exposed. The punishment is a full termination of the sandboxed code)
  • Fixed errors resulting in error in error handling instead of An error occured: No output from Lua. when erroring with a non string or non numerical value. This fixes a case which could potentially allow a script to detect its running in a sandboxed environment. (Currently there are probably a lot more cases scripts can detect this, so, if you notice one, let me know)
  • Fixed a bug with Reflector which caused the sandbox environment (and other reflected values) to incorrectly compare as not equal with the real value of the reflector

Plans (In the short term)

  • Add activity tracking and a way to generate readable and processable activity reports in different formats for different use cases.
    • Example formats: Generic lua code, lua byte code, a custom readable format, etc
  • In-Script runner
  • Guaranteed support for require for ModuleScript instances

Planned release date

I plan and expect to have something I consider acceptable for most games before May 31st. In reality I expect that H6x will be ready in a week or two but that’s not at all a guarantee as I have a lot of things on my radar.

Download

H6x Prototype 5-3-21.rbxm (18.9 KB)

8 Likes

Status update

! REPORTING SECURITY ISSUES !

If you find a security vulnerability, please report it immediately over PMs! Security is a big concern of mine, so, finding these issues is very important. If you find a security issue it will get a test module crediting you for the find and if its a big enough concern I may discuss giving a decent Robux bounty.

May 3rd Prototype Update B (Pre-Release)

  • Script emulation and In-Script Runners. H6x will automatically create a server or local runner script depending on what environment its running in, so, it can be used on the client or server. (With the exception of string based execution since loadstring isn’t available on the client) Runners can still be manually created as a module if passed true in their constructor.
  • Termination now uses the Disabled property of the runner. This results in much more reliable termination. If you want to emulate a module script, you should use Sandbox:SetScript(emulatedModule) rather than manually creating a module runner since its more reliable to use the script.
  • Test logs can now be suppressed and additional arguments can be passed to tests (may be used to support special options for different environments)
  • Added a fast mode for tests. Currently this just skips the Termination test which uses long wait calls to confidently make sure a script terminated in time. There isn’t a great way to do this check without long waits yet and its too slow for doing quick security checks.
  • Reduced the wait time for the Termination check so tests run a little faster.
  • Added a security check feature. It runs all tests in fast mode and throws an error if any of them fail. This would indicate that something isn’t quite right with how H6x is functioning, and, that could be due to a Roblox bug or an H6x bug. You should use pcall when requiring H6x and, depending on how sensitive an attack would be for your game, you probably want to disable features in your game that rely on it. You can ignore the security check but its not recommended.

Documentation changes


void Sandbox:SetScript(Variant script) - Will redirect the script variable to the target value (Doesn’t have to be a script or an Instance). Also tracks the Disabled property if the passed value is a BaseScript instance, disabling or enabling the emulated script variable terminates or unterminates the sandbox.


Doing the security check (Recommended)

To properly handle the security check, all you have to do is just wrap the require result in a pcall. If the security error happens you’ll still get access to H6x but its not recommended that you utilize it for user code if your game could be impacted.

If the security check fails, the module is thrown as the error which is why you get it back from the pcall. The reason for the use of a pcall rather than a regular call is that in the future the error may be invoked indirectly inside of an H6x feature.

local isSafeToUse, H6x = pcall(require(pathToH6xModule))

if not isSafeToUse then
	-- Disable your H6x features if it might impact your game
end

local sandbox = H6x.Sandbox.new()

-- Some code utilizing H6x

Ignoring H6x security checks

To ignore the security check just don’t pcall. The H6x module will work as expected.

local H6x = require(pathToH6xModule)
local sandbox = H6x.Sandbox.new()

-- Some code utilizing H6x

Updated short term plans

  • Add activity tracking and a way to generate readable and processable activity reports in different formats for different use cases.
    • Example formats: Generic lua code, lua byte code, a custom readable format, etc
  • Guaranteed support for require for ModuleScript instances (This should be resolved already it just needs some tests)
  • The ability to redirect require calls in sandboxed code, and, a utility for targeting redirects externally. (For example, if you are emulating a module and you want that module script to actually return the result from the sandboxed code when required in sandboxed, or, if you want to do so in external code, providing a replacement require function)
    • This is primarily useful if you’re loading an asset and can read the source code, or if you are creating a modding system.
    • It should also allow you to redirect non instances and non numbers too, for example, if you wanted to emulate pure lua code which uses require("somemodule") you could redirect that as well.
    • Additionally, sandboxes should be able to disable numeric asset id requires.
  • Add some “presets” for different sandboxes. For example, H6x.Sandbox.Empty.new() might create a sandbox with no access to anything and all restrictions in place by default (e.g. numerical requires). H6x.Sandbox.Basic.new() might create a sandbox that only has access to the basic lua libraries and doesn’t have access to instances and has all restrictions in place by default.

Download

H6x Prototype 5-3-21 B.rbxm (22.1 KB)

4 Likes

what exactly is this i am super duper confused rn

Lua sandboxing.
Sandboxing is like having permissions of what you can do and what you can’t do.

then why is he/she recreating tht in luau…?

So you can create a custom User Code environment. A bit of an advanced application.

It’s a sandbox tool, so, basically you can stop whatever code you run in it from doing certain things you don’t want it to do. That’s useful if you want to, for example, stop code from accessing sensitive services, like TeleportService or MarketplaceService in a script builder, or if you want to stop scripts from requiring outside assets.

For example, this is actually one reason I have been developing H6x, in my game I want to be able to run people’s code so they can create controllers that they can put on things to control them with code.

The reason I’ve written it in luau is because its designed for Roblox specifically. It uses loadstring to load code so loading strings of code on the client isn’t possible but you can on the server by enabling that on ServerScriptService.

3 Likes

Legacy downloads

H6x Prototype 5-3-21 B.rbxm (22.1 KB)
H6x Prototype 5-3-21 A.rbxm (18.9 KB)
H6x Prototype 5-2-21.rbxm (15.1 KB)
H6x Prototype 4-12-21.rbxm (11.9 KB)

Documentation additions


void Sandbox:RestrictAssetRequires() - The default. Disables numerical (asset) requires when in roblox mode. The error thrown is the same as when inputting invalid arguments to require normally.


void Sandbox:AllowAssetRequires() - MUST be called to enable requires in a sandbox, they will not be enabled by default. Enables numerical (asset) requires when in roblox mode.


void Sandbox:AddModule(Variant target, Variant module) - Maps the target input to an imported copy of module in all modes except disabled. (E.g. sandbox:AddModule("util", util) will map require("util") to an imported copy of util)


void Sandbox:SetRequireMode(string|Variant mode) - Sets the require mode. Valid modes are roblox (Default), vanilla (Mimics vanilla lua requires), disabled


Status update

! REPORTING SECURITY ISSUES !

If you find a security vulnerability, please report it immediately over PMs! Security is a big concern of mine, so, finding these issues is very important. If you find a security issue it will get a test module crediting you for the find and if its a big enough concern I may discuss giving a decent Robux bounty.

May 5th Prototype Update

  • !!! Fixed a big bug where non instance Roblox types were failing (E.g. RBXScriptConnection). The fix is done by defining types the user can manipulate (userdata and table). That means any Roblox type (RBXScriptConnection, RBXScriptSignal, Vector2, etc) will now work as intended with function calls.
  • Activity tracking! This isn’t finished and is still in an early form so there’s some stuff that’s a bit weird around it. Currently you can use two formats: h6x and data (default). data returns a list of history entries, the current valid types are get, set, getGlobal, setGlobal, and call. In the future getGlobal and setGlobal will be merged into get/set and at the moment there are duplicate get and set events for globals which show the environment tables.
  • require is now gauranteed to work for module scripts with Redirector Defaults on
  • Custom requires aka require redirects UNLESS the Redirector Defaults are disabled
    (Completely disables requires), custom (Uses a custom function, not really useful to change to this mode manually unless you’re switching modes at runtime), or you can pass a non string hook which will set the custom require hook and set the mode to custom.
    • See Documentation additions for details.
    • When using a custom require hook you can return multiple values, and, the results you return are NOT cached unless you do that yourself. The require hook can be anything that can be called and can throw errors if you want, it doesn’t have to be a function.
    • The disabled mode throws with “Require is disabled.” when require is called at all, no custom require hooks or module additions are used, this completely disables require.
  • The default termination message is no longer “Script terminated” instead, the value true is used which tries to do a silent termination by permanently yielding threads
  • Fixed an unhandled error when terminating inside of a metamethod (attempt to yield across metamethod/C-call boundary)
  • When using silent terminations and a script resumes itself, 100 attempts to yield it are done before falling back to using an error
  • The fall back for silent termination failures is to emit a cannot resume dead coroutine error
  • When a tracked function call is done, termination checks are now done before AND after the call, previously the check was just done before which allowed the script to still potentially utilize call results (e.g. if a yield happened)
  • wait is now overwritten by Redirector Defaults with custom behaviour (To avoid weird errors with terminations). This custom behaviour uses spawn which internally has a very similar behaviour to wait and passes the same arguments that wait returns to its callback. Only if the script isn’t terninated when the delay is up is the thread resumed and the callback arguments returned.
  • Furthermore, delay itself has also been overwritten and so has coroutine.resume with the same check, the callback isn’t called if the sandbox is terminated and the thread won’t be resumed if the sandbox is terminated.
  • Further improved Termination check, there is no longer an attempt to call nil value error outputted to the console

Test activity report:

getGlobal print = <function print>
get <table table: 0x96a96acdad145313>.print = <function print>
call <function print>("[H6x]", "Runner initialized.", "\
", "	Environment correctly applied:", "YES.", "\
", "	Environment:", <table table: 0x96a96acdad145313>, "\
", "	Sandboxed:", "YES.", "\
", "	Sandbox:", <table table: 0x94e2724f1eaaacd3>) 
	return 
getGlobal game = <Instance game "H6x Script Sandbox">
get <table table: 0x96a96acdad145313>.game = <Instance game "H6x Script Sandbox">
call <function GetService>(<Instance game "H6x Script Sandbox">, "RunService") 
	return <Instance Run Service "Run Service">
call <function import>(<Instance game "H6x Script Sandbox">, "RunService") 
	return <Instance Run Service "Run Service">
get <Instance Run Service "Run Service">.Heartbeat = <RBXScriptSignal Heartbeat>
call <function Connect>(<RBXScriptSignal Heartbeat>, <function function: 0x898b0a1017f52553>) 
	return <RBXScriptConnection Connection>
call <function import>(<RBXScriptSignal Heartbeat>, <function function: 0x898b0a1017f52553>) 
	return <RBXScriptConnection Connection>
set <table table: 0x96a96acdad145313>.connection = <RBXScriptConnection connection>
setGlobal connection = <RBXScriptConnection connection>

Plans

  • Add prebuilt sandboxes. For example, H6x.Sandbox.Empty.new() might create a sandbox blacklisting everything for you to adjust yourself (a “whitelist” mode in a sense). H6x.Sandbox.User.new() might create a sandbox tuned to running Roblox user code that doesn’t have access to the DataModel or any instances. H6x.Sandbox.Roblox.new() might create a sandbox that has access to the regular Roblox environment. H6x.Sandbox.Vanilla.new() might create sandbox with vanilla lua compatability features. H6x.Sandbox.Plugin.new() might create a sandbox with plugin compatability features (e.g. for use on the client).
  • Improve h6x format for activity reporting (make simplified names like in the original example) and fix some issues around it. Also, marking internal calls so they can be optionally hidden from the report would be good to do.
  • Add lua and maybe luac (vanilla lua bytecode) formats
  • Add feature for blacklisting instances linked to the DataModel. This would allow for code to create instances and use them or access instances you give to it if they’re in nil but not if they could access game or anything under it.
  • Add feature for
  • Add luau typing for H6x classes

Download

DOES NOT INCLUDE TESTS FOR THE NEW FEATURES YET. They’ll be added once I actually finish them (probably in a few hours). If I need to do bug fixes I’ll post a new status update with them included. I wanted to get this update out despite being a bit short on time tonight.
H6x Prototype 5-5-21 (Missing update tests).rbxm (26.6 KB)

1 Like

I’m probably going to have to include tests later today, I didn’t end up getting to write them so far, so, if any stuff is not working when you’re using it let me know.

There shouldn’t be any security issues with the require features themselves since they’re basically relying on already existing features in the Sandbox class so I’m not too concerned personally.

On the other hand, the activity tracking can cause errors that break code if there is some weird specific edge case I missed in one of the tracked events and I can’t easily write tests for that to check all edge cases so its a bit iffy. There’s also no way to disable it yet despite that being a plan.

Also, if anyone has specific things they think should be tracked besides calls and indexing I’d love to hear it because off the top of my head I can’t think of much that would go under tracking. It could be specific, at some point I’m going to have a feature which simplifies patterns of events, so, you’ll get things like method calls.

I’m going to probably add an obfuscation mode too which seeks to getfenv calls and tries to back track to wherever the start of the code is most likely to be. Some obfuscators do a rediculous amount of calls and that completely floods the history. Most of the garbage that a lot of obfuscators intentionally add gets completely filtered since it goes untracked, because, its not actually used, which, is super useful.

So far I’ve been able to use the activity tracking to pretty well understand some random obfuscated bits of code, which, is great, that’s one of the main use cases.

I’d love to see what the caveats and benefits of of tracking some obfuscators are because it would give a pretty good idea of how to improve the activity tracking for better understanding obfuscated code.

1 Like