Introduction

Hello and welcome to my first blog! In this blog, we’ll be talking in detail about how cheaters utilised the famous “FrameScript_ExecuteBuffer” function (Aka lua_dostring) for over a decade in the creation of bots. These bots range from full-blown item-grinding bots used for market gain (Gold resellers must get their gold from somewhere, right) to incredibly smart rotation bots used for personal gain by players in terms of PvP. Even PvE can be cheated in as you’ll see later from our example of the theory behind a healing bot. Not only will we be talking about how cheaters used this function for malicious gain, but we’ll also throw in my personal take on how private servers that currently use older versions of World of Warcraft could potentially patch this and sort out the “Scripting” issue.

Preface/Theory behind all of this

So now that I’ve discussed what I’ll be talking about and the general basis of how prominent this simple trick is in the history of World of Warcraft, let’s get into the theory behind how all of this works. FrameScript_ExecuteBuffer is a function within World of Warcraft’s main module, this C function is, essentially, a modified method similar to lua_dostring, which works the exact same way by taking a script and executing it in the World of Warcraft lua engine. World of Warcraft uses a lua engine for quite a lot of its in-game features. A popular example would be all the addons everyone uses. Now you might be wondering, what stops people from calling the lua functions that we can use to abuse from a mere addon? Simple, some functions that can be used to automate gaming are assigned the “Protected” flag. Now in the next chapter, we’ll talk specifically about what this protected flag actually means and what happens when we try to call those functions from an addon or even a /script “script” call.

Protected Functions

Now it’s time to talk more about what protected functions are. Blizzard developers saw these functions that players were originally allowed to call in the oldest versions of World of Warcraft and realised, hey this might be a little too overpowered and is causing automation when we strictly prohibit that. What did they end up doing? They decided to start by adding some trivial checks within the C function tied to the lua function. This check merely checked the context in which it was getting called, if it was from an addon or something along those lines - you’d get a generic error saying that you cannot call protected functions. What people originally did to combat this would be what they call “Lua unlockers”. This simply sorted the check out to allow people to continue calling said function. This wasn’t a smart idea as it was incredibly easy to check client modifications for Warden. After people started realising this potentially wasn’t the way, they looked for alternatives. Now, in the Lua C source, there was a function called “luaL_dostring”, this function invokes loadstring and, essentially, executes a script in the lua engine. This was a GOLDMINE for cheaters. They found World of Warcraft’s alternative named “FrameScript_ExecuteBuffer” and decided to start using this to execute malicious Lua scripts. Now you might be wondering, how does this get past the protected flag on the C functions? Simple, the context given was no longer being executed from an addon but rather a context that the check likes. As far as the check saw, it wasn’t an addon calling it and it was a legitimate call made internally.

Different Types of function Flags

So, now that we’ve talked about the Protected flag, what other flags are there? Here’s a table with the description. It’s safe to assume you’re able to call almost all functions with these flags through FrameScript_ExecuteBuffer.

Flag Description
BlizzardUI This function is not a C API but a Lua function declared in Blizzard’s default user interface
Confirmation This function does not prompt the user for confirmation before its results take effect
Deprecated This function is deprecated and is no longer in use
Hardware This function requires a key or mouse press in order to be used, but may not be protected
Internal This function does nothing in the standard game client and is used by Blizzard for internal purposes
LuaApi This function is defined in the Lua Standard Libraries
MacOnly This function is designed for the Mac OS X client only
NoCombat This function cannot be called during combat
Protected This function is protected and can only be called by the Blizzard User Interface
Server This function must query the remote server, and any results will not be immediately available to the game client.

FrameScript_ExecuteBuffer

Enough talking about the LuaApi, let’s talk about what we’re here for. FrameScript_ExecuteBuffer. Now, as I said before - this function is simply a luaL_dostring. We grab our script and run it through this function and we’ll end up executing the script within the context of World of Warcraft. This means we have access to Blizzard’s additional lua library that’s added to vanilla Lua. To complete this, we need to find the address for this function in World of Warcraft’s binary and setup a function delegate and call it. There’s actually another way to call this which was HUGELY popular back in the era this was mainly used in. What developers back in the day did was setup an external C# application which injected shellcode that called the function itself. An example of shellcode used to call this function would be as so;

