-
Lua Frame Protection [3.3.5a]
Hey everyone,
Just wanted to share a little trick for hiding frames from servers that use lua_str_check or similar detection methods to find bots, hacks, or injected scripts.
The concept is simple:
I detour EnumerateFrames and spoof the results so that protected frames are completely skipped during Lua enumeration. You’ll need to register a custom Lua function and then pass your frame into it to mark it as protected.
I’ll be honest — the code isn’t the cleanest, but it works 100%. Fully tested and solid for avoiding most Lua-based detection scans.
side note : Warden can detect the detour, so make sure you've already bypassed Warden's PageCheck (A and B) and MemCheck before using this in any serious environment.
Code:
int ProtectFrame(lua_State* L)
{
if (lua_type(L, 1) == LUA_TTABLE)
{
lua_rawgeti(L, 1, 0);
if (lua_type(L, -1) == LUA_TLIGHTUSERDATA)
{
WoWframe* data = (WoWframe*)lua_touserdata(L, -1);
lua_settop(L, -2);
if (data != nullptr)
{
std::cout << std::hex << "ProtectFrame "<< data << std::endl;
ProtectedFrames.push_back(data);
lua_pushboolean(L, true);
return 1;
}
}
else
{
printf("[ProtectFrame] table[0] is not lightuserdata.\n");
}
}
return 0;
}
int __cdecl EnumerateFrames(lua_State* L)
{
if (lua_istable(L, 1))
{
lua_rawgeti(L, 1, 0);
WoWframe* Userdata = (WoWframe*)lua_touserdata(L, -1);
lua_settop(L, -2);
if (!Userdata || !Userdata->Next)
return OriginalEnumerateFrames(L);
WoWframe* current = (WoWframe*)Userdata->Next[0xA2];
while (current && ((uintptr_t)current & 1) == 0 &&
std::find(ProtectedFrames.begin(), ProtectedFrames.end(), current) != ProtectedFrames.end())
{
std::cout << "[EnumerateFrames] Skipping protected frame: 0x"
<< std::hex << (uintptr_t)current << std::endl;
DWORD nextAddr = current->Next[0xA2] ? current->Next[0xA2] : 0;
current = (nextAddr && !(nextAddr & 1)) ? (WoWframe*)nextAddr : nullptr;
}
if (current && ((uintptr_t)current & 1) == 0)
{
DWORD* Ptr = reinterpret_cast<DWORD*>(current);
if (!Ptr[1])
RegisterFrameWithLua(Ptr, 0);
lua_rawgeti(L, -10000, Ptr[2]);
}
else
{
std::cout << "There is no vaild frame" << std::endl;
lua_pushnil(L);
}
return 1;
}
return OriginalEnumerateFrames(L);
}
Screenshot 2025-04-24 044412.png
Last edited by Makkah; 04-24-2025 at 06:49 AM.
-
Post Thanks / Like - 3 Thanks
-
Banned
-
Member
-
Member
Interesting, thanks for sharing! Have you seen some code that checks the Lua Frames where this would help out in the wild?
Also can you elaborate on the Pagecheck and Memcheck so I can read up on that?
I stayed away from hooking for now and used other workarounds for problems, but hooking the tokenToGuid functions for example would probably make things a lot easier to work with when using lua.
Also.. is that an external imgui lua editor? Im using an addon for my quick ingame tests, but having that outside seems like a good idea.
-
Some private servers implement Lua-based detection for automation tools like Ni. One method I've seen involves scanning UI frames and identifying bots based on specific frame sizes.
Here’s an example Lua script used for that purpose:
Code:
local S, T, R = SendAddonMessage, function()
local e = EnumerateFrames
local f = e()
while f do
local w = f:GetWidth()
local h = f:GetHeight()
if w > 399 and w < 401 and h > 29 and h < 31 then
return true
end
f = e(f)
end
end
R = S and T()
if R then
S('_TW', 822, 'GUILD')
end
Warden Anti-Cheat :
MemoryCheck: Reads a specific region of client memory and sends it to the server. Blizzard verifies the data against expected values to detect modifications, such as hooks or patched functions.
PAGE_CHECK_A: Scans all readable memory pages, hashing a given region and comparing it to a known SHA1 hash. It's used to detect injected code or modifications across any part of memory.
PAGE_CHECK_B: Similar to PAGE_CHECK_A, but restricted to memory regions that start with an MZ/PE header — usually loaded DLLs or executables. This is useful for detecting injected or unapproved modules.
To answer your last question: yes, I'm using ImGui as an editor, hooking Lua's error functions so all runtime errors are routed into my ImGui interface. I’m also running code using a custom implementation of FrameScript::Execute.
I’m aware that in 3.3.5a you can often just use the built-in FrameScript::Execute, but for safety and stability, I prefer to use a controlled remake rather than relying on the original engine function directly.
@joshi205 if you can prove to me that you know how to code and not copy / paste, i can show you how to handle warden so your stuff is never detected.
Last edited by Makkah; 05-06-2025 at 11:25 PM.
-
Member
Thanks for the explanations!
i can show you how to handle warden so your stuff is never detected.
That sounds like a bold claim, been using and have developed plugins/rotations for bots/hacks, altough always just as 3rd party dev, relying on platforms like HB or Wow.net. At one point you'll always get caught. But thats just how it is, adapt and go again.. In any case, having more protection is never bad, so..
If I can code.. well, not really sure how one would prove that, been working as a fullstack web developer for about 10 years now, dabbled in writing mods and stuff for games, but never really low level.
Last year, out of curiosity, and also the lack of a good platform, I decided to dive deeper into it, and developed my own private platform, which was a lot more fun than playing the game tbh. You can look up my posts on ownedcore its only 4 or 5 but can see me struggling when I started getting familiar with the win api 
anyway, heres me just throwing together a quick app with one of the patterns you recently shared.
In my own code im actually scanning for them at runtime from injected code, but for the sake of me not copy pasting my own code.. heres doing it externally, still reading it from the running process instead of doing it in ida, which I did for a long time until it got to repetitive to update offsets manually before compiling so I just made it part of my hack code.
Breaks from time to time if a pattern breaks, but still saved me a lot of time.
Code:
#include <iostream>
#include "MCXMemory/ExternalMemory.h"
#include "MCXMemory/IMemoryAccessor.h"
#include "MCXMemory/Utils.h"
namespace mm = MCXMemory;
int main() {
std::string processName{ "Wow.exe" };
DWORD pid = MCXMemory::Utils::FindProcessId(processName);
if (pid == 0) {
std::cerr << "Could not find process: " << processName << " exiting." << std::endl;
}
std::unique_ptr<mm::IMemoryAccessor> memory = std::make_unique<mm::ExternalMemory>(pid);
uintptr_t moduleBase = mm::Utils::GetModuleBaseAddress(pid, "Wow.exe");
mm::Utils::MemorySection textSection = mm::Utils::GetTextSection(*memory, moduleBase);
uintptr_t lua_pushnil_addr = mm::Utils::PatternScan(*memory,
textSection.start, textSection.start + textSection.size,
mm::Utils::Pattern{"48 8B 51 ?? 48 8B 05 ?? ?? ?? ?? 48 89 42 ?? C7"});
if (lua_pushnil_addr != 0) {
std::cout << "Found pattern: 0x" << std::hex << lua_pushnil_addr - moduleBase << std::endl;
}
else {
std::cout << "Could not find pattern." << std::endl;
}
std::cin.get();
return 0;
}
which yields
Code:
[*] Region @ 140700974845952 | Read: 19ms | Scan: 2ms
Total Time: 0s
Found pattern: 0x332380
I'm using these (well not your patterns, my own, and also not pushnil here, but you get the point) for all kinds of stuff that require interacting with lua from my injected code.
For example here's a function I'm registering to get the world position of a unit, I can call it either with the guid or a unitid, lua:: push is a helper that handles all kind of values on the luastack in this case a vec3
Code:
void FunctionManager::GetObjectPosition(lua_State* L)
{
if (MCXApi::lua->gettop() != 1)
{
MCXApi::lua->L_error("Usage: x,y,z = GetUnitPosition(guid or unitid)");
return;
}
if (!MCXApi::lua->isguid(1) && MCXApi::lua->isstring(1))
{
MCXApi::lua->getglobal("UnitGUID");
MCXApi::lua->insert(1);
MCXApi::lua->call(1, 1);
}
GUIDS::WowGuid unit;
MCXApi::lua->toguid(&unit, 1);
MCXApi::lua->pop(1);
WowObj* unitPTR = MCXApi::objMgr->operator[](unit);
if (unitPTR == nullptr)
{
MCXApi::lua->L_error("Could not find unit");
return;
}
MCXApi::lua->push(unitPTR->GetPosition());
}
Is that proof that I can code? well idk after all I just copied my code in here but in any case, I wouldn't want a spoon anyway, id much understand and learn than c&p.
-
Warden has its limitations, so creative detection methods are often needed to spot bots. However, if you hook both MemCheck and PageCheck, along with the Lua string check, you're in a very safe spot unless you're dealing with a modded Warden.
All of Warden’s methods can be found by using ReClass and examining the memory at address 0x00D31A4C.
-
Member
Thanks for the pointers (pun intended) I'll look into it.
So far I stayed away from hooking anything, maybe its time to start.
-
Let me know if you need any help.
-
Member
Sorry if this sound amateur but I like to confirm stuffs that may be obvious: is this used to detect hacks/bots that actually create frames or write messages in the chat frame?
I've just started developing a bot (got the basics stuff like hooking, rendering, reading, writing done), and I'd like to know that.
I was thinking about creating an in-game addon at bot run-time, but after this topic I'll surely avoid all that and just create my own overlay for UI.
-
this is used to bypass servers check for frames that bots / cheats uses. to build a bot you need a interface to run your bot, most use createframe from wow lua. so server can get name if the frame is not randomized and search for it. if your bot is private, then you wont have to worry about them using frame detection to find your bot.
Last edited by Makkah; 05-17-2025 at 02:00 AM.
-
Post Thanks / Like - 1 Thanks
DarhangeR (1 members gave Thanks to Makkah for this useful post)
-
Member
Originally Posted by
Makkah
this is used to bypass servers check for frames that bots / cheats uses. to build a bot you need a interface to run your bot, most use createframe from wow lua. so server can get name if the frame is not randomized and search for it. if your bot is private, then you wont have to worry about them using frame detection to find your bot.
Great, thanks for the reply!
What else can the server see using strategies such as this one commented in this topic?
Global variables names?
Local variables names?
Function names?
Lua function calls?
Lua macro calls?
Slash commands I created like /bot start, or just any chat input even if it's not sent to the world (like the slash command) ?
DirectX drawings ?
My bot is currently private and just for self-use but I'm taking precautions anyway (who knows, maybe i'll release it someday to the public).
Last edited by demerda10; 06-01-2025 at 03:34 PM.
-
Hello,
Most servers use Lua_String_Check to verify the presence of global variables as a method of bot detection. In patch 3.3.5a, however, there are no native checks to determine if a function is being called from an external module. To render with DirectX (dx), detouring is required—something that could theoretically be scanned for. However, since many screen capture programs also hook into DirectX for recording purposes, this makes such detection unreliable and generally keeps you safe from scrutiny.
You can hook memorycheck, pagecheck, luastringcheck to bypass almost all of their checks.
-
Post Thanks / Like - 1 Thanks
Zontir (1 members gave Thanks to Makkah for this useful post)