Home Malware Development 0 - How to call Windows API from Go
Post
Cancel

Malware Development 0 - How to call Windows API from Go

Introduction

Hello friends!

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 designed for this but Golang is quite different so let’s see how to do it.

Explanation

On the next days I’ll be uploading all kind of malware development and red team posts but all the code will be… written in Golang! That’s why you need to understan how we have to use Windows API as it’s one of the most important things when writing malware

In C++ it’s pretty easy to do, you just have to import the DLL like this on top of the code and all the internal functions are loaded:

1
#include <windows.h>

And for example kernel32.dll is always loaded into every process so you even don’t have to do it.

In Golang this is a little bit different but don’t worry because with some Windows knowledge you will be able to use it without problems. First of all we have to import the golang.org/x/sys/windows package, it has some internal functions which allow us to import the DLLs and its functions. We also could do this with the syscall package because the functions even don’t change.

Let’s start by importing necessary packages

1
2
3
4
5
6
import (
  "fmt"
  "unsafe"

  "golang.org/x/sys/windows"
)

Once we’ve imported one of the 2 packages let’s continue with the DLL:

1
2
3
func main(){
  kernel32 := windows.NewLazyDLL("kernel32.dll")
}

The NewLazyDLL function basically loads a DLL and it looks for it first under C:\Windows\System32

As this posts are focused to red team, we will load the MessageBox function, which is in user32.dll, to test with no risks:

1
2
3
4
func main(){
  user32 := windows.NewLazyDLL("user32.dll")
  MessageBox := user32.NewProc("MessageBoxW")
}

All the imported functions have the Call() procedure, which receives variables of uintptr type and it always will return 3 values. The first one is the main part of the call, for example OpenProcess() would return a handle to a process, GetProcAddress return a procedure address and so on. For the second value I haven’t found any information because it’s never used. And the third one is an error type variable like most of the Golang functions which allow users to handle error. If the call shouldn’t return any handle or special address it will return an status code which can also be used to check if an error has ocurred.

Now to call MessageBox we have to know what arguments should receive, so let’s check the Windows documentation here. As we can see the function receives a window handle (0 for no owner window), text to be displayed, message title and some other flags to determine the type of message box, in our case we’ll be using a common “Accept” box so the value should be MB_OK

Most of the internal Windows values like MB_OK are available to use as a constant variable so we just have to use windows.MB_OK

We also have to do something before calling the function. It’s converting the text and title strings into *uint16 (note the * as it means it’s a pointer), we can do it with another internal function windows.StringToUTF16Ptr() and we’ll also use the unsafe package to access the unsafe.Pointer() function which needed to convert *uint16 to uintptr. The result would be something like this: uintptr(unsafe.Pointer(windows.StringToUTF16Ptr("example text")))

Now let’s put all pieces together to make it work

1
2
3
4
5
6
func main(){
  user32 := windows.NewLazyDLL("user32.dll")
  MessageBox := user32.NewProc("MessageBoxW")

  r, _, _ := MessageBox.Call(0, uintptr(unsafe.Pointer(windows.StringToUTF16Ptr("Hello World!"))), uintptr(unsafe.Pointer(windows.StringToUTF16Ptr("Example"))), windows.MB_OK)
}

I recommend you to call the functions with one argument per line so it can be read easily like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
func main(){
  user32 := windows.NewLazyDLL("user32.dll")
  MessageBox := user32.NewProc("MessageBoxW")

  r, _, _ := MessageBox.Call(
    0,
    uintptr(unsafe.Pointer(windows.StringToUTF16Ptr("Hello World!"))),
    uintptr(unsafe.Pointer(windows.StringToUTF16Ptr("Example"))),
    windows.MB_OK,
  )

  fmt.Println("Return code:", r)
}

Let’s see how it works.

Demo

First of all we compile our non-malicious payload.

1
GOARCH=amd64 GOOS=windows go build main.go

Then we transfer it to a Windows machine

And let’s execute it

As we can see… it has worked! A simple message box appeared.

References

1
2
3
4
https://github.com/golang/go/wiki/WindowsDLLs
https://anubissec.github.io/How-To-Call-Windows-APIs-In-Golang/#
https://justen.codes/breaking-all-the-rules-using-go-to-call-windows-api-2cbfd8c79724?gi=1337f3df6dc9
https://www.thesubtlety.com/post/getting-started-golang-windows-apis/

Conclusion

As you can see calling Windows API is easy if you know in which DLLs the functions are loaded, like GetCurrentProcess() from kernel32.dll. I hope you’ve learned a lot from this post.

Source code here

Go back to top

This post is licensed under CC BY 4.0 by the author.