0x1D
Roblox Studio Remote Code Execution (RCE) Vulnerability | By Latte Softworks
General TLDR
This vulnerability allows the ability for any Roblox Studio plugin (Locally or on the marketplace) to gain direct machine access and achieve "RCE", where one can get a reverse shell or execute a Remote Access Trojan (RAT) program on your machine. latte.to/invite <3
Introduction
Documents & code snippets are licensed under the MIT License, see LICENSE.txt for more information.
Written by Reggie for Latte Softworks
Hey, folks!
In late August this year, I discovered something fairly peculiar about the Roblox Binary Model Format (rbxm
/rbxl
) and a certain jump in TypeId
s. (Each TypeId denotes a type of property in the binary format; e.g. String
, UDim
, Vector3
, etc.. We'll learn more about this later.)
We are leaking this RCE due to the fact that Roblox has put out a change in sitetest2.robloxlabs
/zintegration
that could prevent direct bytecode execution through this method in the future. We'll talk about this part later.. ;)
I'll continue to refer to this binary format as the "rbxm
" format, but I'ts the exact same as rbxl
.
For me to be able to properly explain all of this, you will need to take in and understand a few key aspects of the format relevant to this "vulnarability":
-
There are a couple of fairly well-made documents containing decent previous community findings and documentation on the rbxm format, the best probably being in the docs for rbx-dom. (Yes, the same team that made Rojo) There are some minor issues with this doc such as the header signature being "
89 FF 0A 1A 0A
" (Hex), when it's actually89 FF 0D 0A 1A 0A
. -
Each rbxm file contains first a file header, and then a proceeding list of "chunks". We're personally interested in the
PROP
and (more recently, undocumented)SIGN
chunk. -
Chunks themselves can be compressed using either the LZ4 compression algorithm, or, (just this week, also undocumented) the "zstd" compression algorithm.
You can see more about how chunks with compression work here. -
All
PROP
chunks contain the Roblox property info itself, as-well as an enumerated 1-byte integer value we call the "TypeId
". This TypeId references a very specific type of property in the format specifically so the serailizer can encode/decode each property. For example,BaseScript.Source
is usually represented under the type id 1/0x01 (String
). The first 4 bytes contain the specific length of bytes to read, the rest is the string itself. You can look at more examples here. -
The
SIGN
chunk that was just recently added to Studio contains a "key"/hash of the file itself, used internally for loading "optimized"CoreScriptPackage
s orBuiltInPlugins
. As of now, we don't really have a clear workaround for this chunk. (It's REQUIRED for loading bytecode directly under TypeId 0x1D, which we'll talk about in just a moment)
Theres FAR MORE to the format to this in general, but this is really all you need to know for this situation. This vast information is also why we'll be working on our own official documentation of the modern rbxm format in the future, so stay tuned into our Discord Server for more!
0x1D
"
TypeId "We managed to discover this unique TypeId due to some official model files Roblox put out for their "BuiltInPlugins
". It's read almost equivalent to the String
property-type, except for the fact that it's binary data and not UTF-8. In the disassembled source, it is officially denoted as "Bytecode
", so we're calling it BytecodeString
in our pseudo reference!
In our very extensive testing, (so much we almost decided to jump off of a bridge) we figured out that bytecode wouldn't directly load in the Player, or RCC (Server), and would return a bad/corrupted file error or similar. This is ultimately due to the SIGN
chunk Roblox uses internally in the rbxm format.
I'm only here talking about this in the first place because Roblox Studio doesn't actually totally respect this chunk for the type; right now, that is.. You can directly load Luau VM bytecode in studio under a developer plugin or model, no-matter if it's a local plugin or on the marketplace. :)
With that knowledge, you can directly load and run any Luau VM bytecode instantly under anyone's environment! In-case you didn't know, that's almost like giving full machine-instruction access lol.
Luau Bytecode & RCE From the VM
I'm not the best when it comes to exploiting the Lua/Luau VM at runtime (modifying instructions directly), but you can use any number of these resources to achieve direct RCE in the Luau Virtual Machine:
- Exploiting Lua 5.1 on x86_64 (Still works the same way in Luau)
- Reply From Defcon42 on a Previous Exploit ACE Vulnarability (Still can reference here)
- Lua Bytecode Explorer (Similar to godbolt, but for Lua. Again, you can use these same concepts with the Luau VM)
Proof of Concept
I've provided a basic proof-of-concept for this in the src/build directory, which when placed in your Studio Plugins
folder and a game is loaded, will run directly under the Luau Bytecode Format V3. (Currently in Studio at the time of writing)
Since I'm LAZY (lol) I used Rojo to generate an RBXM with a script containing Luau bytecode, then editing the file in a hex editor afterward and changing the TypeId
for Script.Source
from 0x01 to 0x1D. Again, It's just meant to be a very basic proof of concept showing the direct loading of Luau bytecode from an rbxm.
Conclusion
After gatekeeping this for a little over 2 months successfully without anyone else directly figuring out about this issue, we've decided to release a basic form of the method after Roblox has decided to add a change to how they handle this.
We will be releasing our own RBXM format implementation w/ DOM, which will also include further documentation on the format in the near future. Make sure to join our Discord Server to stay tuned!