Skip to content

Instantly share code, notes, and snippets.

@TehBlaxxor
Created March 16, 2020 08:55
Show Gist options
  • Save TehBlaxxor/c8022570fea9e48ab7a6a6ad23c7c671 to your computer and use it in GitHub Desktop.
Save TehBlaxxor/c8022570fea9e48ab7a6a6ad23c7c671 to your computer and use it in GitHub Desktop.
A guide on debug functions for Roblox exploiting

Exploiting Debug Library Guide

Written by Greenman#0001

Last Updated Thursday, 05-Sep-2019 11:59:24 GMT-0400

Information

Before you continue, please understand these things:

  • This guide is written based on Synapse X's debug functions
  • Luau has changed how some of these functions work
  • Some of these functions work differently or produce different results that differ from vanilla Lua
  • If a new debug function is added to an exploit, it will not be here if it's not documented or I can't figure out what it does

Documentation is formatted like this:

return_type debug.function_name(<type> arg1 [,<type> arg2])

Arguments enclosed in square brackets are optional.

Lua Terminology

It's important to understand some terminology so you don't get confused when reading this guide.

Local(s)

Local means a local variable and the plural version is locals.

Example:

local x = 10 -- This is a local
y = 20 -- This is not a local

Upvalue

An upvalue is an external local variable. This means that you are accessing a local variable that is above the scope of the current block (in the case of the debug lib, a function).

Example:

local x = 10
y = 20

function test()
  local z = 30
  print(x) -- x is an upvalue
  print(y) -- y is not an upvalue because it's a global
  print(z) -- z is not an upvalue because it's an internal local variable
end

If you reassign or reference a local variable that was defined outside the function inside the function, it's an upvalue.

Stack Level

The stack level is a number that returns a specific function or thread.

  • 0 - The debug function that was called
  • 1 - The thread that called this function
  • 2 - The thread calling the function you are in

Example (Levels 0-2):

local a = 10
-- stack level 2


function x()
	local b = 20
	-- stack level 1
	debug.getinfo(0) -- stack level 0 = this function
end
x()

-- stack level 2

Note: There can technically be infinite stack levels but the idea with stack levels is that you are "moving up" threads/functions until you get to the last thread.

Object

An object refers to a table, userdata, thread, or function.

Int

Int is short for integer which means a whole number.

Example:

1 Int
1.4 Not an int

Table of Contents

This guide will overview the following debug functions:

debug.getconstant

Documentation:

any debug.getconstant(<function, int> f, <int> idx)

Returns the constant at the specified index in function or stack level f.

Example:

function test()
  print(10)
end

print(debug.getconstant(test,2)) -- 10

Keep in mind that function names count as constants and are returned as strings.

Example 2:

function test()
  print(10)
end

local x = debug.getconstant(test,1)

print(x) -- print
print("Datatype of constant: "..type(x)) -- Datatype of constant: string

debug.getconstants

Documentation:

table debug.getconstants(<function, int> f)

Returns a table containing the constants of function or stack level f.

Example:

function test()
  print(10)
end

for k,v in pairs(debug.getconstants(test)) do
  print(k,v)
end
-- Output
-- 1 print
-- 2 10

This function can be useful when you need to get the index for a constant.

debug.getfenv

Alias: getfenv

Documentation:

table getfenv(<function, int> f)

Returns the global environment table of function or stack level f. Same as getfenv except it ignores all the safety checks.

Example:

function timer()
	print("5 Second Timer Started!")
	wait(5)
	print("Time's Up!")
end

debug.getfenv(timer).wait = function() end

timer()

This script will have no delay by adding a new function called wait to the global environment of timer that does nothing.

debug.getinfo

Documentation:

table debug.getinfo(<function, int> f [,<string> what])

Returns a table with information about the function or stack level f. The what argument can be used to select specific pieces of information that will go in the returned table. For information on how to use it, read this page.

Example:

function test()
	print("hi")
end

for k,v in pairs(debug.getinfo(test)) do
  print(k,v)
end

-- Output
-- func function: 0xADDRESS
-- nups 0
-- source @NAME
-- what Lua
-- currentline -1
-- lastlinedefined NUMBER
-- linedefined NUMBER
-- short_src NAME

To understand what this information means, please read this (ignore the code and look for the part where it says "The fields of lua_Debug have the following meaning").

debug.getlocal

This will return an error due to a replacement function being made for Luau. debug.getstack should be used instead.

debug.getlocals

This will return an error due to a replacement function being made for Luau. debug.getstack should be used instead.

debug.getmetatable

Alias: getrawmetatable

Documentation:

object debug.getmetatable(object o)

Returns the metatable of object o or nil if o has no metatable. This function is not the same as getmetatable because it will always return the table even if there is __metatable in the metatable.

Example:

