Dear Blizzard developer,
Yesterday I contacted Blizzard support, to report about a critical security issue that I just found in Warcraft III. It is possible to create an exploit and
execute arbitrary machine code in a player's machine from inside a Warcraft map. The support responded quickly and told me to write to this email.
I don't know who will be reading this, but I know that Warcraft III has been created many years ago, and it's probable that you are not one of the developers of that game. Maybe Blizzard doesn't even have a specific team assigned to WC3 at the present moment, I don't know. But don't worry, I will explain everything about the exploit with all details.
Introduction
Warcraft III uses a special and unique language for the map script, the JASS language (an acronym for "Just Another Scripting Syntax"). It is a very verbose and easy to understand language.
JASS works in a way very similar to Java: when a map is loaded, the
JASS script is interpreted and compiled into
JASS bytecode, which is then executed by the
JASS VM. It is a very simple VM, it has a little more than 30 operations, and is entirely implemented within a single function of the Warcraft code.
There are 6 basic types in JASS:
integer, real, string, boolean, handle
and
code
. The
code
type works as a function-pointer type: you assign a function to it, and it will hold the memory address of the bytecode of that function. Then you can pass that address to any native that executes code, such as
ForGroup
.
The JASS language is strongly-typed, which means that all variables must have their type specified in the declaration, and cannot be assigned a value of another type. However,
we have the ability to typecast values between one type and another. This capability was not planned by Blizzard, but was discovered by the map making community many years ago.
Typecasting values can be used for many things. When it was discovered, it started a whole revolution in Warcraft map making. It allowed map makers to develop very sofisticated custom maps, creating things that were never thought to be possible, and turning WC3 into a very powerful game engine, with a huge amount of available resources.
We also have the ability to typecast function pointers. With that, it is possible to
write and execute the JASS bytecode directly, instead of having it created from the map script. This brings many possibilities such as dynamic generation of code, and a dramatic speed increase, unlocking the VM to its full pontential.
The problem
However the JASS VM has a critical security issue that has been present ever since the game was released. With some special bytecode, it's possible to unlock the ability to
read and write memory anywhere in the process, allowing a map to take complete control over the computer.
By the time of patch 1.23, some guy discovered the vulnerability and released a proof-of-concept map. This forced Blizzard to release the 1.24 patch, which fixed that particular exploit. However, that patch didn't solve the problem completely, it just patched the particular methods used by that code, but
the real vulnerability is still present.
The vulnerability lies in current implementation of arrays in the JASS VM. Internally, an array variable is actually a pointer to a special structure, that is represented here:
JASS:
struct JassArray<T>
{
unsigned int maxSize
unsigned int currentSize
T * data
}
Of course we don't have the Warcraft source code, so I don't know the real names of the struct and its members, but this representation allows you to understand. Notice that the size of this struct is actually 16 bytes, because the first 4 bytes are reserved for a vTable.
Whenever the JASS VM is going to read or write an array index, it will check if the requested index is smaller than the current allocated size. If that's the case, it will simply read or write at the data pointer, offset by the requested index.
If the requested index is bigger than the current size, and it's a read operation, it will return 0. If it's a write operation, the array grows dynamically - a new memory area is allocated as necessary, and all data is copied there.
The problem is that it's possible to
directly assign an address to an array variable, making it point to any struct we want. Consider the following JASS code:
JASS:
globals
integer array Memory
endglobals
function main takes nothing returns nothing
set Memory = 0x2114D008
endfunction
As you can see, this code is invalid. It tries to assign a value to an array variable, which is impossible. We can only read or write to the
members of an array, not to the array itself. That makes no sense.
If you write that code in a map, the JASS parser will refuse it, and the map will not run. But if we make this operation directly with JASS bytecode,
the VM will accept it!
Here is a representation of this operation in JASS bytecode:
JASS:
00 09 01 0C 0x2114D008 - LITERAL R1,0x2114D008
00 09 01 11 0x2 - SETVAR "Memory",R1
As I already said, we have no way to know the real names used by Blizzard. These are the names that the community uses to describe the JASS opcodes.
The instruction LITERAL (opcode 0xC) loads an immediate value into a register of the VM. The VM has 256 registers, here we are using register 01. The number 09 is the
type argument - in the VM all registers are type-safe, and when we load them with some value, we must declare the type. The number 9 corresponds to the type integer array.
The instruction SETVAR (0x11) is then used, to store the contents of R1 in a variable. The assignment only works if the type declared in the register is the same type of the variable. That's why we must use type 09 in the LITERAL instruction.
The number 0x2 is the variable id. In the JASS VM all operations handle variables and functions by their name. Those names are stored in the
String Table - every string used in the game is stored there, and assigned a unique id. Then those ids are used in the JASS bytecode.
Notice that it's not possible to obtain the id of a specific variable at runtime. However, because the ids are assigned sequentially, it's possible to manually edit the scripts of a map and declare a variable at the very beginning of the code. With this, we have the guarantee that this variable gets the id #2 (the number #1 is reserved for the string "main").
After executing this bytecode, the variable Memory will point to the address 0x2114D008. This address is located in one of the DLLs loaded by Warcraft, and points to a 16-byte structure that is compatible with JASS arrays. On that structure, the currentSize of the array will be a very large number, and the data pointer is 0.
With those values, the Memory array will be able to read and write memory
to the entire address space of the process! And once we have that ability, we can easily take control of the machine using standard code injection techniques.l.
How to fix it
All array types in the JASS VM have a number greater than or equal to 09. To prevent the JASS bytecode from tampering with an array you just need to check the type whenever a write operation is performed, and allow it to execute only for types smaller 9, which covers only the basic types.
You will find that all write operations are made by a single function, that is called in many places from the JASS VM. All VM operations that write data to a variable or register use that function. It already provides type-safety, so that you can't write a value of a type to a variable of another type.
So, in addition to the type-safety check, it also needs to check if the destination type is >=9, and if it is, don't write the data. If you need a reference, just look at the implementation of the SETARRAY operation (0x12). You will see that it checks if the variable is an array type, and if it isn't, nothing is done. You just need to implement that same check in the function that writes to variables and registers.
With this patch, the JASS VM will be completely secure against all possible hacks with bytecode. Arbitrary code execution from within a map script will never be possible again. It's a very simple fix, and since doesn't remove any feature from the JASS language, it won't break compatibility with any already existing map.
And btw, it would be nice if you can also take a look at The Hive Workshop forums, specially at the
Patch 1.27 wish list. We have been waiting many years for a new patch, and we would love to see some new features that could aid in map making. I know that many things in that list might be pointless or not worthy implementing, but maybe you can make some very simple changes like increasing (or removing) the multiplayer map size limit in this new patch. That would help the map making community a lot.
Anyway thank you very much for your attention! I hope you can fix this exploit and release a new Warcraft patch before someone else discovers about the exploit too. If you need any help with fixing that, or have any doubts about my explanation, feel free to reply to this email. I will gladly help with anything to solve this issue.
Thank you Blizzard developer, and have a good day!