• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Accessing memory from the script - it's time of the revolution

Status
Not open for further replies.
Level 9
Joined
Jul 30, 2012
Messages
156

Reading memory from the script - direct access to any data in game

Last year I have discovered a critical exploit in WC3, as I described here. The vulnerability allowed for unlimited memory access from within a map's script, which is obviously a critical security issue. Anyone with this knowledge could create and distribute a modified map and gain complete control over a user's machine.

However the possibility of memory access also brings a whole new potential for map making in Warcraft. It allows to finally achieve the always dreamed things that were never possible before. Basically there are no limits to what we can do with this power.

After patch 1.27b the exploit has been fixed, and it's no longer possible to write to memory. But I requested Blizzard to keep the read-only access, and they did it, so read-only memory access is still possible in the latest patches!

In the last year DracoL1ch, karaulov and I have been researching about the game memory. They have achieved a lot of amazing things, and released it in the Memory hack thread. Although that exploit doesn't work anymore (because it tries to unlock read-write access), some parts of that API can be ported to work this library, as long as they don't modify things in memory.

The following code provides the ability to read from any place in WC3 memory, but NOT write. It is split into 2 parts: the first library provides typecasting, and the second uses it to get memory access.

1 - Typecasting in the latest patch

As I already explained, the basis of the exploit is the ability to typecast values, which is still possible in the latest patches. Then, by using C2I and I2C we can manipulate the Jass VM by skipping lines of execution, and ultimately running bytecode directly. The following library provides the typecasting functionality:

JASS:
library Typecast

globals
    //These are not used, they are here just to fool Jasshelper.
    code Code
    integer Int
    string Str
    boolean Bool
 
    //These are the actual ones used for typecasting.
    code l__Code
    integer l__Int
    string l__Str
    boolean l__Bool
endglobals

//The "return" line prevents Jasshelper from inlining these functions
function setCode takes code c returns nothing
    set l__Code = c
    return
endfunction

function setInt takes integer i returns nothing
    set l__Int = i
    return
endfunction

function setStr takes string s returns nothing
    set l__Str = s
    return
endfunction

function setBool takes boolean b returns nothing
    set l__Bool = b
    return
endfunction

//Jasshelper will append an "l__" prefix to all Typecast locals
private function Typecast1 takes nothing returns nothing
    local integer Str   //l__Str
    local string Int    //l__Int
endfunction

//# +nosemanticerror
function SH2I takes string s returns integer
    call setStr(s)
    return l__Str
endfunction

//# +nosemanticerror
function I2SH takes integer i returns string
    call setInt(i)
    return l__Int
endfunction

private function Typecast2 takes nothing returns nothing
    local integer Bool  //l_Bool
    local boolean Int   //l_Int
endfunction

//# +nosemanticerror
function B2I takes boolean b returns integer
    call setBool(b)
    return l__Bool
endfunction

//# +nosemanticerror
function I2B takes integer i returns boolean
    call setInt(i)
    return l__Int
endfunction

private function Typecast3 takes nothing returns nothing
    local integer Code   //l__Code
    local code Int       //l_Int
endfunction

//# +nosemanticerror
function C2I takes code c returns integer
    call setCode(c)
    return l__Code
endfunction

//# +nosemanticerror
function I2C takes integer i returns code
    call setInt(i)
    return l__Int
endfunction

//# +nosemanticerror
function realToIndex takes real r returns integer
    return r
endfunction

function cleanInt takes integer i returns integer
    return i
endfunction

//# +nosemanticerror
function indexToReal takes integer i returns real
    return i
endfunction

function cleanReal takes real r returns real
    return r
endfunction

endlibrary

This snippet requires that you use the experimental version of pjass, that can disable syntax checking on specific functions. After all, the code makes pretty much no sense. But it works!

This is because of a bug that occurs during the parsing of the script: if you declare a local variable with the same name as a global, it will cause the type of the global variable to be transformed, and become the type of the local! This means that all code that comes after the Typecast3 function will treat variable l__Code as an integer, and variable l__Int as code.

But the code that comes before Typecast will still treat those variables as their original types. That's why the setCode and setInt functions are used. Since they come before Typecast, they are not affected by the bug. So they can be used to assign a value to those variables, and then that value can be read as a different type.

2 - Getting access to memory

To unlock access to memory, we need to execute the JASS bytecode directly from an array. However. to obtain the memory address of an array, we need the ability to access memory in first place! This leads to a dead end, forcing us to find other ways to get memory access.

There are some ways to solve this, and I decided to use a variable named "stand" to get around that problem, since this string appears in blizzard.j, it will always have the same id. But keep in mind that map optimizers will usually rename variables, so keep an eye on that if you optimize your map, because this name may change in the final MPQ, and you will need to change it back.

JASS:
library Memory initializer InitArrays requires Typecast

globals
    integer array Memory
    real array RMemory
    //Arrays for unaligned memory access.
    integer array Memory1
    integer array Memory2
    integer array Memory3

    integer bytecode  // Not used, it's here just to fool Jasshelper
    integer array l__bytecode
    private integer Count = 0
   
    private trigger t = CreateTrigger()
    private integer GetArrayAddress_Id
endglobals

function ReadMemory takes integer address returns integer
    return Memory[address/4] //Inline-friendly
endfunction

function ReadMemory1 takes integer address returns integer
    return Memory1[address/4] //Inline-friendly
endfunction

function ReadMemory2 takes integer address returns integer
    return Memory2[address/4] //Inline-friendly
endfunction

function ReadMemory3 takes integer address returns integer
    return Memory3[address/4] //Inline-friendly
endfunction

function ReadMemoryReal takes integer address returns real
    return RMemory[address/4] //Inline-friendly
endfunction

private function Structs takes nothing returns integer
    return -0x120
    return 0x220
    return 0x320
endfunction

function GetArrayAddress takes integer StructAddr returns integer
    return Memory[StructAddr/4+3]
endfunction

//# +nosemanticerror
private function RegisterBCArray takes integer id, code func returns nothing
    set l__bytecode[Count] = 0x0E000400
    set l__bytecode[Count+1] = id
    call setCode(func)
    set l__bytecode[Count+2] = 0x11000000
    set l__bytecode[Count+3] = Memory[l__Code/4 + 1]
    set l__bytecode[Count+4] = 0x16000000
    set l__bytecode[Count+5] = Memory[l__Code/4 - 1]
    set Count = Count + 6
    set l__bytecode[Count] = 0x27000000
endfunction

module Bytecode
    integer value
    static integer id
    static integer address

    static if thistype.hasTrigger then
        static trigger trigger = CreateTrigger()
    endif

    static method operator[] takes integer i returns integer
        return thistype(i).value
    endmethod

    static method operator[]= takes integer i, integer value returns nothing
        set thistype(i).value = value
    endmethod
   
    private static method onRegister takes nothing returns nothing
        set address = Memory[address/4 + 3]
        static if thistype.hasTrigger then
            call TriggerClearConditions(trigger)
            call TriggerAddCondition(trigger, Condition(I2C(address)))
        endif
    endmethod

    private static method onInit takes nothing returns nothing
        set thistype(8190).value = 0            //Always allocate full size of the array
        set id = Memory[C2I(function thistype.onInit)/4 + 9]
        call RegisterBCArray(id, function thistype.onRegister)
    endmethod
endmodule

private function RegisterMemArray takes integer id, integer id2 returns nothing
    set l__bytecode[Count] = 0x0E000400
    set l__bytecode[Count+1] = id
    set l__bytecode[Count+2] = 0x13000000
    set l__bytecode[Count+4] = 0x16000000
    set l__bytecode[Count+5] = GetArrayAddress_Id
    set l__bytecode[Count+6] = 0x0B010000
    set l__bytecode[Count+8] = 0x06040000
    set l__bytecode[Count+9] = id2
    set l__bytecode[Count+10] = 0x11000000
    set l__bytecode[Count+11] = id2
    set Count = Count + 12
    set l__bytecode[Count] = 0x27000000
endfunction

