Home > Software design >  How do you properly overwrite instructions loaded in memory from an injected DLL?
How do you properly overwrite instructions loaded in memory from an injected DLL?

Time:06-22

I am writing a dynamic link library to be injected into a singleplayer game on Windows and serve as a "cinematic tool" (overwriting camera transforms, timescale, etc.):

Let's say that the base address for the game executable in the virtual memory space is 0x140000000 and there's an instruction at foo.exe 0x10D64B81 that I want to overwrite. The instruction is mulss xmm1,[rdi 0000027C] (F3 0F 59 8F 7C 02 00 00, 8 bytes), the modified code to be written starting at that address/offset is xorps xmm1,xmm1 / nop / nop / nop / nop / nop (0F 57 C9 90 90 90 90 90, 8 bytes).

I have the following implementation (simplified for demonstration purposes):

#include <Windows.h>

using byte = unsigned char;

class FunctionMod
{
public:
    FunctionMod(void* baseAddress, size_t offset, const byte* code, size_t length)
        : m_BaseAddress(baseAddress), m_Offset(offset), m_ModifiedCode(), m_Length(length)
    {
        m_ModifiedCode = new byte[m_Length];
        std::copy(code, &code[m_Length], m_ModifiedCode);
    }

    ~FunctionMod() noexcept
    {
        delete[] m_ModifiedCode;
    }

    void overwrite() const
    {
        for (size_t i = 0; i < m_Length; i  )
            *((byte*)m_BaseAddress   m_Offset   i) = m_ModifiedCode[i];
    }

private:
    void*  m_BaseAddress;
    size_t m_Offset;
    size_t m_Length;
    byte*  m_ModifiedCode;
};

...

DWORD64 functionOffset  = 0x10D64B81;
byte    functionCode[8] = { 0x0F, 0x57, 0xC9, 0x90, 0x90, 0x90, 0x90, 0x90 };

FunctionMod guiMod(
    (void*)GetModuleHandle(NULL),
    functionOffset,
    functionCode,
    sizeof(functionCode)
);

guiMod.overwrite();

This works perfectly fine with other offsets and instructions, but for some reason, this particular example consistently writes 0F **A2** C9 90 ... to the respective memory location, instead of 0F **57** C9 90 ..., translating to cpuid \ leave \ nop ... and thus crashing the process.

I'm losing my mind trying to figure out what I'm doing wrong. There doesn't seem to be a point where 0x57 could somehow become 0xA2. I have verified that the memory is copied correctly in the class constructor, printing out the bytes to a console instead of writing them to the actual memory also gives the expected result, the pointer arithmetic checks out as well. The only time the 0xA2 is written is when I dereference the pointer and write to the actual memory.

Anyone, please, any ideas?

EDIT: Overwriting the memory from a different process running as administrator with WriteProcessMemory works properly, but I "need" (it would be much less work) to do this from the dll.

CodePudding user response:

Pausing and resuming threads

Thanks to @CherryDT for pointing this out — suspending the other threads of the process before writing to memory and then resuming them did the trick.

The function whose instructions I'm overwriting is one that calculates and modifies HUD opacity. In hindsight, it seems kind of obvious that a function like this would be called very often (probably in the game loop, so at least once per every frame) and so my hypothesis is that one of the game threads probably got to the function while my thread was still writing to that memory region, therefore encountering invalid instructions and stopping execution.

I still don't know why the magical A2 byte appeared every time, but even when I tried writing different instructions or changed their order (which, strangely, wrote the bytes I wanted correctly), I still got a crash afterwards.

Hence the solution:

void FunctionMod::overwrite() const
{
    pauseThreads();

    for (size_t i = 0; i < m_Length; i  )
        *((byte*)m_BaseAddress   m_Offset   i) = m_ModifiedCode[i];
        
    flushInstructionCache(); // in my experience not necessary but better be safe than sorry
    resumeThreads();
}

where both pauseThreads and resumeThreads first get a list of thread ids using Toolhelp32 as decribed here (make sure to exclude the id of the calling thread, i.e. GetCurrentThreadId()) and then use OpenThread with the THREAD_SUSPEND_RESUME access flag to get the thread handles and finally SuspendThread and ResumeThread to pause and resume thread execution. Phew!

  • Related