local myTable = {1,2,3}
setmetatable(myTable,{
	__index = function(t,k)
		return "Could not access "..k
	end,
	__metatable = "No access" -- does not effect debug.getmetatable
})

print(myTable[4]) -- Could not access 4

local backup = {"backup1","backup2","backup3","backup4","backup5"}
debug.getmetatable(myTable).__index = backup
print(myTable[4]) --backup4

debug.getproto

This function was recently added to Synapse X and doesn't seem to be working properly at the moment.

debug.getprotos

This function was recently added to Synapse X and doesn't seem to be working properly at the moment.

debug.getregistry

Alias: getreg

Documentation:

table debug.getregistry()

Returns the Lua registry table. This table contains all functions and threads created from any client-side scripts. Keep in mind that getgc() is actually better for most purposes.

Example:

for k,v in pairs(debug.getregistry()) do
  if type(v) == "function" then
    --you can do stuff like getting upvalues, constants, environment, etc.
  end
end

debug.getstack

Documentation:

table debug.getstack(<int> lvl)

Returns a table containing the local variables at the stack level lvl. Unfortunately, this function does not allow you to get the arguments of a function (probably because of Luau?).

Example:

for k,v in pairs(debug.getstack(1)) do -- level 1 is current thread
  print(k,v)
end

This script will display all of the local variables inside the current thread.

debug.getupvalue

Documentation:

any debug.getupvalue(<function, int> f, <int> idx)

Returns the upvalue inside the function or stack levelf at index idx. If an upvalue at idx is not found, nil is returned.

Example:

local health = 100

function takeDamage()
  health = health - 10
end

print(debug.getupvalue(takeDamage,1)) -- 100

debug.getupvalues

Documentation:

table debug.getupvalues(<function, int> f)

Returns a table containing the upvalues of function or stack level f. If the function is from a script inside the game, you will get number indices instead of names because of Luau.

Example:

local health = 10
local ammo = 100
local state = "standing"

function showStats()
  print("Health: "..health.."\nAmmo: "..ammo.."\nState: "..state)
end

for k,v in pairs(debug.getupvalues(showStats)) do
	print(k,v)
end

debug.setconstant

Documentation:

nil debug.setconstant(<function, int> f, <int> idx, <object> value)

Sets the constant at index idx to value inside function or stack level f. It's recommended to use debug.getconstants to see all of the constants available in a function before using this function.

Example:

function test()
	local x = 10
	print(x)
end

function hijack(...)
	print("HIJACKED: ",...)
end

debug.setconstant(test,2,"hijack")
test() -- HIJACKED: 10

debug.setlocal

This will return an error due to a replacement function being made for Luau. debug.setstack should be used instead.

debug.setmetatable

Alias: setrawmetatable

Documentation:

bool debug.setmetatable(<object> o, <table> mt)

Sets o's metatable to mt even if the __metatable field exists in o's metatable. Keep in mind that this function will return a boolean in Synapse X even though the function is supposed to return o according to the reference manual.

Example:

local t = {1,2,3}

local mt = {__metatable = "The metatable is locked!"}

setmetatable(t,mt)

--[[ This block will return an error
print(setmetatable(t,{
  __index = {1,2,3,4,5,6}
}))
]]

print(debug.setmetatable(t,{
  __index = {1,2,3,4,5,6}
})) -- true

debug.setproto

This function was recently added to Synapse X and doesn't seem to be working properly at the moment.

debug.setstack

Documentation:

nil debug.setstack(<int> lvl, <int> idx, <any> value)

Sets the local at index idx to value at stack level lvl. Unfortunately, you cannot set the arguments of a function with this function (probably because of Luau?).

Example:

local x = 10
debug.setstack(1,1,100)
print(x) -- 100

debug.setupvalue

Documentation:

nil debug.setupvalue(<function, int> f, <int> idx, <any> value)

Sets the upvalue at index idx to value in function or stack level f.

Example:

local health = 100

function takeDamage()
	health = health - 10
end

print(health) -- 100
debug.setupvalue(takeDamage,1,math.huge)
print(health) -- inf

debug.setupvaluename

Documentation:

nil debug.setupvaluename(<function, int> f, <int> idx, <string> name)

Sets the name of the upvalue at idx to name in function or stack level f. This function is meant to be used when you are working with functions from a script inside the game that uses number indices instead of names.

Example:

local health = 100

function takeDamage()
	health = health - 10
end

debug.setupvaluename(takeDamage,1,"hp")

print(debug.getupvalue(takeDamage,"hp")) -- 100

debug.traceback

Documentation:

string debug.traceback([<string> message, <int> lvl])

Returns a full execution stack trace of stack level lvl and starting with message message before the logging. Both arguments are optional and the default value for lvl is 1 which is the current thread.

Example:

print(debug.traceback())
-- @SCRIPTNAME:1

Example 2:

print(debug.traceback("Traceback test"))
-- Traceback test!
-- @SCRIPTNAME:1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment