The Debug Library
Lua provides a powerfuldebug library that analysts use to inspect running code. Functions like debug.sethook, debug.getinfo, debug.getlocal, and debug.getupvalue allow an attacker to trace execution, read local variables, and inspect closures. Your first line of defense is detecting whether this library is present and whether it is being actively used against you.
Checking for the Debug Library
Start by verifying thatdebug exists and has not been replaced with a stub:
debug is missing entirely, that may indicate a sandboxed environment rather than an active debugger. Treat this as a separate signal (see Sandbox Detection).
Detecting Active Hooks
If the debug library is present, check whether a hook has been installed. Thedebug.gethook() function returns the current hook function, mask, and count. If any of these are non-nil, a debugger is likely tracing your execution:
is_hook_active() returns true, your script is running under a debugger or tracer that has set a line, call, or return hook.
Hook Detection and Countermeasures
Analysts often hook standard library functions likeprint, tostring, and pcall to intercept arguments and return values. You can detect these hooks by comparing the string representation of functions against expected patterns.
Function Fingerprinting
In standard Lua, native functions print asfunction: 0xADDRESS. In LuaJIT, they print as builtin#N. If a function has been wrapped or replaced with a Lua closure, its string representation changes:
check_common_hooks() returns true, one of these functions has been replaced or wrapped.
Closure Hooking
In Lua,type(print) always returns "function" even if the function has been replaced with a closure. The tostring trick above is more reliable. For additional confidence, compare the function’s bytecode identity using string.dump (Lua 5.1+):
Timing-Based Detection
A debugger stepping through code inflates execution time. By measuring a known-fast operation, you can detect whether a human or automated debugger is slowing execution.Using os.clock()
Useos.clock() for high-resolution timing in Lua. Measure a tight loop and compare against a threshold:
collectgarbage("stop") before measuring and restart it afterward. Choose a threshold based on your target platform. On a modern CPU, one million integer additions should take well under 0.1 seconds. If the elapsed time exceeds 0.5 seconds, a debugger is likely present.
Luau and Roblox Timing
In Luau and Roblox,os.clock() is available and preferred for high-resolution timing. tick() is also available but deprecated. Use os.clock() for consistency:
Environment Integrity Checks
Verify that critical functions have not been replaced since script startup. Capture their original string representations at load time and compare them periodically or before sensitive operations.Original Reference Capture
At the top of your script, store the original representations:Metatable Tampering
In stock Lua,getmetatable("") returns nil because strings do not have a user-accessible metatable by default. If an analyst has attached a metatable to strings to intercept operations, this check will fail:
Luau and Roblox Specific Checks
Luau and Roblox expose additional APIs that help you detect development and analysis environments.Detecting Roblox Studio
Roblox Studio is the primary environment where analysts decompile and debug scripts. Detect it withRunService:
IsStudio() as a debug signal.
Verifying Execution Context
Check whether the script is running on the server or client as expected:LocalScript is unexpectedly running on a server, or a Script on a client, the environment may be manipulated.
Script Parent Verification
Verify that your script resides in the expected service:Player-Based Checks
ForLocalScript instances, verify that the script is associated with the expected player:
LocalScript into an unexpected context. Parent and player checks help catch this.
Responding to Detection
Once you detect a debugger or tampered environment, you have several response options. Choose based on your threat model and tolerance for false positives.Response Strategies
Fake Mode Pattern
Fake mode is one of the most effective responses. Replace real function logic with decoy implementations that produce plausible but incorrect results:math.random to extract a key, they will receive all zeros and waste time analyzing meaningless output.