//! textmacro MemArray takes NAME, TYPE
module MemArray$NAME$
    $TYPE$ value
    private integer struct

    static method operator address takes nothing returns integer
        return thistype(3).struct
    endmethod

    static method operator address= takes integer i returns nothing
        set thistype(3).struct = i
    endmethod

    static method operator[] takes integer i returns $TYPE$
        return thistype(i).value
    endmethod

    private static method onInit takes nothing returns nothing
        set thistype(2).struct = 0xFFFFFFFF
        set thistype(1).struct = 0xFFFFFFFF
        call RegisterMemArray(Memory[C2I(function thistype.onInit)/4 + 9], Memory[C2I(function thistype.onInit)/4-13])
    endmethod
endmodule
//! endtextmacro

//! runtextmacro MemArray("","integer")
//! runtextmacro MemArray("Real","real")

private function InitBytecode takes integer i, integer r, integer func returns nothing
    set l__bytecode[8190] = 0
    set l__bytecode[0] = 0x0C000300 //op: 0C(LITERAL), type: 03(code), reg: 00
    set l__bytecode[1] = C2I(function Structs)
    set l__bytecode[2] = 0x06030000 //op: 06(NEWGLOBAL), type: 03(code)
    set l__bytecode[3] = i
    set l__bytecode[4] = 0x11000000 //op: 11(SETVAR), reg: 01, var: "Memory"
    set l__bytecode[5] = i
    set l__bytecode[6] = 0x06030000 //op: 06(NEWGLOBAL), type: 03(code)
    set l__bytecode[7] = r
    set l__bytecode[8] = 0x11000000 //op: 11(SETVAR), reg: 01, var: "RMemory"
    set l__bytecode[9] = r
    set l__bytecode[10] = 0x16000000 //op: 16(JASSCALL), func: "Step2"
    set l__bytecode[11] = func
endfunction

//# +nosemanticerror
private function Step2 takes nothing returns nothing
    set l__bytecode[12] = 0x0C010300 //op: 0C(LITERAL), type: 03(code), reg: 01
    set l__bytecode[13] = l__Code-7
    set l__bytecode[14] = 0x0C020300 //op: 0C(LITERAL), type: 03(code), reg: 02
    set l__bytecode[15] = l__Code+25
    set l__bytecode[16] = 0x0C030300 //op: 0C(LITERAL), type: 03(code), reg: 03
    set l__bytecode[17] = l__Code+49
    set l__bytecode[18] = 0x06030000 //op: 06(NEWGLOBAL), type: 03(code)
    set l__bytecode[19] = Memory[C2I(function ReadMemory1)/4+13]
    set l__bytecode[20] = 0x11010000 //op: 11(SETVAR), reg: 01, var: "Memory1"
    set l__bytecode[21] = l__bytecode[19]
    set l__bytecode[22] = 0x06030000 //op: 06(NEWGLOBAL), type: 03(code)
    set l__bytecode[23] = Memory[C2I(function ReadMemory2)/4+13]
    set l__bytecode[24] = 0x11020000 //op: 11(SETVAR), reg: 02, var: "Memory2"
    set l__bytecode[25] = l__bytecode[23]
    set l__bytecode[26] = 0x06030000 //op: 06(NEWGLOBAL), type: 03(code)
    set l__bytecode[27] = Memory[C2I(function ReadMemory3)/4+13]
    set l__bytecode[28] = 0x11030000 //op: 11(SETVAR), reg: 03, var: "Memory3"
    set l__bytecode[29] = l__bytecode[27]
    set l__bytecode[30] = 0x27000000
    set GetArrayAddress_Id = Memory[C2I(function GetArrayAddress)/4-1]
endfunction

private function Typecast takes nothing returns nothing
    local integer bytecode
endfunction

//# +nosemanticerror
private function Step1 takes nothing returns nothing
    local integer array stand
    call InitBytecode(stand[C2I(function ReadMemory)/4+13], stand[C2I(function ReadMemoryReal)/4+13], stand[C2I(function Step2)/4-1])
    call TriggerAddCondition(t, Condition(I2C(stand[l__bytecode/4+3])))
endfunction

private function Step0 takes nothing returns integer
    local code stand = function Structs
    return -0xC5F0603
    return 0x2700
endfunction

//Initialize registered bytecode arrays.
//# +nosemanticerror
private function InitArrays takes nothing returns boolean
    call ForForce(bj_FORCE_PLAYER[0], I2C(Memory[l__bytecode/4+3]))
    return false
endfunction

private module Init
//# +nosemanticerror
private static method onInit takes nothing returns nothing
    call setCode(function Step0)
    call TriggerAddCondition(t, Condition(I2C(l__Code+26)))
    call TriggerAddCondition(t, Condition(I2C(l__Code+8)))
    call setCode(function Step1)
    call TriggerAddCondition(t, Condition(I2C(l__Code+8)))
    call TriggerEvaluate(t)
    call TriggerClearConditions(t)
    call TriggerRegisterGameEvent(t, EVENT_GAME_LOADED)
    call TriggerAddCondition(t, Condition(function InitArrays))
endmethod
endmodule

private struct MemInit extends array
    implement Init
endstruct

struct CustomMem extends array
    implement MemArray
endstruct

endlibrary

Here are a few libraries that provide some real functionality, since this is what everyone is expecting after all. This is just the beginning of course, as I said you can read basically any information from the memory, it's just a matter of finding where it is.

EDIT 04/10/2017:: Updated all libraries. Please check the changelog and get the new versions.


JASS:
/* Provides automatic detection of GameState object, Jass context, Unit
   and Ability object data tables and GameUI object for Mouse functions.
   This code is expected to work on all versions of WC3, even future ones. */

library Version initializer Init requires Memory, optional DummyCaster

globals
    integer GameState
    integer pJassContext
    integer pGameUI
    integer TagTable
    integer StringHandles
    integer array ObjectTables

    integer IsMac = 0
    integer IsLegacy = 0
endglobals

struct HandleTable extends array
    implement MemArray
endstruct

struct GameTypeSupported extends array
    implement MemArray
endstruct

native DebugBreak takes integer i returns nothing

private function GetDummyUnit takes nothing returns unit
    static if LIBRARY_DummyCaster then
        return DUMMY
    else
        return CreateUnit(Player(0), 'uloc', .0, .0, .0)
    endif
endfunction

private struct BC extends array
    static constant boolean hasTrigger = true
    implement Bytecode
endstruct

private function FindDataTable takes integer func, boolean inlined returns integer
    set CustomMem.address = func
    loop
        exitwhen CustomMem[0]/16777216 == 0xFFFFFFE9    //always search after first CALL (E8)
        set CustomMem.address = CustomMem.address + 1
    endloop
    if IsMac == 1 then  //all code in Mac is position-independent, first CALL returns current EIP
        set func = CustomMem.address
        loop
            set CustomMem.address = CustomMem.address + 1
            exitwhen CustomMem[1]/16777216 == 0xFFFFFFE9    //begin searching after 2nd call
        endloop
        loop
            set CustomMem.address = CustomMem.address + 1
            exitwhen CustomMem[2]*256/16777216 == 0xFFFFFF8E and CustomMem[2]/1073741824 == 0xFFFFFFFF  //LEA r32, [r32+offset]
        endloop
        return CustomMem[3] + func + 36
    endif
    if inlined then  //GetUnitData is inlined in 1.27+, windows only.
        loop
            //"CMP r32,-1", followed by JZ SHORT (83F? FF, 74 XX)
            exitwhen CustomMem[2]*16777216 == 0x83000000 and CustomMem[2]/2048 == 0xE9FFF
            set CustomMem.address = CustomMem.address + 1
        endloop
        return CustomMem[1] - 8
    endif
    loop
        set CustomMem.address = CustomMem.address + 1
        exitwhen CustomMem[1]/16777216 == 0xFFFFFFBA     //"MOV ECX, offset" (B9)
    endloop
    return CustomMem[2] + 28
endfunction