mov eax, 0xDEADC0DE ; address of where the script is located in memory
push 0 ; will explain the last arg of FrameScript_ExecuteBuffer in the next chapter so don't worry about being confused about these arguments
push eax
push eax
mov eax, 0xDEADBEEF ; address of FrameScript_ExecuteBuffer
call eax
add esp, 0xC ; Decrement stack pointer
retn 

As we can see here, this was exactly how they would call the function externally. You may be wondering, where are they injecting this shellcode? It’s actually quite funny, this function is pretty good but it has one drawback. It MUST be executed from World of Warcraft’s main thread. This means, you couldn’t simply load your own dll and call it on a separate thread. Alternatively, you couldn’t just write this shellcode to the games memory and use CreateRemoteThread or anything. You had to get this code accessed from the main thread. To solve this, developers either installed temporary hooks on EndScene or flat out had an active hook placed on EndScene where they had this code executing in.

Where can we find FrameScript_ExecuteBuffer

Now that we understand how FrameScript_ExecuteBuffer reacts and how it was used by previous developers, how do we find the function itself? This might be the funniest part of everything, for such a huge game you’d expect it to be somewhat difficult in finding this extremely overpowered function. Wrong. Let’s dissect the binary using our favourite tool, IDA! After dumping the game using a dumping tool, me personally I enjoy using PD which is a really well written process dumper. Alternatives include Scylla! Now that we’ve loaded the dumped binary into IDA and let it anaylse till idle, let’s open the strings list and find a specific string, “RunScript”. Now this function is tied to the script class and pretty much is just a FrameScript_ExecuteBuffer encased function. Let’s go to the subroutine and see what we can find!

int __cdecl run_script_4DD490(int a1)
{
  _BYTE *v1; // eax

  if ( sub_84DF60(a1, 1) )
  {
    v1 = (_BYTE *)sub_84E0E0(a1, 1, 0);
    if ( v1 )
    {
      if ( *v1 )
        sub_819210((int)v1, (int)v1, 0); // Interesting call!
    }
  }
  return 0;
}

Would you look at that, a suspicious call to another function with arguments that match that of the shellcode I provided above, two of the same arguments & a argument of 0 at the end. Just like that we found quite possibly one of the most overpowered functions we could for the malicious purpose a cheater would have!

How to call FrameScript_ExecuteBuffer

Cool, we’ve got the offset. We know under what conditions we need to call it. Let’s piece it all together and write a basic idea on how to call said function. Two ways, either go external and inject shellcode or go internal and call it directly.

The internal way

  • Setup a function delegate for FrameScript_ExecuteBuffer
  • Hook EndScene (Either temporarily or permanently)
  • Call function with script passed e.g func_call(“print(‘hello’)”, “print(‘hello’)”, 0);
  • Finished!

The external way

  • Create codecave in allocation and write bytes of the script to that codecave
  • Setup shellcode with all arguments needed
  • Inject shellcode with a temporary hook to endscene
  • Finished!

Possible ways to detect this

We’ve given the cheaters too much credit, if something is easy to do it must be easier to detect right? Yes! There are quite a lot of ways able to detect this. Return checks are definitely an easy way to check the validity of the call. If EndScene is hooked and the return address from the stack is pointing towards EndScene, isn’t that just a little bit suspicious? Why on earth would EndScene want to call a function? As simple as this may be, it’s even simpler to bypass. Cheat developers are nifty, you could simply spoof the return address on the stack to a legit area in the games memory. Another idea could be setting up a VEH handler and placing a debug breakpoint on either RunScript or FrameScript_ExecuteBuffer if EndScene has been hooked (EndScene being hooked is not a good enough reason to call somebody a cheater because a lot of recording applications do this or legitimate internal overlays). If EndScene has been hooked and the breakpoint was hit, we could check the validity of the instruction pointer in the VEH handler to see where this call came from. This is just some basic methods that private servers could utilise because most of the software people use to script on World of Warcraft would definitely fall victim to these methods.

Handling return values

Enough of the methods of preventing it, let’s start getting into some more work of FrameScript_ExecuteBuffer. You might’ve already wondered to yourself, if we had this script;

some_return_value_we_need = 12

