H3x - Script Sandbox (Deprecating soon)

The prototype of H6x has just been posted here literally a year after I said it would (April fools or something? :sweat_smile:)
It’s in an early state. I would recommend not using H3x for any new work, but, I would also not recommend using H6x until its a bit more ready.

This is being deprecated by January 2020 (Funny joke)

H6x will be the new version of H3x (Get it cuz H3x * 2 = H6x so its two times better?). I plan to initially release this sometime near the end of December in a fairly minimal state.

H3x is a script sandbox which aims to be minimally intrusive (as little modification of functionality as possible), easily editable, secure, and reliable. Note: It requires loadstring to be enabled. You can do so with the ServerScriptService property.

NOTE: This project is a work in progress and has not yet been thoroughly tested. If you use it in a game, keep in mind you are still running user code and that user code can still be malicious!

If you find a security issue, or something you think is a risk, please notify me via PMs ASAP so I can release a fix to the issue(s)!

Joining the update mention list (Revised 11/2/19)

If you would like to be mentioned by me when an update is published and get extra information such as planned features please PM me the search phrase H3x mention list in your message and be sure to include any specifics. For example, I can include any information you’d like to know about when I roll out an update and also include/exclude you for unimportant updates.

About

Features of the H3x Script Sandbox
  • Completely indistinguishable from a root Roblox script environment besides modifications you make to the environment (or if using the default environment all Roblox instances will be missing).
  • A default sandbox environment which automatically protects all global functions and values as well as removes all instances returned from functions or tables. getfenv on protected functions will return the sandbox’s root environment. setfenv will invoke the error 'setfenv' cannot change environment of given object.
  • Additionally getfenv(2) from inside of a protected function will invoke the error bad argument #1 (invalid level) exactly like a root environment.
  • No globals, Roblox functions, etc are rewritten or changed at all. Everything is left in tact with zero modifications including getfenv/setfenv, setmetatable, etc with the sole exception of require (due to security issues).
  • A clean API offering access to controlling scripts.
  • Script environment and require libraries APIs .
  • All code contained within only two modules, one of which is only used internally to create new default environments.
  • Nothing hacky is used. No uploading script models, no HttpService, no text parsing, only loadstring access is required.
  • Custom require via the Context:AddLibrary(libraryName, library), Context:RemoveLibrary(libraryName), and in scripts, library = require(libraryName) APIs.
  • Hooking APIs which allow you to log every external call the script makes as well as external table usage. (Accessible via Sandbox:MakeEnvironment(Context ctx)'s second argument)
Uses of script sandboxing
  • Running user-supplied code with limited or reduced access
  • Analyzing obfuscated code
  • Changing or extending the functionality of a script without changing its source code
  • Map making SDKs

API Docs

Releases

43 Likes

How does H3x’s sandboxing work without messing with fenvs?

also i would like to be added to the H3x mention list

I’d like to be added to the update mentions please. I must say, the speed at which you push updates based on user feedback is awe inspiring.

It’s been a day or two and I’m already seeing progress and polish.

* Tips hat *

3 Likes

I do mess with fenvs quite a bit however I do this without modifying any particular function and I do this in a way that makes it nearly impossible to tell from inside the sandbox even to Roblox’s own functions. I use coroutine.wrap very frequently due to the properties of its return function. This function is actually a c function. And global functions are also c functions. In fact, they behave exactly the same outside of the coroutine. But inside of the coroutine lua code can still be run and this coroutine is completely isolated from the c function which invokes it.

For example, I use coroutine.wrap to pretty much just convert my lua wrapper functions into C functions from lua’s pov.

My wrapped functions also contain an overcomplicated way of returning arguments and protecting the values they return. A function can return 0 arguments. But it can also return 1 argument that is nil. This is detectable using select thus I use select to create a “perfect” tuple instead of simply doing {…}.

Finally sandbox scripts run in a real script. The context module is cloned and it contains several functions to talk with the sandbox script (which is literally even running in the same environment and script so it’s basically talking with itself). Again, coroutine.wrap is used here for a few things. 1, isolate fenvs. The script cannot get a calling environment. There simply isn’t one. It’s in a coroutine. Thus the script appears to be in a root environment.

I make use of the fact that coroutines don’t have a calling environment but work like functions as well. For example you may see the Pointer function. This just creates a coroutine with a vararg. The vararg is then returned with the next call. So it pretty much holds a perfect vararg reference without any extra processing on my part.

2 Likes

Could you explain and perhaps give an example for this new Hook API? It seems extremely useful, but I’m not completely understanding its usage and I’m not at a PC to work it out myself.

My use case of Lua Learning needs to log everything the script does in order to determine if it passed the lesson.

I sometimes surprise myself too haha… I manage to get stuff done so quickly and I don’t know why or how. I guess in a way I am driven by feedback because when I’m not receiving feedback the project kind of slips out of my mind.

1 Like

The Hook API is pretty much just a table. You define functions on it which have some return values and some arguments passed to it.

OnSetValue/OnGetValue refer to sandbox-controlled tables. That includes the environment as well as any tables that are a descendant of the environment. When any table is pulled out of the sandbox it gets protected and returned to the sandbox script. So basically any table that the script pulls from the environment is tracked to maintain sandboxing for the entire environment table.

The protect function invokes the OnProtectValue hook. This hook allows you to control what values are returned. You get the real value and the value that the default sandbox gave as the protected result. This means you can bypass default protections by returning the original value with override as true and also allows you to write your own protection or extend the existing protection.

For functions, OnProtectFunction is invoked from within the same function as OnProtectValue. This controls the actual act of protecting a function. The protected functions’ return values are protected meaning OnProtectValue is invoked for all of them. This allows you to wrap the function to change functionality or log values. You can also do most of the same things you can with OnProtectValue buy it is specific to functions.

Additionally, indexes, names, etc are fully tracked and provided to your script for debug and filtering purposes.

That’s pretty much the entire hook API. It makes the default sandbox fully modifiable without actually editing it and provides it in a way that makes it easy to extend or get rid of sandbox functionality.

You can also create different types of logging by storing some data where you define the hook functions. For example, to track the full path of values in tables and the environment you can use the real table value and the wrapped values and give them an index identifier. The real/wrapped values can be indexes in a table and the indexes can be values for example. Then you’d need to define parent tables for values and you can fully track the full path as a list of indexes or a list of tables.

1 Like

I’ve been working on a project to load plugins into studio via a plugin and I will be using this to sandbox them as well. I made note of that on another post so I thought I’d make note of it here.

This is what I plan to include:

  • Full “virtualization” of a place
  • Full emulation of a plugin within this virtualization
  • Sandboxing, emulation of a live environment, and logging of scripts the plugin places in the game
    And probably more.
2 Likes

I would like to be added to the beta program

All jokes aside this is actually pretty cool, this would help me learn in a way, and right now, I definitely need to learn how to script.

I would like to be added to the H3x mention list .

2 Likes

Could you elaborate on this? What’s the purpose of it? Why not just use normal plugins?

Because these will be sandboxed plugins. It logs what the plugin does (what scripts it places, etc).
I wouldn’t be surprised if someone makes a masterplugin where you can load plugins in a sandbox with set permissions.

1 Like

The main use will be detecting backdoors. For example, let’s say the plugin imports a script. That script could do anything (like require a module) so showing some sort of log of what the script does would be great information. And because its a sandbox you can make the script think its in a live server (e.g. wrap RunService:IsStudio) so it will run any malicious code it might run in a live server.

4 Likes

Sandbox:Load() does not follow the API you have written. It doesn’t have Hook as a third return.

Hook is returned as a second argument by Sandbox:MakeEnvironment(), but it is not passed up to the :Load() function.


function Sandbox:Load(code, env, mergeMode)
	local ctx = Context:Create()
	local loadedFunc = ctx:Load(code)
	ctx:SetEnvironment(env or Sandbox:MakeEnvironment(ctx), mergeMode)
	return loadedFunc, ctx
end
function Sandbox:MakeEnvironment(ctx)
	--- A whole long function
	return realEnv, hook
end

I might have messed up the docs. It is returned by MakeEnvironment.

1 Like

The next time I get a chance I’ll be fixing up some of that stuff.

3 Likes

I would like to be added to the H3x mention list .

I would like to be added to the H3x mention list.
This looks amazing.
I’m so excited to see more. (:

If you have an account for a remote git hosting service, such as GitHub, would you mind adding it to source control? I think that would make it easier for us to see changes, for you to push changes and for people to contribute.

Yep I was planning on doing so. The current update also has a lot of bugs related to the Runner API which I haven’t gotten a chance to fix. This is my github: https://github.com/hexcede

1 Like

Pretty nice work, I am an amateur at scripting (and I think this will be beneficial), and I think that this’ll be a great opportunity to implement this into a game I’m making. I would like to be added to the H3x mention list .