private function FindTagTable takes integer func returns integer
    set CustomMem.address = func
    loop
        exitwhen CustomMem[0]/16777216 == 0xFFFFFFE9
        set CustomMem.address = CustomMem.address + 1
    endloop
    set func = CustomMem.address
    //First call in Mac is GetEIP, in Windows it is DecodeTags.
    //To my surprise, DecodeTags is inlined in Mac but not in Windows.
    if IsMac == 1 then
        loop
            set CustomMem.address = CustomMem.address + 1
            exitwhen CustomMem[1]*256/16777216 == 0xFFFFFF8C and CustomMem[1]/1073741824 == 0xFFFFFFFF   //MOV r32, [r32+offset]
        endloop
        return CustomMem[2] + func + 8
    endif
    set CustomMem.address = CustomMem[1] + func + 6  //begin searching at DecodeTags function
    loop
        exitwhen (CustomMem[0]*32/2097152 == 0xFFFFFD8C and (CustomMem[0]+1073741824)/1073741824 == 1) or CustomMem[0]/16777216 == 0xFFFFFFA2  //MOV r32, [address]
        set CustomMem.address = CustomMem.address + 1
    endloop
    return CustomMem[1]
endfunction

private function FindGameUI takes integer func returns integer
    set CustomMem.address = func
    if IsMac == 1 then
        loop
            exitwhen CustomMem[0]/16777216 == 0xFFFFFFE9
            set CustomMem.address = CustomMem.address + 1
        endloop
        set func = CustomMem.address
        loop
            set CustomMem.address = CustomMem.address + 1
            exitwhen CustomMem[1]*256/16777216 == 0xFFFFFF8C and CustomMem[1]/1073741824 == 0xFFFFFFFF  //MOV r32, [r32+offset]
        endloop
        return CustomMem[2] + func + 8
    endif
    loop
        exitwhen CustomMem[0]/65536 == 0xFFFFA365 and CustomMem[1] == 0
        set CustomMem.address = CustomMem.address + 1
    endloop
    if IsLegacy == 1 then
        loop
            set CustomMem.address = CustomMem.address + 1
            exitwhen CustomMem[1]/65536 == 0x3D83   //CMP [address], constant
        endloop
        return CustomMem[2]
    endif
    loop
        set CustomMem.address = CustomMem.address + 1
        exitwhen (CustomMem[1]-0x80000000)/16777216 == 33 or (CustomMem[1]*32/2097152 == 0xFFFFFD8C and (CustomMem[1]+1073741824)/1073741824 == 1)     //MOV r32, [address]
    endloop
    return CustomMem[2]
endfunction

private function Step2 takes integer i returns nothing
    local unit u = GetDummyUnit()
    call UnitAddAbility(u, 'AInv')
    set GameState = i
    set HandleTable.address = Memory[Memory[GameState/4+7]/4+103] - 0xBFFFFC
    set GameTypeSupported.address = Memory[GameState/4+12]+48
    set i = HandleTable[GetHandleId(u)*3]/4     //ConvertHandle(u)
    set CustomMem.address = Memory[Memory[i]/4+88+IsMac]-3
    loop
        exitwhen (CustomMem[0]-0x80000000)/33554432 == 52  //matches both CALL (0xE8) and JMP (0xE9)
        set CustomMem.address = CustomMem.address + 1
    endloop
    set ObjectTables[0] = FindDataTable(CustomMem[1] + CustomMem.address + 5, (IsLegacy == 0))/4    //GetUnitData is inlined on 1.27+, windows only.
    set CustomMem.address = Memory[Memory[Memory[i+126]/4]/4+16+IsMac]-3
    loop
        exitwhen CustomMem[0]/16777216 == 0xFFFFFFE9    //Skip first CALL
        set CustomMem.address = CustomMem.address + 1
    endloop
    loop
        set CustomMem.address = CustomMem.address + 1
        exitwhen CustomMem[1]/16777216 == 0xFFFFFFE9    //2nd CALL is GetAbilityData
    endloop
    set ObjectTables[1] = FindDataTable(CustomMem[2] + CustomMem.address + 9, false)/4  //GetAbilityData is never inlined
    set TagTable = FindTagTable(Memory[Memory[HandleTable[GetHandleId(Condition(function GetDummyUnit))*3]/4]/4+30+IsMac])/4
    set CustomMem.address = Memory[Memory[i]/4+101+IsMac]
    if IsMac == 1 then  //First call in Mac is GetEIP, so skip it
        loop
            exitwhen CustomMem[0]/16777216 == 0xFFFFFFE9   
            set CustomMem.address = CustomMem.address + 1
        endloop
    endif
    loop
        set CustomMem.address = CustomMem.address + 1
        exitwhen CustomMem[1]/16777216 == 0xFFFFFFE9    //2nd CALL is GetAbilityData
    endloop
    set pGameUI = FindGameUI(CustomMem[2] + CustomMem.address + 9)/4
    static if LIBRARY_Mouse then
        set MouseEnv.address = Memory[Memory[pGameUI]/4 + 239]+784
    endif
    static if not LIBRARY_DummyCaster then
        call RemoveUnit(u)
    endif
    set u = null
endfunction

private function Step1 takes integer p, integer i returns nothing
    set i = i/4
    loop
        exitwhen Memory[i] == BC.address and Memory[i+3] == 300000
        set i = i+1
    endloop
    set pJassContext = Memory[i-1]
    if Memory[pJassContext/4+17] == 300000 then
        set IsMac = 1
    else
        loop
            set i = i-1
            set pJassContext = Memory[i]
            exitwhen ModuloInteger(pJassContext*65536/65536, 0x28B0) == 0x88 and pJassContext>65536 and Memory[pJassContext/4+17] == 300000
        endloop
    endif
    set StringHandles = Memory[pJassContext/4+2589]/4+2
    if p != GetPlayers() then
        set BC(33).value = 623    //SetPlayers
        return
    endif
    set IsLegacy = 1
    if IsMac == 1 then
        set BC(28).value = 0x13000000
        set BC(33).value = 631    //SetStartLocPrio
        return
    endif
    set BC[33] = 642        //SetGamePlacement
    call GetGamePlacement()
    return
endfunction

private function DebugBreak_Id takes nothing returns nothing
    call DebugBreak(0)
endfunction

//# +nosemanticerror
private function InitBytecode takes nothing returns nothing
    set BC[0] = 0x15000000
    set BC[1] = 652         //GetPlayers
    set BC[2] = 0x13000000
    set BC[4] = 0x15000000
    set BC[5] = 623         //SetPlayers
    set BC[6] = 0x17000000
    set BC[8] = 0x13000000
    set BC[10] = 0x15000000
    set BC[11] = 595        //R2I
    set BC[12] = 0x13000000
    set BC[14] = 0x13000000
    set BC[16] = 0x15000000
    set BC[17] = Memory[C2I(function DebugBreak_Id)/4+5]
    set BC[18] = 0x17000000
    set BC[20] = 0x13000000
    set BC[22] = 0x15000000
    set BC[23] = 595        //R2I
    set BC[24] = 0x13000000
    set BC[26] = 0x16000000
    set BC[27] = Memory[C2I(function Step1)/4-1]
    set BC[30] = 0x13000000
    set BC[32] = 0x15000000
    set BC[34] = 0x0D010000
    set BC[36] = 0x17000000
    set BC[38] = 0x13000000
    set BC[40] = 0x15000000
    set BC[41] = 595        //R2I
    set BC[42] = 0x21010100
    set BC[44] = 0x17010000
    set BC[46] = 0x13010000
    set BC[48] = 0x0D010000
    set BC[50] = 0x15000000
    set BC[51] = 595        //R2I
    set BC[52] = 0x20000100
    set BC[54] = 0x13000000
    set BC[56] = 0x16000000
    set BC[57] = Memory[C2I(function Step2)/4-1]
    set BC[58] = 0x27000000
endfunction

private function Init takes nothing returns nothing
    call InitBytecode()
    call TriggerRegisterGameEvent(BC.trigger, EVENT_GAME_LOADED)
    call TriggerEvaluate(BC.trigger)
