Search code examples
c++pointersx86inline-assemblycheat-engine

INC opcode compiles to wrong address


I'm compiling the following code however it isn't working as expected.

Can someone explain why the following code doesn't work and how to correct it so it does?

DWORD data_location = 0x0100579C;
DWORD ret = 0x1002FFA;

void __declspec(naked) inc()
{
    // The following is what I'm trying to accomplish which works
    *(DWORD*)data_location = *(DWORD*)data_location + 1;

    __asm
    {   
        inc [data_location] //Should compile as FF 05 9C570001, instead compiles to the address containing the pointer to data_location
        // inc data_location also compiles to the same thing above

        jmp [ret]
    }
}

Solution

  • [data_location] is the same thing as data_location in MASM syntax. Square brackets are optional, not the extra level of indirection you need to deref a pointer from static storage.

    Remember that in C, data_location gives you the value from memory, and your C is then dereferencing that. But inline asm uses asm syntax.


    If you want it to assemble with the address hard-coded into the instruction, you need to make the address a preprocessor constant, not just a DWORD variable in static storage.

    #define data_location  0x0100579C
    #define ret_addr  0x1002FFA
    
    void __declspec(naked) inc()
    {
        //++*(DWORD*)data_location;
        //((void (*)(void))ret)();
    
        __asm
        {   
            add  dword ptr ds:[data_location], 1
             // add dword ptr ds:[0x0100579C], 1   // after C preprocessor
    
            mov  eax, ret_addr
            jmp  eax
        }
    }
    

    Apparently a ds: is necessary to make MASM/MSVC treat [0x12345] as a memory operand, not an immediate. But it also has the downside of actually emit a redundant ds prefix byte in the machine code.

    Obviously you could make this much more efficient by actually using
    ++*(DWORD*)data_location; and letting the compiler inline the add or inc instruction. Forcing a caller to actually call this stub function will just slow you down.

    add [mem], immediate is only 2 uops, vs. 3 for memory-destination inc on Intel CPUs. It only costs 1 extra byte of code-size.

    jmp [ret] with DWORD ret = ...; will work, but is an unfortunate choice. You don't really need to load the target address from static storage. Ideally you'd jmp 0x1002FFA and let the assembler calculate a relative offset to that absolute destination. But unfortunately MASM syntax and/or Windows .obj files don't support that.

    If you can use a tmp register, mov-immediate of the address into the register avoids needing any static data, potentially allowing the front-end to sort out a branch mispredict sooner. It's still an indirect branch, though.


    Also, if you ever actually call this function, remember that the caller will have pushed a return address which you leave on the stack, so this is like a tailcall.

    In fact, you could get the compiler to emit a jmp for you if you simply made a normal function call with no args at the end of a void function.