How do we get the value stored? This is crucial because a lot of the internal Lua functions return some information we need, like getting the health. The solution is a nifty function called GetText. What this function does is, it’ll grab a global variable’s value and return it. Now, how do we find this function? We know that World of Warcraft has some lua functions they’ve written for the blizzard interface (BlizzardUI Flag) and it’s only logical to assume that they do in fact call these functions themselves. How many of these do we think return something they need? A lot. Let’s go back into IDA and look for some suspicious strings. I decided to look at some functions that contain anything to do with the players health, maybe they want to see the players health or any information regarding the player at any point of time. Eventually I found something that looks like it might have something to do with it.

if ( v142 > 6 )
      v33 = "HEALTH_COST";
    else
      v33 = *(const char **)&off_AD2EA0[4 * v142];
    if ( v142 != 5 )
    {
      if ( v182 > 0 )
      {
        if ( v32 <= 0 )
        {
          if ( v29 )
          {
            v42 = sub_819D40(*(_DWORD *)(v29 + 8), -1, 0);
            sub_76ED20(Format, v42, 128);
            v43 = (char *)sub_819D40((int)"POWER_DISPLAY_COST", -1, 0); // Looks very interesting
            sub_76F070((int)v158, 128, v43, v182);
          }
          else
          {
            v44 = sub_819D40((int)v33, -1, 0);
            sub_76ED20(Format, v44, 128);
            sub_76F070((int)v158, 128, Format, v182);
          }
          goto LABEL_76;
        }
      }

Although I didn’t include it, sub_819D40 is called quite a lot and by checking the xrefs it does look like it does exactly as we figured, it’s getting information from the game’s lua context. This is it being called elsewhere.

v574 = (v573 - 3600) / 60;
v575 = (char *)sub_819D40((int)"INT_SPELL_DURATION_HOURS", 1, 0);
sub_76F070((int)v648, 4096, v575, 1);
v576 = (char *)sub_819D40((int)"INT_SPELL_DURATION_MIN", v574, 0);

Looks like this is our GetText function! It appears to take these arguments

  • Arg1 -> the global variable’s name
  • Arg2 -> Seems to range between -1, 1 quite a lot
  • Arg3 -> Constantly 0

Calling GetText

So now that we have the address of GetText, we can also call this just as we did with FrameScript_ExecuteBuffer. Once again, two ways of doing this - Internal & Externally.

The internal way
  • Setup a function delegate for GetText
  • Hook EndScene (Either temporarily or permanently)
  • Call function with global variable passed e.g int return_from_lua = func_call(“some_return_value_we_need”, -1, 0);
  • Finished!
The External way

We’ll need to write some new shellcode, so let’s do it.

mov eax, 0xDEADC0DE ; address of where the global variable is located in memory
push 0 
push -1 
push eax
mov eax, 0xDEADBEEF ; address of GetText
call eax
add esp, 0xC ; Decrement stack pointer
retn 

Just note, you’ll have to read the return as well after injecting this shellcode. We can’t just leave it there!

  • Create codecave in allocation and write bytes of the script to that codecave
  • Setup shellcode with all arguments needed
  • Inject shellcode with a temporary hook to endscene
  • Finished!

Theory behind a healbot!

So now that we can call any lua function and grab any return value from said function, what can we do? Practicaly everything that a normal player can. For instance, you can create a healbot! Let’s go through the steps how a healbot should typically go. We’re going to act as if a player is at least moving the bot.

  1. Grab the lowest healthed player. We’d want to do this by percentage rather than raw HP as players will all have different raw HP and some may be less but that’s all depicted by Class, Gear, Talents & Stats.
  2. Check if they’re in range of spell to be casted.
  3. Cast a rotation of spells depending on cooldowns & range.
  4. Profit?

Now I did mention that this could also be used for PvE, what’s the difference anyway? We’re merely healing a low HP’d target. You could realistically setup any type of bot you wanted to by using TWO functions!

Testing out a call

Now that we’ve got everything ready, let’s do a test call.

  • UnitHealth
using p_Dostring = int(*)(const char* script, const char* scriptname, int null);
static p_Dostring Dostring = reinterpret_cast<p_Dostring>(0x819210);
//...

using p_GetText = char*(__cdecl*)(char* text, std::uintptr_t unk1, std::uintptr_t unk2);
static p_GetText GetText = reinterpret_cast<p_GetText>(0x819D40);
//...

#DEFINE do_string(script) DoString(script, script, 0)
#DEFINE get_text(var) GetText((char*)var, -1, 0)

//... IN ENDSCENE HOOK
do_string("current_player_hp = UnitHealth(\"player\")");
std::string player_hp_str = get_text("current_player_hp");
std::printf("Player health -> %d\n", std::stoi(player_hp_str));
[TOGGLED]
Player health -> 34931

PoC

Here’s a simple PoC of how a healbot would work.

// Setup variables
std::string group_type = "(null)";
int num_of_members = 0;

// Get list of teammates
DoString("num_party = GetNumPartyMembers()");
std::string num_party = GetText((char*)"num_party", -1, 0);

DoString("num_raid = GetNumRaidMembers()");
std::string num_raid = GetText((char*)"num_raid", -1, 0);

// Check if raid
if (std::stoi(num_raid) > 0) {
    // Is a raid
    group_type = "raid";
    num_of_members = std::stoi(num_raid);
}
else {
    // Is a party
    group_type = "party";
    num_of_members = std::stoi(num_party);
}

Hps.push_back(GetHealth("player"));
if (num_of_members) {
    for (int i = 1; i <= num_of_members; i++) {
        std::string current_member_tag = group_type + std::to_string(i);
        int player_hp = GetHealth(current_member_tag.c_str());
        if ([current_member_tag]() {
            char p_hp[255];
                sprintf(p_hp, "range = UnitInRange(\"%s\")", current_member_tag.c_str());
                DoString(p_hp);
                return (!strcmp(GetText((char*)"range", -1, 0), "1"));
            }()) 
            Hps.push_back(player_hp);
        else
            Hps.push_back(0);
    }
}



std::string lowest_hp_person;
int lowest = GetLowestPlayer();
if (lowest != 0)
    lowest_hp_person = group_type + std::to_string(GetLowestPlayer());
else
    lowest_hp_person = "player";


int player_hp = GetHealth(lowest_hp_person.c_str());
// Check if player is stunned
for (std::string current_spell : negative::dispellable_stuns) {
    if (lowest_hp_person != "player" && HasDebuff(lowest_hp_person.c_str(), current_spell.c_str()) && IsSpellReady("Dispel Magic"))
        CastSpell("Dispel Magic", lowest_hp_person.c_str());
}

if (InCombat(lowest_hp_person.c_str()) && !HasDebuff(lowest_hp_person.c_str(), "Weakened Soul")) {
    CastSpell("Power Word: Shield", lowest_hp_person.c_str());
    return _EndScene(pDevice);
}
if (player_hp <= 40 && InCombat(lowest_hp_person.c_str()) && IsSpellReady("Pain Suppression")) {
    CastSpell("Pain Suppression", lowest_hp_person.c_str());
    return _EndScene(pDevice);
}
if (player_hp <= 40 && InCombat(lowest_hp_person.c_str()) && IsSpellReady("Desperate Prayer") && lowest_hp_person == "player") {
    CastSpell("Desperate Prayer", lowest_hp_person.c_str());
    return _EndScene(pDevice);
}
if (player_hp <= 75) {
    if (IsSpellReady("Penance")) {
        CastSpell("Penance", lowest_hp_person.c_str());
        return _EndScene(pDevice);
    }
    else {
        CastSpell("Flash Heal", lowest_hp_person.c_str());
        return _EndScene(pDevice);
    }
}
if (player_hp <= 80 && InCombat(lowest_hp_person.c_str()) && IsSpellReady("Prayer of Mending") && !HasBuff(lowest_hp_person.c_str(), "Prayer of Mending")) {
    CastSpell("Prayer of Mending", lowest_hp_person.c_str());
    return _EndScene(pDevice);
}
if (player_hp <= 85 && IsSpellReady("Renew") && !HasBuff(lowest_hp_person.c_str(), "Renew")) {
    CastSpell("Renew", lowest_hp_person.c_str());
    return _EndScene(pDevice);
}

Although the code is not pretty, this essentially will let a lvl 80 discipline priest heal like an arena champion. It’s amazing how such simple code can have players healing so well. Any function not shown within this simple PoC is essentially calling a function found from the old 2010 Lua Wiki for World of Warcraft.

Conclusion

Thank you for reading my first blog, this was made to essentially show just how insecure the older versions of World of Warcraft is, the newer ones are definitely better but still not there as of yet. The reason of this blog is because there is still a HUGE community of players who play on private servers that use older builds of World of Warcraft which are extremely susceptible to this basic yet brutal form of cheating.