endfunction

endlibrary


JASS:
library Mouse requires Memory, Version

struct MouseEnv extends array  //Library Version will initialize the struct.
    implement MemArrayReal
endstruct

function GetMouseX takes nothing returns real
    return MouseEnv(0).value
endfunction

function GetMouseY takes nothing returns real
    return MouseEnv(1).value
endfunction

function GetMouseZ takes nothing returns real
    return MouseEnv(2).value
endfunction

endlibrary


JASS:
/* Provides the binary AND, OR and XOR operations, using natives
   and memory reading. This should be faster than all alternatives
   that use math operations and/or lookup tables */

library Bitwise requires Memory, Version

globals
    constant gametype GAME_TYPE_ALL = ConvertGameType(0xFFFFFFFF)
endglobals

function GetGameTypeSupported takes nothing returns integer
    return GameTypeSupported[0]
endfunction

function BitwiseNot takes integer i returns integer
    return 0xFFFFFFFF - i
endfunction

function BitwiseOr takes integer a, integer b returns integer
    call SetGameTypeSupported(GAME_TYPE_ALL, false)
    call SetGameTypeSupported(ConvertGameType(a), true)
    call SetGameTypeSupported(ConvertGameType(b), true)
    return GetGameTypeSupported()
endfunction

function BitwiseAnd takes integer a, integer b returns integer
    call SetGameTypeSupported(GAME_TYPE_ALL, false)
    call SetGameTypeSupported(ConvertGameType(a), true)
    call SetGameTypeSupported(ConvertGameType(BitwiseNot(b)), false)
    return GetGameTypeSupported()
endfunction

function BitwiseXor takes integer a, integer b returns integer
    call SetGameTypeSupported(GAME_TYPE_ALL, false)
    call SetGameTypeSupported(ConvertGameType(a), true)
    call SetGameTypeSupported(ConvertGameType(b), true)
    return GetGameTypeSupported()*2 - a - b
endfunction

endlibrary

JASS:
library ObjectData initializer onInit requires Memory, Version, Bitwise, Table

globals
    constant integer UNIT_OBJECT_DATA = 0
    constant integer ABILITY_OBJECT_DATA = 1

    private integer array H
    private Table array tb
endglobals

function IntegerHash takes integer i returns integer
    local integer a = 0x7FED7FED
    local integer b = 0xEEEEEEEE
    local integer byte
    loop
        set byte = i*16777216/16777216
        set a = BitwiseXor(a+b,H[byte])
        set i = i/256
        exitwhen i == 0
        set b = b*32+a+b+byte+3
    endloop
    return a
endfunction

/* Searches the provided data table for the object's data structure.
   This function will return 0 if the object has not been loaded yet!
   You can force an object to be loaded by calling GetObjectName. It's
   up to you to decide if you're going to call it or not. */
function FindObjectData takes integer pData, integer rawcode returns integer
    local integer hash = IntegerHash(rawcode)
    local integer list = Memory[pData]/4 + ModuloInteger(hash, Memory[pData+2]+1)*3
    local integer i = Memory[list+2]/4
    loop
        exitwhen i <= 0
        if Memory[i] == hash and Memory[i+5] == rawcode then
            return i
        endif
        set i = Memory[Memory[list]/4+i+1]/4
    endloop
    return 0
endfunction

/*Object data tables are actually... hashtables! The computational work to
  retrieve a object's data is exactly the same done by hashtable natives.
  But doing that work under native code is much faster than doing it in
  JASS. So caching the entries is certainly a good idea.*/
function GetObjectData takes integer objType, integer rawcode returns integer
    /*Should I cache everything into a single Table, or is it better to keep
    separete Tables for each object type? */
    local integer data = tb[objType][rawcode]
    if data == 0 then
        set data = FindObjectData(ObjectTables[objType], rawcode)
        set tb[objType][rawcode] = data
    endif
    return data
endfunction

//Cache becomes invalid after saved game is loaded
private function OnGameLoaded takes nothing returns boolean
    local integer i = 0
    loop
        call tb[i].flush()
        set i = i+1
        exitwhen ObjectTables[i] == 0
    endloop
    return false
endfunction

