Skip to content

Instantly share code, notes, and snippets.

@thesubtlety
Created February 5, 2020 05:18
Show Gist options
  • Save thesubtlety/be6e7ec9c19083473bed4cae11c8160d to your computer and use it in GitHub Desktop.
Save thesubtlety/be6e7ec9c19083473bed4cae11c8160d to your computer and use it in GitHub Desktop.
Calling Windows DLLs from Go
package main
import (
"fmt"
"syscall"
"unicode/utf16"
"unsafe"
)
//https://github.com/golang/go/wiki/WindowsDLLs
//https://golang.org/src/syscall/dll_windows.go
//https://github.com/iamacarpet/go-win64api/blob/master/users.go
//https://blog.gopheracademy.com/advent-2017/unsafe-pointer-and-system-calls/
var (
kernel32DLL = syscall.NewLazyDLL("kernel32.dll")
procCreateProcessA = kernel32DLL.NewProc("CreateProcessA")
modNetapi32 = syscall.NewLazyDLL("netapi32.dll")
usrNetUserGetInfo = modNetapi32.NewProc("NetUserGetInfo")
)
const (
USER_PRIV_ADMIN = 2
)
func main() {
/*
//NetUserGetInfo(serverName *uint16, userName *uint16, level uint32, buf **byte) (neterr error)
//nStatus = NetUserGetInfo (NULL, L"Domain\\TestUser", dwLevel, (LPBYTE *) & pBuf);
NET_API_STATUS NET_API_FUNCTION NetUserGetInfo(
LPCWSTR servername,
LPCWSTR username,
DWORD level,
LPBYTE *bufptr
);
typedef struct _USER_INFO_1 {
LPWSTR usri1_name;
LPWSTR usri1_password;
DWORD usri1_password_age;
DWORD usri1_priv;
LPWSTR usri1_home_dir;
LPWSTR usri1_comment;
DWORD usri1_flags;
LPWSTR usri1_script_path;
} USER_INFO_1, *PUSER_INFO_1, *LPUSER_INFO_1;
*/
type USER_INFO_1 struct {
usri1_name *uint16
usri1_password *uint16
usri1_password_age uint32
usri1_priv uint32
usri1_home_dir *uint16
usri1_comment *uint16
usri1_flags uint32
usri1_script_path *uint16
}
out := new(byte)
n, _ := syscall.UTF16PtrFromString("user")
syscall.NetUserGetInfo(nil, n, 1, &out)
// the following .Call is the same as the above syscall.NetUserGetInfo and can be used
// ...if a syscall function doesn't exist and you want to make a direct DLL call?
/*
var dataPointer uintptr
_, _, _ = usrNetUserGetInfo.Call(0,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("user"))),
uintptr(uint32(1)), //a single value doesn't need a pointer
uintptr(unsafe.Pointer(&dataPointer)),
)
var data = (*USER_INFO_1)(unsafe.Pointer(dataPointer))
*/
var data = (*USER_INFO_1)(unsafe.Pointer(out))
fmt.Printf("%+v\n", data) //print struct with struct names
fmt.Printf("name: %v\n", UTF16toString(data.usri1_name)) // custom converter changes *uint16 to *[4096]uint16
fmt.Printf("priv: %v", data.usri1_priv)
if data.usri1_priv == USER_PRIV_ADMIN {
fmt.Printf(" - User is admin\n")
}
}
/*
BOOL CreateProcessA(
LPCSTR lpApplicationName,
LPSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCSTR lpCurrentDirectory,
LPSTARTUPINFOA lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa
https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types
*/
func createProc() {
command := StringToUTF16Ptr("calc.exe")
si := new(syscall.StartupInfo)
pi := new(syscall.ProcessInformation)
err := syscall.CreateProcess(nil, command, nil, nil, false, 0, nil, nil, si, pi)
if err != nil {
fmt.Printf("CreateProcess: %v\n", err)
}
}
// builtin utf16tostring string expects a uint16 array but we have a pointer to a uint16
// so we need to cast it after converting it to an unsafe pointer
// this is a common pattern though the buffer size varies
// see https://golang.org/pkg/unsafe/#Pointer for more details
// also useful https://blog.gopheracademy.com/advent-2017/unsafe-pointer-and-system-calls/
func UTF16toString(p *uint16) string {
//return syscall.UTF16ToString((*[4096]uint16)(unsafe.Pointer(p))[:])
ptr := unsafe.Pointer(p) //necessary to arbitrarily cast to *[4096]uint16 (?)
uint16ptrarr := (*[4096]uint16)(ptr)[:] //4096 is arbitrary? could be smaller
return syscall.UTF16ToString(uint16ptrarr) //now uint16ptrarr is in a format to pass to the builtin converter
}
/* //builtin syscall.UTF16.ToString
func UTF16ToString(s []uint16) string {
for i, v := range s {
if v == 0 {
s = s[0:i]
break
}
}
return string(utf16.Decode(s))
}
*/
// StringToUTF16Ptr converts a Go string into a pointer to a null-terminated UTF-16 wide string.
// This assumes str is of a UTF-8 compatible encoding so that it can be re-encoded as UTF-16.
func StringToUTF16Ptr(str string) *uint16 {
wchars := utf16.Encode([]rune(str + "\x00"))
return &wchars[0]
}
/*
//https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types
type (
BOOL uint32
BOOLEAN byte
BYTE byte
DWORD uint32
DWORD64 uint64
HANDLE uintptr
HLOCAL uintptr
LARGE_INTEGER int64
LONG int32
LPVOID uintptr
SIZE_T uintptr
UINT uint32
ULONG_PTR uintptr
ULONGLONG uint64
WORD uint16
)
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment