Function getrawmetatable() written in pure Luau (copyrawmetatable())

Please view the disclaimer before use!

So… I got a little bored and thought, what might be a generically useful function? So I decided to remake getrawmetatable in pure Luau.

For those of you who don’t know, getrawmetatable is a function commonly used in exploits which retrieves a table’s metatable regardless of whether or not the __metatable field is set. The __metatable field is supposed to protect the metatable, preventing it from being modified and also controlling what getmetatable returns when called on the table.

How does it work?

This works by leveraging how Luau manages the stack.

What's the stack?

The stack is an area of memory where temporary values are stored, such as functions, their parameters, and local variables, as well as return results. In Luau, this is shared between Luau scripts and the C++ code, allowing effective data sharing between the two. It follows the LIFO (Last In, First Out) principle, where the last value to enter the stack is the first value to exit it.

When a metamethod is invoked, Luau pushes it onto the stack. It executes, and when completed, it’s popped off the top. This script uses a userdata object, with metamethods, to “intercept” the stack and retrieve the metamethod while it’s executing. Additionally, it leverages xpcall to inspect the stack if the metamethod errors, before Luau pops it off the top.

Inherent limitations

Roblox’s Luau is heavily sandboxed and doesn’t allow that many debugging tools - the main one used in this module is debug.info, which allows me to retrieve functions and their information from the stack. Therefore, the functionality of this module is limited:

  • Metamethods that do not interact with the proxy object, or do not error, are undetectable by this function. This includes things like __tostring.
  • This limitation means some metamethods can be “hidden” from copyrawmetatable - using specific code means they won’t show up.

What can I use it on?

Due to the nature of userdata objects, here’s what you can use it on and what it will return:

  • Instances: returns the __namecall, __index, __type, __metatable and __newindex metamethods currently assigned
  • Tables: returns all detectable metamethods (see above)

Demonstration

local copyrawmetatable = require("./getrawmetatable/getrawmetatable")

local testTable = setmetatable({
	someValue = 3,
	someString = "hi"
}, {
	__add = function(self, other)
		return self + other
	end,
	
	__concat = function(self, other)
		return self.someString..other
	end,
	
	__index = function(self, other)
		print(other)
	end,
	
	__newindex = function(self, other)
		print("This doesn't reference the proxy (would be in variable other), so won't show up")
	end,
	
	__metatable = "This is protected."
})

local tableMetatable = copyrawmetatable(testTable)
local instanceMetatable = copyrawmetatable(workspace.Baseplate)

local __concat = tableMetatable.__concat
local instance__newindex = instanceMetatable.__newindex

print(__concat({someString = "string 1 "}, "string 2"))
instance__newindex(workspace.Baseplate, "Name", "SomeOtherName")

and here’s the results:
image
image

And the metatables themselves:
Table metatable:
image

Instance metatable:
image

Disclaimer

Use this at your own risk! I will not accept any responsibility for consequences which arise as a result of using this module. Should this module break due to later Roblox updates, I will try and fix it, but there’s always the chance it may just stop working. Please note that this doesn’t retrieve all metamethods at all times because of Luau’s limitations.

Tables returned by copyrawmetatable are readonly, due to Roblox limitations… and also the fact it’s against terms of service (for instances, i think…)

They are editable, but changes will not apply to the actual metatable.

Feel free to edit this however you want - just add a comment in the script somewhere to say you edited it :slight_smile:

I’m open to suggestions on how to improve this, and what functionality could be added. I’d appreciate ideas!

Here’s the GitHub repository:

31 Likes

WHAT WHAT WHAT, I’ve really been trying to realize this for a long time, it’s just crazy, I didn’t think it was possible. All that remains is to add setrawmetatable and OOP in luau will be achieved although in fact there is no OOP here, it would be cool to implement the “getPartRegistry” method directly from the instance.

3 Likes

Although it’s limited, it’s better than nothing! I would love to be able to implement setrawmetatable but I don’t think Luau’s sandboxing allows for it.

If you give it a go, I’d love to hear how you get on!

1 Like