private function onInit takes nothing returns nothing
    local integer i = 0
    local trigger t = CreateTrigger()
    call TriggerRegisterGameEvent(t, EVENT_GAME_LOADED)
    call TriggerAddCondition(t, Condition(function OnGameLoaded))
    loop
        set tb[i] = Table.create()
        set i = i+1
        exitwhen ObjectTables[i] == 0
    endloop
    set H[1]=0xA22E726E
    set H[2]=0xD43D94C0
    set H[3]=0x6DE064C7
    set H[4]=0xFE8D4B2F
    set H[5]=0x345A287E
    set H[6]=0x13941BCF
    set H[7]=0xD822114D
    set H[8]=0xA79E1270
    set H[9]=0xFB2D4CF9
    set H[10]=0xCB25DDAE
    set H[11]=0x7B5E64D5
    set H[12]=0x88544672
    set H[13]=0xF201BF3F
    set H[14]=0x677CAF6E
    set H[15]=0x34502020
    set H[16]=0x5DD18D92
    set H[18]=0x320F2252
    set H[19]=0xCBB1F259
    set H[20]=0x5C5ED8C1
    set H[21]=0x922BB610
    set H[22]=0x7165A961
    set H[23]=0x35F39EDF
    set H[24]=0x056FA002
    set H[25]=0x58FEDA8B
    set H[26]=0x28F76B40
    set H[27]=0xD92FF267
    set H[28]=0xE625D404
    set H[29]=0x4FD34CD1
    set H[30]=0xC54E3D00
    set H[31]=0x9221ADB2
    set H[32]=0x2BC26B40
    set H[33]=0xCDF0DDAE
    set H[35]=0x99A2D007
    set H[36]=0x2A4FB66F
    set H[37]=0x601C93BE
    set H[38]=0x3F56870F
    set H[39]=0x03E47C8D
    set H[40]=0xD3607DB0
    set H[41]=0x26EFB839
    set H[42]=0xF6E848EE
    set H[43]=0xA720D015
    set H[44]=0xB416B1B2
    set H[45]=0x1DC42A7F
    set H[46]=0x933F1AAE
    set H[47]=0x60128B60
    set H[48]=0x921F9B39
    set H[49]=0x344E0DA7
    set H[50]=0x665D2FF9
    set H[52]=0x90ACE668
    set H[53]=0xC679C3B7
    set H[54]=0xA5B3B708
    set H[55]=0x6A41AC86
    set H[56]=0x39BDADA9
    set H[57]=0x8D4CE832
    set H[58]=0x5D4578E7
    set H[59]=0x0D7E000E
    set H[60]=0x1A73E1AB
    set H[61]=0x84215A78
    set H[62]=0xF99C4AA7
    set H[63]=0xC66FBB59
    set H[64]=0x0172B4D1
    set H[65]=0xA3A1273F
    set H[66]=0xD5B04991
    set H[67]=0x6F531998
    set H[69]=0x35CCDD4F
    set H[70]=0x1506D0A0
    set H[71]=0xD994C61E
    set H[72]=0xA910C741
    set H[73]=0xFCA001CA
    set H[74]=0xCC98927F
    set H[75]=0x7CD119A6
    set H[76]=0x89C6FB43
    set H[77]=0xF3747410
    set H[78]=0x68EF643F
    set H[79]=0x35C2D4F1
    set H[80]=0xCBA5D782
    set H[81]=0x6DD449F0
    set H[82]=0x9FE36C42
    set H[83]=0x39863C49
    set H[84]=0xCA3322B1
    set H[86]=0xDF39F351
    set H[87]=0xA3C7E8CF
    set H[88]=0x7343E9F2
    set H[89]=0xC6D3247B
    set H[90]=0x96CBB530
    set H[91]=0x47043C57
    set H[92]=0x53FA1DF4
    set H[93]=0xBDA796C1
    set H[94]=0x332286F0
    set H[95]=0xFFF5F7A2
    set H[96]=0xEC6BE431
    set H[97]=0x8E9A569F
    set H[98]=0xC0A978F1
    set H[99]=0x5A4C48F8
    set H[100]=0xEAF92F60
    set H[101]=0x20C60CAF
    set H[103]=0xC48DF57E
    set H[104]=0x9409F6A1
    set H[105]=0xE799312A
    set H[106]=0xB791C1DF
    set H[107]=0x67CA4906
    set H[108]=0x74C02AA3
    set H[109]=0xDE6DA370
    set H[110]=0x53E8939F
    set H[111]=0x20BC0451
    set H[112]=0x27DDEEB3
    set H[113]=0xCA0C6121
    set H[114]=0xFC1B8373
    set H[115]=0x95BE537A
    set H[116]=0x266B39E2
    set H[117]=0x5C381731
    set H[118]=0x3B720A82
    set H[120]=0xCF7C0123
    set H[121]=0x230B3BAC
    set H[122]=0xF303CC61
    set H[123]=0xA33C5388
    set H[124]=0xB0323525
    set H[125]=0x19DFADF2
    set H[126]=0x8F5A9E21
    set H[127]=0x5C2E0ED3
    set H[128]=0x5861ED90
    set H[129]=0xFA905FFE
    set H[130]=0x2C9F8250
    set H[131]=0xC6425257
    set H[132]=0x56EF38BF
    set H[133]=0x8CBC160E
    set H[134]=0x6BF6095F
    set H[135]=0x3083FEDD
    set H[137]=0x538F3A89
    set H[138]=0x2387CB3E
    set H[139]=0xD3C05265
    set H[140]=0xE0B63402
    set H[141]=0x4A63ACCF
    set H[142]=0xBFDE9CFE
    set H[143]=0x8CB20DB0
    set H[144]=0x04D2B307
    set H[145]=0xA7012575
    set H[146]=0xD91047C7
    set H[147]=0x72B317CE
    set H[148]=0x035FFE36
    set H[149]=0x392CDB85
    set H[150]=0x1866CED6
    set H[151]=0xDCF4C454
    set H[152]=0xAC70C577
    set H[154]=0xCFF890B5
    set H[155]=0x803117DC
    set H[156]=0x8D26F979
    set H[157]=0xF6D47246
    set H[158]=0x6C4F6275
    set H[159]=0x3922D327
    set H[160]=0x34DA2252
    set H[161]=0xD70894C0
    set H[162]=0x0917B712
    set H[163]=0xA2BA8719
    set H[164]=0x33676D81
    set H[165]=0x69344AD0
    set H[166]=0x486E3E21
    set H[167]=0x0CFC339F
    set H[168]=0xDC7834C2
    set H[169]=0x30076F4B
    set H[171]=0xB0388727
    set H[172]=0xBD2E68C4
    set H[173]=0x26DBE191
    set H[174]=0x9C56D1C0
    set H[175]=0x692A4272
    set H[176]=0x84A19B2B
    set H[177]=0x26D00D99
    set H[178]=0x58DF2FEB
    set H[179]=0xF281FFF2
    set H[180]=0x832EE65A
    set H[181]=0xB8FBC3A9
    set H[182]=0x9835B6FA
    set H[183]=0x5CC3AC78
    set H[184]=0x2C3FAD9B
    set H[185]=0x7FCEE824
    set H[186]=0x4FC778D9
    set H[188]=0x0CF5E19D
    set H[189]=0x76A35A6A
    set H[190]=0xEC1E4A99
    set H[191]=0xB8F1BB4B
    set H[192]=0x77ABB98E
    set H[193]=0x19DA2BFC
    set H[194]=0x4BE94E4E
    set H[195]=0xE58C1E55
    set H[196]=0x763904BD
    set H[197]=0xAC05E20C
    set H[198]=0x8B3FD55D
    set H[199]=0x4FCDCADB
    set H[200]=0x1F49CBFE
    set H[201]=0x72D90687
    set H[202]=0x42D1973C
    set H[203]=0xF30A1E63
    set H[205]=0x69AD78CD
    set H[206]=0xDF2868FC
    set H[207]=0xABFBD9AE
    set H[208]=0x0DFE40C1
    set H[209]=0xB02CB32F
    set H[210]=0xE23BD581
    set H[211]=0x7BDEA588
    set H[212]=0x0C8B8BF0
    set H[213]=0x4258693F
    set H[214]=0x21925C90
    set H[215]=0xE620520E
    set H[216]=0xB59C5331
    set H[217]=0x092B8DBA
    set H[218]=0xD9241E6F
    set H[219]=0x895CA596
    set H[220]=0x96528733
    set H[222]=0x757AF02F
    set H[223]=0x424E60E1
    set H[224]=0x98835092
    set H[225]=0x3AB1C300
    set H[226]=0x6CC0E552
    set H[227]=0x0663B559
    set H[228]=0x97109BC1
    set H[229]=0xCCDD7910
    set H[230]=0xAC176C61
    set H[231]=0x70A561DF
    set H[232]=0x40216302
    set H[233]=0x93B09D8B
    set H[234]=0x63A92E40
    set H[235]=0x13E1B567
    set H[236]=0x20D79704
    set H[237]=0x8A850FD1
    set H[239]=0xCCD370B2
    set H[240]=0xCBAFDFE0
    set H[241]=0x6DDE524E
    set H[242]=0x9FED74A0
    set H[243]=0x399044A7
    set H[244]=0xCA3D2B0F
    set H[245]=0x000A085E
    set H[246]=0xDF43FBAF
    set H[247]=0xA3D1F12D
    set H[248]=0x734DF250
    set H[249]=0xC6DD2CD9
    set H[250]=0x96D5BD8E
    set H[251]=0x470E44B5
    set H[252]=0x54042652
    set H[253]=0xBDB19F1F
    set H[254]=0x332C8F4E
endfunction

endlibrary


JASS:
library Utils requires Memory, Version, ObjectData

function ConvertHandle takes handle h returns integer
    return HandleTable[GetHandleId(h)*3]
endfunction

function GetStringAddress takes string s returns integer
    return Memory[Memory[Memory[StringHandles]/4+SH2I(s)*4+2]/4+7]
endfunction

function DecodeTags takes integer a, integer b returns integer
    local integer i = Memory[TagTable]/4
    if a < 0 then
        set i = i+8
        set a = a-0x80000000
    endif
    if a < Memory[i+7] then
        set i = Memory[i+3]/4+a*2
        if Memory[i] == 0xFFFFFFFE then
            set i = Memory[i+1]/4
            if Memory[i+6] == b then
                return i
            endif
        endif
    endif
    return 0
endfunction

function GetAgentFromRef takes integer ref returns integer
    local integer i = Memory[ref+1]
    set ref = Memory[ref]
    if ref !=0xFFFFFFFF and i !=0xFFFFFFFF then
        set i = DecodeTags(ref, i)
        if i != 0 and Memory[i+8] == 0 then
            return Memory[i+21]
        endif
    endif
    return 0
endfunction


function GetUnitAbility takes unit u, integer id returns integer
    local integer i = GetAgentFromRef(ConvertHandle(u)/4+119)
    loop
        exitwhen i == 0 or Memory[i/4+13] == id
        set i = GetAgentFromRef(i/4+9)
    endloop
    return i
endfunction

function GetUnitFlags takes unit u returns integer
    return Memory[ConvertHandle(u)/4+23]
endfunction

function IsUnitStunned takes unit u returns boolean
    return Memory[ConvertHandle(u)/4+102] > 0
endfunction

function GetAgentType takes agent a returns integer
    return Memory1[Memory[Memory[ConvertHandle(a)/4]/4+7]/4]
endfunction

function GetUnitArmor takes unit u returns real
    return RMemory[ConvertHandle(u)/4+56]
endfunction

function GetHeroPrimaryAttribute takes unit u returns integer
    return Memory[Memory[ConvertHandle(u)/4+124]/4+51]
