JASS - Bytecode and I2C

Tutorial By Jesus4Lyf

In this tutorial I will teach you how to fire bytecode (compiled JASS) out of an array in Warcraft III. This is JASS bytecode, not machine code, and so cannot be used to write viruses. Furthmore, the map you write in this for yourself is likely not to work on other computers, or on battle.net. It is purely for your own experience and learning and toying, and I also take no responsibility for damage done to your computer (yeah, one of those disclaimers) by following this tutorial.First, you must acquire Cheat Engine. I am using version 5.5. We will not use this to edit any of Warcraft III's memory, but we will need it to find the memory location of an array.

Understanding what we are doing.

All data in RAM in a computer is stored in binary. Each 1 or 0 (each slot) is called a "bit". 8 of them is called a "byte". Computers look at things in bytes. A value, such as an integer, may be one or more byes in an application. Warcraft III is a 32 bit application (that being 4 bytes if you do the math) meaning each value is 32 bits in size. So integers, real values, strings, etc are all stored in 32 bits aka 4 bytes (although in the case of strings and handles and such, those bytes are used to point to other memory where the full string is stored, or handle and its data, etc, similar to the way a struct integer "points" to the values it holds which are actually stored in arrays).A byte does not have a type (integer, string, real, etc) but can be interpreted as any type (this is how typecasting with the return bug works, simply look at the same set of bits as a different type). So in this tutorial, we will look at the jass commands and take their hexadecimal values (you may Google that if you don't know what it means, but it is a number format) and insert them into a simple integer array. This will be effectively allow us to "copy" the memory containing a JASS function into an array, so to the computer the two will look exactly the same. We can then change a code variable to point at this memory location using the return bug, and fire it off.

The return bug.

I recently discovered a way to use the return bug in patch 1.24b. This is an explanation of how it works (well, more or less - this is an understandable explanation which is appropriate for the target audience).Warcraft III uses a hidden global variable to store the last value returned. When you say "return x" in a function, the value x is stored in the last value returned global. This global knows no type. The type is whatever type the function returns.So:
function GetHello takes nothing returns string
    return "Hello"
endfunction

Then lets say we do this:
function SayHello takes nothing returns nothing
    call BJDebugMsg(GetHello())
endfunction

It works like this. First, GetHello is called. GetHello sets the "last value returned" hidden global to "Hello". It then terminates. (That is what a "return x" does.) Then, the parameter loading stuff pulls this value out of the global and stores it to be fired with BJDebugMsg.What if we don't set a value?
function GetHello takes nothing returns string
    if false then // Meaning the lines in this clause will never fire, same as if 1==0.
        return "ThisLineIsNeverReached"
    endif
endfunction

Warcraft III allows this code to be accepted in Warcraft III (but in your editor, the syntax checker that comes with NewGen will scream at you - that is pJass).What this will do is return a "string" but it will not update the "last value returned" global to a string! Therefore, when the parameter loading stuff goes to pick up the last returned value, it will pick up the value last returned before this function, typecasted to a string (since a computer only sees bits and bytes, not types).So we can typecast code to integer like this:
function ReturnC takes code c returns code
    return c // Sets the last returned value to the code passed in.
endfunction
function C2I takes code c returns integer
    call ReturnC(c) // Set the last returned value to the code var.
    if false then // Never do this next line.
        return 0 // This line is never reached.
    endif   // So the function will terminate with the last returned value as our code,
endfunction // but it claims to return an integer! We have typecasted code to integer. :)

However, we must also run this through a cleaner function for unknown reasons. A cleaner function just returns the value passed in. Strange stuff, it fixes it.
function ReturnI takes integer i returns integer
    return i
endfunction// Now we can use ReturnI(C2I(function MyFunction)) to get code as an integer.

Get used to the syntax checker screaming at you, I don't know how to disable it.
You will not be able to use vJass in this tutorial. You shall simply have to put up with this. It is not the point, anyway.So here is the full C2I/I2C, which we will play with both of.
function ReturnI takes integer i returns integer
    return i
endfunction
function ReturnC takes code c returns code
    return c
endfunction
function C2I takes code c returns integer // Remember to wrap in ReturnI!
    call ReturnC(c)
    if false then
        return 0
    endif
endfunction
function I2C takes integer i returns code // Remember to wrap in ReturnC!
    call ReturnI(i)
    if false then
        return null
    endif
endfunction


Getting some code to play with.

First, we shall find the code we wish to experiment with (by duplicating in Bytecode). For this tutorial, let's call a function that displays an integer twice. So this:
function DisplayInt takes integer i returns nothing
    call BJDebugMsg(I2S(i))
endfunction
function WhatWeWillDoInBytecode takes nothing returns nothing
    call DisplayInt(5)
    call DisplayInt(6)
endfunction

Open up a blank Warcraft III map.
Delete Melee Initialization trigger.
Make a new trigger.
Name it "Bytecode".
Set the event to "Time - Elapsed game time is 0.30 seconds".
Edit > Convert to custom text.

Should look like this:
[SPOILER]
function Trig_Bytecode_Actions takes nothing returns nothing
endfunction//===========================================================================
function InitTrig_Bytecode takes nothing returns nothing
    set gg_trg_Bytecode = CreateTrigger(  )
    call TriggerRegisterTimerEventSingle( gg_trg_Bytecode, 0.30 )
    call TriggerAddAction( gg_trg_Bytecode, function Trig_Bytecode_Actions )
endfunction
[/SPOILER]
Add this at the top, above Trig_Bytecode_Actions:
function ReturnI takes integer i returns integer
    return i
endfunction
function ReturnC takes code c returns code
    return c
endfunction
function C2I takes code c returns integer // Remember to wrap in ReturnI!
    call ReturnC(c)
    if false then
        return 0
    endif
endfunction
function I2C takes integer i returns code // Remember to wrap in ReturnC!
    call ReturnI(i)
    if false then
        return null
    endif
endfunctionfunction DisplayInt takes integer i returns nothing
    call BJDebugMsg(I2S(i))
endfunction
function WhatWeWillDoInBytecode takes nothing returns nothing
    call DisplayInt(5)
    call DisplayInt(6)
endfunction

Set the actions to this:
function Trig_Bytecode_Actions takes nothing returns nothing
    call WhatWeWillDoInBytecode()
endfunction

Save the map. Save the map again. You should get two syntax errors. Nod and smile, and close it. Test the map. "5" and "6" will display. Yay.

Finding the code in memory.

Now we're ready to hack things. First, let's find what this looks like in memory. To do this, let's duplicate the function we wish to example 3 times.
function DisplayInt takes integer i returns nothing
    call BJDebugMsg(I2S(i))
endfunction
function WhatWeWillDoInBytecodeA takes nothing returns nothing
    call DisplayInt(5)
    call DisplayInt(6)
endfunction
function WhatWeWillDoInBytecodeB takes nothing returns nothing
    call DisplayInt(5)
    call DisplayInt(6)
endfunction
function WhatWeWillDoInBytecodeC takes nothing returns nothing
    call DisplayInt(5)
    call DisplayInt(6)
endfunction

Now, change your actions to this:
function Trig_Bytecode_Actions takes nothing returns nothing
    call BJDebugMsg(I2S(ReturnI(C2I(function WhatWeWillDoInBytecodeB))))
endfunction

Did I mention that code variables are real memory location pointers to jass bytecode? Well they are. What the frak does that mean? They store the memory location of where a function begins, basically.So, run the testmap.
OMG! Nasty number. Let's make it pretty.
We need to convert it to hex. At the very top of your trigger, add this function:
function ToHex takes integer i returns string
    local integer r
    local string result=""
    loop
        exitwhen i==0
        set r=i-(i/16)*16
        if r==0 then
            set result="0"+result
        elseif r==1 then
            set result="1"+result
        elseif r==2 then
            set result="2"+result
        elseif r==3 then
            set result="3"+result
        elseif r==4 then
            set result="4"+result
        elseif r==5 then
            set result="5"+result
        elseif r==6 then
            set result="6"+result
        elseif r==7 then
            set result="7"+result
        elseif r==8 then
            set result="8"+result
        elseif r==9 then
            set result="9"+result
        elseif r==10 then
            set result="A"+result
        elseif r==11 then
            set result="B"+result
        elseif r==12 then
            set result="C"+result
        elseif r==13 then
            set result="D"+result
        elseif r==14 then
            set result="E"+result
        else
            set result="F"+result
        endif
        set i=(i-r)/16
    endloop
    return "0x"+result
endfunction

We don't care about efficiency, we just care about not using vJass since it will not compile with ease. This converts an integer into a hex string. Very good for us.Change actions to:
function Trig_Bytecode_Actions takes nothing returns nothing
    call BJDebugMsg(ToHex(ReturnI(C2I(function WhatWeWillDoInBytecodeB))))
endfunction

Run map. Note down what displays. For me it is 0xF480FF0. It may be different for you. Do not close Warcraft III. Let it stay up in the background. Doesn't matter if it pauses itself.We will now examine the memory located there. Get notepad open and open Cheat Engine. Click the top left computery searchy icon thing. Near the bottom (probably) you should find 000xxx-war3.exe (something like that). Click it and click open. In the bottom right above the display click the "Add address manually" button. Paste in everything after the "0x" we got before for the Address field, so for me it is F480FF0. Click OK. In your list at the bottom you now have that memory location. But only the first 4 bytes of what is there. Let's check out what's there! Right click on it, click "browse this memory region" (should be third option).I bet you feel leet. Ignore the top half of the screen and look at the bottom half. Should look like this:
0F480FF0 00 04 A2 0C 05 00 00 00 00 00 A2 13 00 00 00 00 0F480000 00 00 00 16 91 0F 00 00 00 00 01 0B 00 00 00 00 0F480010 00 04 A3 0C 06 00 00 00 00 00 A3 13 00 00 00 00 0F480020 00 00 00 16 91 0F 00 00 00 00 01 0B 00 00 00 00 0F480030 00 00 00 27 00 00 00 00 00 00 00 04 00 00 00 00 0F480040 00 00 00 03 94 0F 00 00 00 04 A4 0C 05 00 00 00 0F480050 00 00 A4 13 00 00 00 00 00 00 00 16 91 0F 00 00 0F480060 00 00 01 0B 00 00 00 00 00 04 A5 0C 06 00 00 00 0F480070 00 00 A5 13 00 00 00 00 00 00 00 16 91 0F 00 00 0F480080 00 00 01 0B 00 00 00 00 00 00 00 27 00 00 00 00 0F480090 00 00 00 04 00 00 00 00 00 00 00 03 95 0F 00 00 0F4800A0 00 03 A6 0F 93 0F 00 00 00 00 A6 13 00 00 00 00 0F4800B0 00 00 00 16 8F 0F 00 00 00 00 01 0B 00 00 00 00 0F4800C0 00 00 00 13 00 00 00 00 00 00 00 16 8D 0F 00 00 0F4800D0 00 00 01 0B 00 00 00 00 00 00 00 13 00 00 00 00
The reason I got us to put the code there 3 times is so we can look for patterns. My experience lets me know that "00 00 00 27" means endfunction. Oh, by the way, since it is in hex, each two digits represents a value (from 0 to 255 in hex). Which is a byte. Remembering Warcraft III is 32 bit, which is 4 byte, we can look at each byte. Let's beak it up.
0F480FF0 00 04 A2 0C 05 00 00 00 00 00 A2 13 00 00 00 00 0F480000 00 00 00 16 91 0F 00 00 00 00 01 0B 00 00 00 00 0F480010 00 04 A3 0C 06 00 00 00 00 00 A3 13 00 00 00 00 0F480020 00 00 00 16 91 0F 00 00 00 00 01 0B 00 00 00 00 0F480030 00 00 00 27 00 00 00 00 00 00 00 04 00 00 00 00 0F480040 00 00 00 03 94 0F 00 00 00 04 A4 0C 05 00 00 00 0F480050 00 00 A4 13 00 00 00 00 00 00 00 16 91 0F 00 00 0F480060 00 00 01 0B 00 00 00 00 00 04 A5 0C 06 00 00 00 0F480070 00 00 A5 13 00 00 00 00 00 00 00 16 91 0F 00 00 0F480080 00 00 01 0B 00 00 00 00 00 00 00 27 00 00 00 00 0F480090 00 00 00 04 00 00 00 00 00 00 00 03 95 0F 00 00 0F4800A0 00 03 A6 0F 93 0F 00 00 00 00 A6 13 00 00 00 00 0F4800B0 00 00 00 16 8F 0F 00 00 00 00 01 0B 00 00 00 00 0F4800C0 00 00 00 13 00 00 00 00 00 00 00 16 8D 0F 00 00 0F4800D0 00 00 01 0B 00 00 00 00 00 00 00 13 00 00 00 00
Look for the pattern. Remember, we call the function twice. Something must occur twice. By the way, the very left side is just the memory locations. A line number, if you will. ;)So this is the code for "WhatWeWillDoInBytecodeB" (as it reads from where we looked for it, typecasted to int, to 00 00 00 27):
0F480FF0 00 04 A2 0C 05 00 00 00 00 00 A2 13 00 00 00 00 0F480000 00 00 00 16 91 0F 00 00 00 00 01 0B 00 00 00 00 0F480010 00 04 A3 0C 06 00 00 00 00 00 A3 13 00 00 00 00 0F480020 00 00 00 16 91 0F 00 00 00 00 01 0B 00 00 00 00 0F480030 00 00 00 27
And then we see a blank byte, 00 00 00 00. Then we see some random stuff which from experience I have concluded is part of the endfunction but is not necessary.
And then we see it repeats for "WhatWeWillDoInBytecodeC"! How thrilling...
00 04 A4 0C 05 00 00 00 0F480050 00 00 A4 13 00 00 00 00 00 00 00 16 91 0F 00 00 0F480060 00 00 01 0B 00 00 00 00 00 04 A5 0C 06 00 00 00 0F480070 00 00 A5 13 00 00 00 00 00 00 00 16 91 0F 00 00 0F480080 00 00 01 0B 00 00 00 00 00 00 00 27
So! 00 xx xx 0c is obviously "call" (as it occurs exactly twice in a function that called a functon twice). Warcraft 3 has wierd things, like each call gets assigned a unique ID which doesn't actually matter. Anyway, so the bytecode is as such:
WhatWeWillDoInBytecodeB 00 04 A2 0C 05 00 00 00 00 00 A2 13 00 00 00 00 00 00 00 16 91 0F 00 00 00 00 01 0B 00 00 00 00 00 04 A3 0C 06 00 00 00 00 00 A3 13 00 00 00 00 00 00 00 16 91 0F 00 00 00 00 01 0B 00 00 00 00 00 00 00 27
And
WhatWeWillDoInBytecodeC 00 04 A4 0C 05 00 00 00 00 00 A4 13 00 00 00 00 00 00 00 16 91 0F 00 00 00 00 01 0B 00 00 00 00 00 04 A5 0C 06 00 00 00 00 00 A5 13 00 00 00 00 00 00 00 16 91 0F 00 00 00 00 01 0B 00 00 00 00 00 00 00 27
Lets look at them next to eachother. Since they are the same. Maybe we can learn something.
WhatWeWillDoInBytecodeB WhatWeWillDoInBytecodeC 00 04 A2 0C 00 04 A4 0C // Slight difference. 05 00 00 00 05 00 00 00 // Hey look! It's that parameter 5. 00 00 A2 13 00 00 A4 13 // Slight difference. 00 00 00 00 00 00 00 00 00 00 00 16 00 00 00 16 91 0F 00 00 91 0F 00 00 00 00 01 0B 00 00 01 0B 00 00 00 00 00 00 00 00 // Repeat? 00 04 A3 0C 00 04 A5 0C // Slight difference. 06 00 00 00 06 00 00 00 // Hey look! It's that parameter 6. 00 00 A3 13 00 00 A5 13 // Slight difference. 00 00 00 00 00 00 00 00 00 00 00 16 00 00 00 16 91 0F 00 00 91 0F 00 00 00 00 01 0B 00 00 01 0B 00 00 00 00 00 00 00 00 // End function 00 00 00 27 00 00 00 27
Now we're getting somewhere.Close Cheat Engine. Close Warcraft III.

