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 (new) 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).

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 module, it’s in a model with a test part you can use to test the function.

If you just want the source, here’s the GitHub repository.

16 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!

2 Likes

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…

2 Likes

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.

1 Like

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.

2 Likes

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)

2 Likes

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.

2 Likes

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!

2 Likes

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)

2 Likes

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 ;-;

2 Likes

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:

2 Likes

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!

1 Like

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

2 Likes

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)

2 Likes

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

1 Like