endfunction

function GetAbilityMaxLevel takes integer abil returns integer
    return Memory[GetObjectData(ABILITY_OBJECT_DATA, abil)+20]
endfunction

function GetAbilityManaCost takes integer abil, integer level returns integer
    return Memory[Memory[GetObjectData(ABILITY_OBJECT_DATA, abil)+21]/4-22+level*26]
endfunction

function GetAbilityCooldown takes integer abil, integer level returns real
    return RMemory[Memory[GetObjectData(ABILITY_OBJECT_DATA, abil)+21]/4-21+level*26]
endfunction

function GetAbilityCurrentCooldown takes integer address returns real
    local integer pData = Memory[address/4+55]/4
    if pData != 0 then
        return RMemory[pData+1] - RMemory[Memory[pData+3]/4+16]
    endif
    return .0
endfunction

endlibrary

I am making all of this for free, but if you feel like making a donation for my work, it would really please my heart ♥
If you want to make a donation, please send a PM to me.


03/10/2017

-Mouse functions are back! Now present in a separate library, tested and working in all versions of WC3 through automatic detection! Also optimized so that getting the mouse coordinates is now a simple array lookup.

-Implemented automatic detection of Ability object table. Now functions such as GetAbilityManaCost and GetAbilityCooldown are working again.

-Introduced function GetUnitAbility which searches and returns an ability object from a unit's current abilities. Notice that this function returns an object address, and currently the only thing you can do with it is call GetAbilityCurrentCooldown. Also you should only use that address as soon as you get it, you are not supposed to save it anywhere as it may become invalid later.

-Fixed a bug where Step2 was not being executed when the map was compiled in Debug Mode or with optimization disabled.

29/09/2017

-Introduced new module: MemArray. This module provides a custom array for memory access, you just implement it in your struct and set the address, and then your struct becomes an array-like object where Struct[0] returns the memory at the specified address, and so on. The library Memory also exposes an array named "CustomMem" that implements this module, you can use it as well.

-Module "Bytecode" now exposes an "id" member, that returns the string id of the bytecode array. This makes it easier for you to write self-modifying bytecode, you no longer need to worry about obtaining the variable id, just use the provided id in the GETARRAY and SETARRAY instructions.

-ConvertHandle function from library Utils now uses the new MemArray module. Handle conversion is now a simple array lookup HandleTable[GetHandleId(h)*3] instead of a bunch of memory reads. The same principle was also used in the Bitwise library, to speed up bitwise operations.

-Library Version now detects UnitData table and AgentTags table. Detection of other data tables and Mouse functions shall be implemented soon.

-Reworked library ObjectData to work with new code, also it now caches the found values in a Table object (after all, ObjectTables are hashtables internally, so it's faster to delegate this work to native code instead of doing it in pure JASS)

08/09/2017

-Introduced new functionality for bytecode arrays! Now you can have your bytecode array get automatically assigned to a trigger! Just write the line static constant boolean hasTrigger = true before implementing the Bytecode module, and it will automatically create a trigger and assign your bytecode array to it. Then you can use the .trigger member to access that trigger. You can register events to it, or call it directly with TriggerEvaluate

-Implemented support for loading saved games! When saved games are loaded, everything is located in different addresses. Now the Bytecode module will automatically obtain the new addresses of all bytecode arrays, and update the corresponding triggers. Also Version library will redetect game addresses after the game is loaded.

-Minor improvements to the code, Memory library now uses a different method to unlock memory access.

15/08/2017

-Introduced experimental detection of game addresses. Currently detects the address of Jass Context in all versions of WC3 already released, also expected to work in future versions. Detection of main game class shall be included in the future.

-Introduced new syntax for bytecode execution! Now it's much easier to run bytecode from an array:
All you need to do is declare a struct that extends array, and then implement the Bytecode module:
JASS:
struct MyBCArray extends array
   implement Bytecode
endstruct
Now you can use that struct normally as a regular array:
JASS:
function InitBytecode takes nothing returns nothing
    set MyBCArray[0] = <my>
    set MyBCArray[1] = <bytecode>
    set MyBCArray[2] = <here>
endfunction
And you can easily use the member MyBCArray.address to obtain the address of that array. Then you just need to pass that address to I2C and execute it.

14/04/2017

-Mouse functions added! Credits to DracoL1ch for finding the offsets.
-Typecast library slightly modified to allow functions C2I and I2C to be inlined.
-Memory library updated with better code, memory reading is now a simple array lookup instead of a trigger evaluation
-Added array RMemory to allow reading the memory as a real without the need for typecasting
-Version library updated with offsets for patches 1.27b and 1.28
-Changed implementation of XOR in Bitwise library, XOR(a,b) is now implemented with OR(a,b)*2 - a - b

26/05/2016

-Typecast library updated with typecasts of all basic types to and from integer.
-Initial release of libraries Version, Bitwise, ObjectData and Utils

16/05/2016

-Initial release of libraries Memory and Typecast
 
Last edited:
Level 29
Joined
Jul 29, 2007
Messages
5,174
This was previously done with the return bug for illegal type casting.
While it's awesome and allows to do many cool things (practically anything), it also allows some nasty people to insert lovely unwanted things into your computer, and therefore Blizzard "fixed" it.
Whether an exploit like this is good or bad, who knows.
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
This was previously done with the return bug for illegal type casting.
While it's awesome and allows to do many cool things (practically anything), it also allows some nasty people to insert lovely unwanted things into your computer, and therefore Blizzard "fixed" it.
Whether an exploit like this is good or bad, who knows.

C++ created more viruses than any other language. Im wonder why does stackoverflow still not blacklisted.
blizz could easily give us a whole access to everything inside wc3 with direct code execution, so we won't need this hacks anymore. but they didnt
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
Can't wait so see what can you do with that.

lOD1.png

SLWl.png

for instance
getAbilityHotkey / buttonpos / etc as well
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Holy shit, access to object data would be AMAZING!

Here's the catch though: there is no way this makes it past the next 1.27 patch, since it's pretty easy to fix by just updating the compiler.


However, I hope you suggested to blizzard that they give us direct access to object data via a new native in the future, since we now know that (and how) it's possible.
I have so many ideas and uses for this it's not even funny.

JASS:
function Init takes nothing returns nothing
     set teamScore = C2I(function Nothing)-8
     call TriggerAddCondition(MemReader, Condition(I2C(1648+C2I(function MeleeTournamentFinishNowRuleA))))
endfunction
What the actual fuck is happening here...?!


Anyway... you wanted requests, so here is mine: is it possible to directly manipulate individual unit stats in the memory without crashes? I'm not talking about editing the values in the object editor, but stats of individual unit instances directly at runtime?
For example, if I wanted to change the displayed level of a unit for Neutral Hostile (not a hero!) or alter the white armor value of a unit at runtime?
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
Anyway... you wanted requests, so here is mine: is it possible to directly manipulate individual unit stats in the memory without crashes? I'm not talking about editing the values in the object editor, but stats of individual unit instances directly at runtime?
For example, if I wanted to change the displayed level of a unit for Neutral Hostile (not a hero!) or alter the white armor value of a unit at runtime?

here's only READable hack. you can't write with it.
and who needs any patch when you can create WC3.5 right now? I have no idea what you could trade this for. Nobody needs official bnet today.
 

Chaosy

Tutorial Reviewer
Level 40
Joined
Jun 9, 2011
Messages
13,182
You'll need a graphic update to call it wc 3.5, code features is not enough.
When this gets released (the functions mentioned) it's better than any patch Blizz could come up with.

With the exception of HD models which I honestly want more.

Regardless, I do indeed this will get fixed by blizz in the next patch, whenever it comes out.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
here's only READable hack. you can't write with it.
and who needs any patch when you can create WC3.5 right now? I have no idea what you could trade this for. Nobody needs official bnet today.
I certainly don't want to play a game in which mapmakers have the possibility to compromise my system.
So a fix is necessary, no matter what your oppinion on bnet is.

If you can help with that by finding out how it works, all power to you. If you can show blizzard how to read and manipulate game data in the process (so that we can get a safe alternative); even better.
But don't suggest we won't ever need patches anymore because this is so powerful that we can possibly do almost everything with that. That is absolutely the wrong message to send.
 
Level 33
Joined
Apr 24, 2012
Messages
5,113
Reading Object Data should be enough, that fulfills and solves almost everything that needs to be written e.g. writing an Attack Indexer, where you will need to index the attack either via dealing 0 damage to unit then deal the base damage the unit has OR detecting if the unit has finished launching the attack after the attack animations.
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
I certainly don't want to play a game in which mapmakers have the possibility to compromise my system.
So a fix is necessary, no matter what your oppinion on bnet is.

If you can help with that by finding out how it works, all power to you. If you can show blizzard how to read and manipulate game data in the process; even better.
But don't suggest we won't ever need patches anymore because this is so powerful that we can possibly do almost everything with that. That is absolutely the wrong message to send.

will see next time. I'd call to stay on 26 and watch your downloads instead of being restricted with nonsense limits. time will show who would win here.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Reading Object Data should be enough, that fulfills and solves almost everything that needs to be written e.g. writing an Attack Indexer, where you will need to index the attack either via dealing 0 damage to unit then deal the base damage the unit has OR detecting if the unit has finished launching the attack after the attack animations.
Reading object data is great. But without the possibility to write them, we still can't do stuff like changing the base armor or attack of a unit.

will see next time. I'd call to stay on 26 and watch your downloads instead of being restricted with nonsense limits. time will show who would win here.
The classic games team seems to be very concerned about breaking backwards compatibility. And I honestly wouldn't care about a fix to something nobody ever used so far (except for those who discovered it). We (as in the mapping community) didn't get the lollipop yet, so there is no harm in taking it away.

Instead of being super conservative about patches, how about you invest your time and energy into suggesting a way for blizzard to fix the bad without removing the goods it can do? Worked for the preload fix too; at least the thing they suggested as a possible fix does not break all the good things that come with it.
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
Instead of being super conservative about patches, how about you invest your time and energy into suggesting a way for blizzard to fix the bad without removing the goods it can do? Worked for the preload fix too; at least the thing they suggested as a possible fix does not break all the good things that come with it.

I told my point. They could give us ability to write and read any memory within WC3 itself. Restrict it with current game's memory, i don't care. Add ability to connect with http (since it cannot be done from sandbox). Aaaand guess who cares about those suggestions? Even if some programmer would like to help us, there's deadlines, outdated code and top-managers with different KPI measures
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
I told my point. They could give us ability to write and read any memory within WC3 itself. Restrict it with current game's memory, i don't care. Add ability to connect with http (since it cannot be done from sandbox). Aaaand guess who cares about those suggestions? Even if some programmer would like to help us, there's deadlines, outdated code and top-managers with different KPI measures
You are the guy with the power and the required information, atm. I highly doubt anyone else knows how this works.
What we know for sure, however, is that blizzard will release new patches soon AND that blizzard is in contact with the Hive staff.

So, there are basicly two options for you:

1) Be open to the community, accept that blizzard will want to fix that and suggest ways to do that in a way that benefits everyone (including safe alternatives)