Duplicating the memory using an array (and firing it).

In the variable editor add an integer array named "bc" (so it will be udg_bc in JASS).To duplicate this in an array, you must know one thing. The bytes are in reverse order. So for example, endfunction which is 00 00 00 27 in memory, is 0x27000000 in an integer array to have the same memory replication.Let's find your array's memory location. We can use Cheat Engine to do a memory search for this. Firstly, there's something about WC3 arrays you should know. I've heard they are coppied in memory elsewhere when they need to grow. So the first thing we should do is set array[8191] to 1 so that the full array must be created and won't move.Then, let's give it a generic value we can search for. Let's use 0x13371337. So to give the memory a reading of 13 37 13 37, we need to store value 0x37133713 in the array (backwards bytes principle).Once again, do it 3 times because patterns are easier to find. So we have for our actions:
function Trig_Bytecode_Actions takes nothing returns nothing
    set udg_bc[8191]=1
    set udg_bc[0]=0x77057705 // For something different to find.
    set udg_bc[1]=0x37133713
    set udg_bc[2]=0x37133713
    set udg_bc[3]=0x37133713
endfunction

Run the map. Wait a second. Then minimise Warcraft III and off we go again. Open Cheat Engine. Find the process war3.exe and such.Let's do the memory search. Near the top you will find "First Scan". Under that is a check box labelled "Hex". Check it. For the value, copy 13371337 in. Click "First Scan".On the left it will list all memory locations with this value (yes, 13371337 randomly shows up sometimes, so there is more than what we wanted). For me it shows like this:
Address Value 0A11AB56 13371337 0C8D2C2E 13371337 0F040EE5 13371337 0F040EE7 13371337 0F040EE9 13371337 0F040EEB 13371337
Now. The reason we did it 3 times is we can see that there are a clear 3 memory locations very close together all with this value. Well actually, it looks like 4. This is why we chose a different value for index 0. Guess what we do now? Right click the first of the cluster (0F040EE5 in this case for me) and click "browse this memory region". We can see they are 2 apart. If we subtract 2 frm the first one, then we should end up with a memory location that holds "05770577". So that, for me, should be 0F040EE3. Do it for your value also. Right click in what should be a mass of 00's basically, and click Goto address. Enter in the value you got (0F040EE3 for me). Oh look at that, wasn't quite right. In fact, we were silly to choose 37133713 because it repeats itself - no wonder we got an extra result. Subtract another 3 and you should get there. There's a bit of trial and error about this kind of thing (0F040EE0 for me). Go to that address and bam, 05 77 05 77. We now know the memory location of the array. We can fill it with bytecode from there to replicate what a JASS function looks like in memory, and then typecast its memory location to a code variable and fire it! Let's do that! NOTE THIS MEMORY LOCATION AS THE MEMORY LOCATION FOR INDEX 0 FOR THE ARRAY udg_bc. By the way, this location can change as you edit the map, believe it or not. So beware. ;)So! Let's make display "1337".
Here's what the function calling displaying 5 and 6 looked like:
00 04 A4 0C 05 00 00 00 // This would look like 0x0000005 in JASS. Which is the integer 5. 00 00 A4 13 00 00 00 00 00 00 00 16 91 0F 00 00 00 00 01 0B 00 00 00 00 00 04 A5 0C 06 00 00 00 00 00 A5 13 00 00 00 00 00 00 00 16 91 0F 00 00 00 00 01 0B 00 00 00 00 00 00 00 27
So let's make it look like this:
00 04 A4 0C 1337 // Not hex. This is the actual value we want. 00 00 A4 13 00 00 00 00 00 00 00 16 91 0F 00 00 00 00 01 0B 00 00 00 00 00 00 00 27
Apply reverse byte principle.
0CA40400 1337 // Not hex. This is the actual value we want. 13A40000 00000000 16000000 00000F91 0B010000 00000000 27000000
Jam it in an array and fire the array by memory location! (Expand array first using set udg_bc[8191]=1)To convert the array memory location into a code variable, you do not apply the reverse byte order principle. You simply stick "0x" in front of it, typecast it to code, pass it through the cleaner function, and fire it off how you like.
function Trig_Bytecode_Actions takes nothing returns nothing
    local trigger t
    
    set udg_bc[8191]=1
    // Call DisplayInt(1337)
    set udg_bc[0]=0x0CA40400
    set udg_bc[1]=1337
    set udg_bc[2]=0x13A40000
    set udg_bc[3]=0x00000000
    set udg_bc[4]=0x16000000
    set udg_bc[5]=0x00000F91
    set udg_bc[6]=0x0B010000
    set udg_bc[7]=0x00000000
    // Endfunction
    set udg_bc[8]=0x27000000
    
    set t=CreateTrigger()
    call TriggerAddAction(t,ReturnC(I2C(0x0F040EE0))) // This value is specific to your computer.
    call TriggerExecute(t)
endfunction

I actually wrote this tutorial right up to this point at which I discovered the exploit no longer works. Blizzard seems to have implemented bounds checking so that arrays cannot be used to fire from.

Edit:
I recalled that Preloader runs in its own scope effectively, so its globals only last for the instant that it runs. This allows me to write to an array in that scope, then when the memory gets deallocated, it is no longer an array, but the memory remains the way it was set up. Running the memory at this location then allows me to execute bytecode.

I'll probably update this tutorial later, but for now, working bytecode map attached, follow the tutorial and figure out how to make it work for your computer until then. :D

Click here to comment on this tutorial.
 
 
Blizzard Entertainment, Inc.
Silkroad Online Forums
Team Griffonrawl Trains Muay Thai and MMA fighters in Ohio.
Apex Steel Pipe - Buys and sells Steel Pipe.