Created
February 5, 2020 05:18
-
-
Save thesubtlety/be6e7ec9c19083473bed4cae11c8160d to your computer and use it in GitHub Desktop.
Calling Windows DLLs from Go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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