2) Keep it a secret and share it with only a selected circle that you know will cause no harm.


... and you basicly opted out of 2) already by posting it in a public forum.
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
You are the guy with the power and the required information, atm. I highly doubt anyone else knows how this works.
What we know for sure, however, is that blizzard will release new patches soon AND that blizzard is in contact with the Hive staff.

So, there are basicly two options for you:

1) Be open to the community, accept that blizzard will want to fix that and suggest ways to do that in a way that benefits everyone (including safe alternatives)

2) Keep it a secret and share it with only a selected circle that you know will cause no harm.


... and you basicly opted out of 2) already by posting it in a public forum.

you can't hide anything which is basically wide open within simple mpq. even to hide it, you have to make sure WC3 will read it first. and therefore anyone else can do the same.

Leandro tried to contact Blizz. They didn't look like they gonna take a journey. Last time I asked they didn't even bother to reply Leandro on the message containing info about reading exploit.

Surely I have little hope for something like GetHandleId for 1.24. But guess what, here we have literally everything. I won't believe Blizz could give us as much as we currently have. Basically we need hooks for everything, everything is what WC3 built on. Thats kinda overkill even if they wanna be friendly enough, and we didn't really felt it yet.
 

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
537
Atleast for objectdata there were always ways to get the values using extra tools. The real value would be ofcourse writing object data at runtime (but i bet there are many other cute values to read :).
But we all know what that would imply, sooo:

@zwiebel you seem to be in contact with blizz so why don't you create some communication bridges.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
you can't hide anything which is basically wide open within simple mpq. even to hide it, you have to make sure WC3 will read it first. and therefore anyone else can do the same.

Leandro tried to contact Blizz. They didn't look like they gonna take a journey. Last time I asked they didn't even bother to reply Leandro on the message containing info about reading exploit.

Surely I have little hope for something like GetHandleId for 1.24. But guess what, here we have literally everything. I won't believe Blizz could give us as much as we currently have. Basically we need hooks for everything, everything is what WC3 built on. Thats kinda overkill even if they wanna be friendly enough, and we didn't really felt it yet.
You don't quite understood my point.

My point is very similar to yours: maybe we don't need this fixed entirely. Maybe we can find a way that prevents users from doing harm with it without removing the opportunity to do good in the process.
And this is where my point is different from yours:
You don't want Blizz to touch that. I do want Blizz to touch that; but in a way that won't remove the good it can do.

You are the one with the details here. You have the knowledge to suggest a way to restrict access to memory outside of a WC3 game session without restricting access to memory inside a running game.

That's all I'm asking for.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Awesome find.
I know you could mess with local/global variables to typecast before, without any functions calls.
Good to know, that there is still a way with functions and variables.

Sadly (or not), as said, it will probably fixed in a new patch.
I hope Blizzard will not fuck off all the features.
 

There is a map on another site which allows write access and even opens the command prompt (which proves anything is possible). I have tested it myself.

Of course it was for a previous version but I think it can be ported.

As always, don't play random maps, or at the very least scan the war3map.j

EDIT:


JASS:
scope Test initializer Init
    
    globals
        private force F=CreateForce()
    endglobals

    private function Func1 takes nothing returns nothing
        call BJDebugMsg("Func1")
    endfunction
    private function Func2 takes nothing returns nothing
        call BJDebugMsg("Func2")
    endfunction
    private function Func3 takes nothing returns nothing
        call BJDebugMsg("Func3")
    endfunction

    private function OnGameStart takes nothing returns nothing
        local CodeArray funcs = CodeArray.create()

        set funcs[0] = function Func1
        set funcs[1] = function Func2
        set funcs[2] = function Func3

        call ForForce(F, funcs[1])
    endfunction

    private function Init takes nothing returns nothing
        call ForceAddPlayer(f, Player(PLAYER_NEUTRAL_PASSIVE))
        call TimerStart(CreateTimer(), 0, false, function OnGameStart)
    endfunction

endscope

JASS:
library CodeArrayLib requires Typecast
    
    globals
        private hashtable Table
    endglobals

    struct CodeArray

        method operator[]= takes integer index, code func returns nothing
            call SaveInteger(Table, this, index, C2I(func))
        endmethod

        method operator[] takes integer index returns code
            return I2C(LoadInteger(Table, this, index))
        endmethod

        static method onInit takes nothing returns nothing
            set Table=InitHashtable()
        endmethod
    endstruct

endlibrary

 
Last edited:
Level 29
Joined
Jul 29, 2007
Messages
5,174
C++ created more viruses than any other language. Im wonder why does stackoverflow still not blacklisted.
blizz could easily give us a whole access to everything inside wc3 with direct code execution, so we won't need this hacks anymore. but they didnt