This has already been done by exploiters in the past. It is not a real getrawmetatable because the metatable that is returned is not the one that the object truly uses; you’re recreating the metatable at best, not getting the metatable of the object, and this is 1000% not against ToS, it’s just Luau code. you’re not doing an executor or obtaining executor power with this…

1 Like

No, completely, it is literally just reconstructing the metatable, hence why applied changes won’t apply to the objects themselves. When I said it’s against ToS, I means that modifying core Instance metamethods such as __namecall and overwriting them with your own would likely come under “misusing roblox systems” (I think, at least). Well, all the function references in the tables point to the original metamethods, making it useful for checking… or whatever. There’s only so much you can do in a sandboxed environment such as Luau, and I thought I’d push that to as much as I could. I’m honestly surprised we’re allowed to access as much of the stack as we are.

I kind of wanted to get a generic way to retrieve both table metamethods and instance metamethods, mostly for anticheat and debugging purposes.

What do you exactly mean by “No, completely”. Luau externals have been faking the function for ages, it’s the same concept.

Modifying metamethods of an instance isn’t ToS breaking; it’s just a luau metatable; reversing it themselves could count as it (as in, reversing the backing C/C++ implementation for the bridge), but that’s something no one would honestly do unless they’re doing an executor.

The method is useful for anticheats, yes, but everyone just does xpcall with select to get metamethods; it has always been that way. Abstracting it into a full function is just kind of unnecessary, in my opinion, more so when it is acting as a function that it is not.

1 Like

Even if modifying an instance metatable isn’t ToS breaking, I’m pretty sure Luau’s sandboxing makes it pretty much impossible (I’d actually love to see it done though).

Obviously, this isn’t a 1 to 1 recreation of getrawmetatable, and since it’s within this environment, it doesn’t have nearly as much power as it’s C-based equivalent which is just registered to executor environments - mainly because I don’t have the ability to retrieve the metamethod while it’s on the stack if there’s nothing it can reference which can do so, as to say. The main advantage of executors is being able to execute their own C/C++ code, and it just so happens that lua_getmetatable disregards a __metatable field. (ik u know that already though).

I can’t do it before or after, because it’s not on the stack at that point, and the only way to do it during the execution is either through forcing an error or having another metamethod invoke on top of it to capture it (what I did with the proxy).

This also isn’t just for instances, but also for tables - I felt it necessary to abstract into a full function because the metamethod can appear at different points on the stack depending on the function call traceback.

I called it getrawmetatable because it does the same kind of thing - retrieves the real metatable of x, bypassing it’s __metatable field. It just can’t retrieve as many metamethods because of Luau’s restrictions.

If you know how I can improve this, please do tell me. (sorry if that sounded sarcastic lol, i meant it as a genuine statement)

1 Like

nah, it’s just naming it getrawmetatable kind of triggers me, like genuinely. I have seen so many external cheat implementations that do the same, which aren’t 100% accurate with the actual function behaviour, which is sort of annoying. You’re right however, the function implementation is (roughly) this one:
image
(Macros I use removed for simplicity)

Depressingly, setrawmetatable will likely never be a thing in raw luau, because the only way to manipulate metatables in Luau with the base library requires that the metatable’s __metatable metafield is not set, which we all know.

setmetatable lbaselib.cpp loc 96 - loc 106
image

(It also doesn’t accept userdata/C proxies as first argument in tables, so we couldn’t manipulate game even if we wanted and a method was discovered)

I’d honestly name this function copymetatable or maybe rebuildmetatable, it’s probably more accurate and doesn’t imply you get the reference to the metatable.

1 Like

good idea, ill probably change it at some point. Not surprised it triggered u, i was actually just looking at the getrawmetatable code from RbxStu lol

I’ll probably name it copyrawmetatable, i want to emphasise that it bypasses __metatable.

edit: changed it to copyrawmetatable!

1 Like

It’s actually fun that my executor code is being used; I mostly see some exploiters grabbing parts of it, some even got entire libraries from it, and others began their own projects from it; it’s kind of fun.

