This blog post is a continuation of a series on Process Injection. To see all posts in the series, click here!

The accompanying source files for this blog post are on GitHub.

Now that you have successfully run code in the address space of another process, it is time to make that actually code do something. Since HelloWorld.exe just prints Hello World! over and over again, let's change the string being printed.

Finding offsets

While not necessary to follow this blog, it is useful to understand a little bit about Portable Executable (PE) files, which is the file format of Windows Executable files. The microsoft docs are here, and Corkami has some great visual resources.

Fire up 32-bit x64dbg and open up HelloWorld.exe (you can just drag it in), and hit run. x64dbg will stop at the entry point for you.

EntryPoint

At this point you are done debugging 😄 press run again to allow the HelloWorld.exe to execute (The bottom left corner should say Running).

Open up the Memory Map tab and and press ctrl+b. A search window will pop up, allowing you to search for values in memory. Input Hello World! in the ASCII field:

Search

After searching, one result should be returned - this is address of the string you want to change:

Search Result

NOTE: The address you see will likely be different than the screenshot above! This is due to Address Space Layout Randomization (ASLR). ASLR randomizes the base address of HelloWorld.exe, but the offset of your target string from the base address HelloWorld.exe in memory will always be the same.

With this knowledge, note the address of the target string, in this example it is 0x0107CB30. Then return to the memory map and note the base address of HelloWorld.exe, in this case it is 0x01060000. The difference of 0x0107CB30 - 0x01060000 = 0x1CB30 gives you the offset of your target string. This offset will not change.

One more characteristic to note is that your target string is in a read-only portion of memory. The payload will have to account for this:

Read-Only Data

Creating the payload

Create a new DLL in Visual Studio like you did in 101. This payload will only require 4 API calls to overwrite your target string:

*You should call VirtualProtect again at the end of our payload code to make the string read-only again. This just ensures you leave the program in as similar of a state as possible after modifying it.

GetModuleHandle and Calculating the target string's address

These two lines should exist outside of the switch statement in DllMain, otherwise Visual Studio will complain undefined variables could be referenced within the switch.

// https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandlea
BYTE* stringAddr = (BYTE*)GetModuleHandle(TEXT("HelloWorld.exe")) + 0x1CB30;
LPVOID stringAddress = LPVOID(stringAddr);

Take note of the casting going on. This is necessary because you cannot do pointer arithmetic on a void pointer. Why? because C++ does not know the size of each unit of data being pointed at. Casting the pointer as a BYTE pointer allows C++ to know the arithmetic is adding 0x1CB30 to the pointer to move it 0x1CB30 bytes (size of 4 bits) forward. If that explanation didn't make any sense, try this Quora thread about it.

After the pointer arithmetic is done, you can cast the pointer back to LPVOID, which is what the other API calls expect as an argument.

First VirtualProtect

Something else to note is that it is dangerous to overwrite data of length x with data of length > x. You might overwrite other data the program expects to be there for another purpose, akin to a buffer overflow. In this case the length of the target string is 0xE, so only make 0xE bytes writeable.

    DWORD oldPerms;
    // https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect
    if (!VirtualProtect(stringAddress,
        0xE,   //Length of the string "Hello World!\n"
        PAGE_EXECUTE_READWRITE,
        &oldPerms //saves off the old permissions
    )) {
        printf("Error with VirtualProtect! %#010x", GetLastError());
        exit(0);
    }

WriteProcessMemory and GetCurrentProcess

 // https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory
 // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentprocess
        if (!WriteProcessMemory( GetCurrentProcess(),
            stringAddress,
            "Injected!\n",
            0xc,
            NULL
        )) {
            printf("Error with VirtualProtect! %#010x", GetLastError());
            exit(0);
        }

Second VirtualProtect

DWORD finalPerms;
        if (!VirtualProtect(stringAddress,
            0xE,
            oldPerms,
            &finalPerms
        )) {
            printf("Error with VirtualProtect! %#010x", GetLastError());
            exit(0);
        }

Voila! You should be able to see the print statement now change when you inject your DLL into HelloWorld.exe:

Injected!

As always you can check out the source for a completed payload on GitHub if you run into trouble.