You download a compiled program at your own risk. I don't think Blizzard intended each custom map to be a security risk. :)
I am not saying this is bad, I actually really like these sort of hacks, but it will definitely get patched.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
There is a map on another site which allows write access and even opens the command prompt (which proves anything is possible). I have tested it myself.

Of course it was for a previous version but I think it can be ported.

As always, don't play random maps, or at the very least scan the war3map.j

EDIT:


JASS:
scope Test initializer Init
    
    globals
        private force F=CreateForce()
    endglobals

    private function Func1 takes nothing returns nothing
        call BJDebugMsg("Func1")
    endfunction
    private function Func2 takes nothing returns nothing
        call BJDebugMsg("Func2")
    endfunction
    private function Func3 takes nothing returns nothing
        call BJDebugMsg("Func3")
    endfunction

    private function OnGameStart takes nothing returns nothing
        local CodeArray funcs = CodeArray.create()

        set funcs[0] = function Func1
        set funcs[1] = function Func2
        set funcs[2] = function Func3

        call ForForce(F, funcs[1])
    endfunction

    private function Init takes nothing returns nothing
        call ForceAddPlayer(f, Player(PLAYER_NEUTRAL_PASSIVE))
        call TimerStart(CreateTimer(), 0, false, function OnGameStart)
    endfunction

endscope

JASS:
library CodeArrayLib requires Typecast
    
    globals
        private hashtable Table
    endglobals

    struct CodeArray

        method operator[]= takes integer index, code func returns nothing
            call SaveInteger(Table, this, index, C2I(func))
        endmethod

        method operator[] takes integer index returns code
            return I2C(LoadInteger(Table, this, index))
        endmethod

        static method onInit takes nothing returns nothing
            set Table=InitHashtable()
        endmethod
    endstruct

endlibrary


Damn, that's beautiful. So basicly what TriggerExecute does, but a lot faster.
 
Level 5
Joined
Sep 6, 2010
Messages
91
I do not know why many users think that this will probably be patched. Remember that we just have the "Get" but not the "Set", so there would be no problems of virus or hacks in maps. :hohum:
Greetings...(leandrotp thanks for bringing back the "C2I, I2C" in a way that no one can use this to cause damage to maps the community of warcraft 3. :smile: )
 
I emailed the bug directly to Blizz over a month ago with leandropt's help. They received it and handed it off to QA. I imagine it will be patched, but hopefully we'll get something in exchange (part of the email was suggesting for an alternative, native way to do the same).

@fenix: They have implemented "set". leandropt only showcased the reading version in his post.

'Get' is mostly just syntactical sugar/convenience. 'Set' is where the power comes from.
 
Level 5
Joined
Sep 6, 2010
Messages
91
@fenix: They have implemented "set". leandropt only showcased the reading version in his post.

'Get' is mostly just syntactical sugar/convenience. 'Set' is where the power comes from.

I know that "set" is the forte of this process but also is the cause of many problems in previous patches and the reason that decided "Blizzard" take the option to remove this method since version 1.24 .. on.

I hope that "Blizzard" consider this, as an opportunity to improve your game and ultimately as Nestharus commented "Would Have Been very nice if this HAD still worked on latest patch ^_-" this could now be real and not otherwise.
Greetings... (For my part, I appreciate the work you're doing, and I hope that most users can give their opinions on this topic :cool:)
 
Level 9
Joined
Jul 30, 2012
Messages
156
My point is very similar to yours: maybe we don't need this fixed entirely. Maybe we can find a way that prevents users from doing harm with it without removing the opportunity to do good in the process.

Short story: I have already given Blizzard detailed instructions on how to fix the vulnerability in the VM itself (which is actually the easiest and most correct thing to do), without touching the parser. This would prevent us from tampering array variables (which is the real problem), while not removing the ability to typecast (which, by itself, gives read-only access)

Long story: They didn't do it before. Will they do it now?

Actually Blizzard should have done that years ago, at the time of patch 1.23. They should have opened the old exploit and figured how it works (like I did), and then patched the real vulnerability exploited by that code. But no, they simply deemed that "typecasting is unsafe" and decided to remove it completely.

And as we all know, since typecasting was used everywhere by that time, they had to give us something we could use in place, which is the Hashtable. But the truth is that Hashtables should never have existed at all, they could have simply modified a couple of lines to fix the vulnerability while still allowing us to use typecasting and gamecache.

But instead, they spent probably a lot of time and resources to develop this complex API, and this didn't solve the real problem, as I'm demonstrating here.

JASS:
function Init takes nothing returns nothing
     set teamScore = C2I(function Nothing)-8
     call TriggerAddCondition(MemReader, Condition(I2C(1648+C2I(function MeleeTournamentFinishNowRuleA))))
endfunction
What the actual fuck is happening here...?!

By skipping 1648 bytes from function MeleeTournamentFinishNowRuleA the execution will jump directly to this line:

set bestScore = teamScore[index]

A controlled array read is all that we need to read from memory. By pointing the variable teamScore to a hacked struct (which is produced from the bytecode of function Nothing) we obtain access to the entire address space of the process with that array. Then I just need to put the address in variable index, and the result will be in variable bestScore.

Then the execution of that function will proceed until this line:

exitwhen index == teamCount

There it will try to read variable teamCount, which is uninitialized, and this causes the VM to crash, thus returning the execution to our code.

However this method can't be used to write memory because all write operations in the JASS VM are type-safe! (Good job Blizzard!) This means that you can't write to the array teamScore, because it's not an array at all (it has been declared as a global integer)

However, through the use of bytecode, it's possible to tamper with a TRUE array variable, therefore unlocking the write access!


Dear Blizzard, if you are reading this, please don't remove the typecasting! All you need to do is to patch a single instruction in the VM, and we won't be able to tamper with arrays anymore! The typecasting itself is harmless, and provides many benefits to the community! Just look at all the power it provides to map makers! If you keep it as it is currently, and fix only the true vulnerability, everyone will be benefited!


@topic: I have posted some new snippets, check in the first post. And there's much more to come. (Blizzard, take a look what we can do with this power! Do you really want to remove it from the game completely?)
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
Damn, just took a look on the utilities API and got my pants wet (figuratively).

How would a function look that only gets the "white" armor not including green or red bonuses?

I didn't researched this question yet (it wasn't needed). Curently we have access only to the global value. It's unit's total armor, changing it alters unit's armor to fit new value. Set 8 armor for unit with 0+10 armor will turn his base armor to -2.

meanwhile
JASS:
//armor types:
// 0 - Light; 1 - Medium; 2 - Heavy; 3 - Fortified; 4 - Normal; 5 - Hero; 6 - Divine; 7 - unarmored; 
//rest seems to have Light properties
function GetUnitArmorType takes unit u returns integer
	return Memory[(ConvertHandle(u) + 0xE4)/4]
endfunction

getting unit's params kinda hard, since they are stored in different memory location than unit's handle. tricky enough for non-pro (like me)
 
Last edited:
Level 12
Joined
Mar 13, 2012
Messages
1,121
Didn't look too long on it yet, so don't bash me if it's obvious.

Memory returns the contents of memory at the address (i*4).

What's the use of this? Seems to make things complicated to me. Why not make the calculation directly in the Memory library?
 
Level 9
Joined
Jul 30, 2012
Messages
156
What's the use of this? Seems to make things complicated to me. Why not make the calculation directly in the Memory library?

Because in the near future I'm going to replace the Memory struct with a simple array called "Memory". So there won't be a "method operator" anymore, and the memory read operation will be a simple array lookup, which is many times faster.

But the index passed to an array must always be divided by 4, and that's why I am keeping this convention in the first release, so when I change the library, people won't have to modify their code.
 
Level 9
Joined
Aug 26, 2010
Messages
573
Because WC3 memory contains 32 bit values. When you read integer you get 4 bytes. And array index is byte offset. I don't think you will ever wont to read parts of 2 values so you have to use index pointing to start of value.
1 value = 4 bytes
10 values = 40 bytes
So 0 value starts from 0 and 11th starts from 40. So I variable starts from i*4.
 
Status
Not open for further replies.
Top