Waseem Akram

Malware Development How to call Windows API from Go

By Waseem Akram on 9/30/2024

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

Malware Development How to call Windows API from Go

Introduction

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 understand 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:

#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

import (
    "fmt"
    "unsafe"
    "golang.org/x/sys/windows"
)

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

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:

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 occurred.

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 msref

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

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:

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

go build main.go

msgboxbuild And let's execute it:

./main.exe

msg

As we can see... it has worked! A simple message box appeared. This is the first step to start writing malware in Golang, I hope you have enjoyed it and learned something new. If you have any questions or suggestions feel free to ask me.

References

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 - 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
10/1/2024
·
Waseem Akram

Malware Development 1 - CreateRemoteThread shellcode injection (Golang)

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

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