I did V1, V2 and V3. I’m on V4 right now; I don’t know what to do with it. I just did it because I didn’t like what V3 ended up being, and I really didn’t want the project to end up as a ‘wow, it broke entirely and it died.’ This time I took a different approach to design, wrote an entire reflection abstraction, etc. But I really don’t know what to do with it xd.

Still, yes, the name copyrawmetatable is probably most accurate name. I have been looking towards implementing replicatesignal, it’s complete; it’s just that argument parsing is annoying ;-;

I deviated from the topic; sorry, it’s useful for anti-cheats, and I’ll probably steal it for some stuff when I test things on this new version :slight_smile: (sethiddenstack on hooks, for example)

1 Like

Honestly, it’s RbxStu that drove me to try this, cmake doesnt want to compile v3 and i really wanted to access some “raw” metatables ;-;

1 Like

V3 was practically fully patched depressingly; most of the methods are broken. For V4, I kind of made a monster with V3 and V2 to get it to work stably. If you want V4, just hit me up on Discord (@usrdottik), and I’ll give you the new Discord server for it no problem. I just haven’t released it because I have too many things to do and many plans :slight_smile:

1 Like

Update!

Added support to get __type from instances. I’m trying to get __tostring and __metatable too, but not sure if its possible. I’ll try!

Edit: Trying to get around __type metamethod hooks on instances

This is interesting. Sorry to make you continue renaming this, but what about calling it clonerawmetatable? It’s much more streamlined with other lua methods

Additionally, why not host this on a gist or github repo? It’s pretty annoying to have to get the roblox model for the module source

1 Like

probably a better idea to host it on github, yeah good idea ill go do that now

tbh it doesnt really matter what its called you can always rename it in ur code
copyrawmetatable is fine i think because it gets the gist across it’s not the actual metatable but it does bypass __metatable (to the extent that it can)

1 Like

alright, it’s up as a GitHub repo now.

2 Likes

Update!

  • Made it so that when checking to see if the target is an Instance or not, the __type metamethod is bypassed. This means an Instance can be determined as an Instance even if it’s __type metamethod is set to table or something else.
  • More secure keys for forcing game metamethods to error. ____ was a bit too nice to exploiters… lol
  • Returned metatables are now readonly by default
1 Like

Update!

This function is mainly for anticheat purposes now. I added detection for sethiddenstack, a function which prevents a metamethod from appearing on the stack. If a metamethod is hidden, the error message will look something like this:

and if we check at the stack level the metamethod is expected, we can find it. copyrawmetatable now takes an additional parameter, a callback function to be executed if a suspicious error message is detected.

If anyone finds any issues, please report them here!

edit: refined the string pattern + made the extra function optional. Metamethods detected as hidden are now properly hidden from the returned metatable.

1 Like

Another update

  • Added support for __metatable on instances, note this is not pushed to the GitHub repo yet.

Currently trying to add support for __tostring on instances, although it is very difficult because roblox handles it differently to other instance metamethods. Since once a metamethod finishes executing, it is pushed off of the stack, I can’t exactly just use tostring(instance). For the tables, this was fine, because as long as the metamethod itself interacted with the userdata I could retrieve it while it was still on the stack through the userdata’s metamethods. This is doable if it interacts with the userdata in such a way which causes it to cast to a string, such as __concat.

Since Roblox instances are just userdatas with metatables which act as wrappers to call the corresponding C++ API code, there’s not really a need for metamethods like __add, __concat, et cetera because that “part” of the instance will remain constant no matter what. Therefore, there is no underlying code to handle such a metamethod; only one underlying C++ function, as such, which will always throw an error: attempt to perform [operation] on Instance and [other thing]. In fact, I even tried with a modified instance which had a __concat metamethod, and it still threw the same error.

Therefore, if I can’t get around this error or otherwise push the __tostring metamethod to the stack, I simply cannot retrieve it. It might just be a Luau limitation, which is really annoying especially considering this is the only one I need to retrieve to have all the instance metamethods captured into a table.

pushed __metatable to the github repo