├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── build.ps1 ├── credenumeratew_managed ├── README.md ├── credentials.go ├── main.go ├── syscall_windows.go ├── types_windows.go └── zsyscall_windows.go ├── credenumeratew_unmanaged ├── README.md ├── credentials.go ├── main.go ├── syscall_windows.go ├── types_windows.go └── zsyscall_windows.go ├── go.mod ├── go.sum ├── logon ├── README.md ├── logon.go ├── main.go ├── sidtype_string.go ├── syscall_windows.go └── zsyscall_windows.go ├── netstat ├── README.md ├── main.go ├── syscall_windows.go ├── tcpstate.go ├── tcptable.go ├── types_windows.go └── zsyscall_windows.go ├── networkparams ├── README.md ├── main.go ├── networkparams.go ├── syscall_windows.go ├── types_windows.go └── zsyscall_windows.go ├── tools.go ├── unsafe_cast ├── README.md └── main.go └── volumes ├── README.md ├── main.go ├── volumes.go └── volumes_test.go /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Go ### 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Justen Walker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Safety Not Guaranteed - GopherCon 2020 2 | 3 | This repo contains some full-featured examples of calling Windows APIs from Go from my [talk at GopherCon](https://www.youtube.com/watch?v=EsPcKkESYPA). 4 | Check out each folder for mor information. 5 | 6 | ## Building and Running the Examples 7 | 8 | Run `.\build.ps1`, the commands will be built into the `.\bin` folder 9 | 10 | ## Unsafe Operations 11 | 12 | Here is a list of collection unsafe operations that should be in your tool belt. 13 | 14 | ### Pointer Arithmetic 15 | 16 | ```go 17 | var arr *T // Pointer to the first element of the array 18 | i := uintptr(1) // subscript/offset to the element we want 19 | sz := unsafe.Sizeof(T{}) // sz = size of T in bytes 20 | 21 | // t := arr[i] 22 | t := *(*T)(unsafe.Pointer(uintptr(unsafe.Pointer(arr)) + (i * sz))) 23 | // this is several steps all happening on one line: 24 | // a) uintptr(unsafe.Pointer(arr)) --- convert to uintptr so we can do arithmetic 25 | // b) (a + i*sz) - advance the pointer the correct number of bytes to get to index 'i' 26 | // c) (*T)unsafe.Pointer(b) -- convert uintptr back to a *T 27 | // d) *(c) -- dereference *T to get the value T 28 | ``` 29 | 30 | *Note*: As of Go 1.17, the unsafe package has [`unsafe.Add`](https://pkg.go.dev/unsafe#Add) making pointer-arithmetic more straight-forward. 31 | 32 | ```go 33 | var arr *T // Pointer to the first element of the array 34 | i := uintptr(1) // subscript/offset to the element we want 35 | sz := unsafe.Sizeof(T{}) // sz = size of T in bytes 36 | 37 | // t := arr[i] 38 | t := *(*T)(unsafe.Add(unsafe.Pointer(arr),i * sz)) 39 | ``` 40 | 41 | Example: 42 | 43 | C-Strings are either *uint8 (ANSI) or *uint16 (UTF-16) 44 | But regardless, to find the length, use pointer arithmetic. 45 | 46 | ```go 47 | func strlen(p *uint8) (n int) { 48 | if p == nil { 49 | return 50 | } 51 | for *p != 0 { 52 | // Go 1.16 and earlier 53 | p = (*uint8)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + 1)) 54 | // Go 1.17 and later 55 | // p = (*uint8)(unsafe.Add(unsafe.Pointer(p), 1)) 56 | n++ 57 | } 58 | return 59 | } 60 | ``` 61 | 62 | ### Converting from *T,len => []T 63 | 64 | A C array is a pointer to the first element. 65 | The size of the array is either given, or found (rather unsafely) by traversing the array until encountering a sentinel (usually `NULL`). 66 | 67 | ```go 68 | var tptr *T // Pointer to the first element of the array 69 | var n int // The real length of the array 70 | 71 | // Convert *T,n to []T 72 | ts := (*[1 << 30]T)(unsafe.Pointer(tptr))[:n:n] 73 | // this is 3 steps all happening at once: 74 | // a) unsafe.Pointer(tptr) --- so we can convert to a pointer of another type 75 | // b) (*[1<<30]T)(a) -- convert to a pointer to a large array. 76 | // c) (b)[0:n:n] -- create a slice backed by the array we're pointing at, setting both its length and capacity to the known value 'n' 77 | ``` 78 | 79 | *Note*: As of Go 1.17, the unsafe package has [`unsafe.Slice(ptr *T, len anyIntegerType) []T`](https://pkg.go.dev/unsafe#Slice) making this construct obsolete 🎉. 80 | 81 | ```go 82 | var tptr *T // Pointer to the first element of the array 83 | var n int // the real length of the array 84 | 85 | // Convert *T,n to []T 86 | ts := unsafe.Slice(tptr,n) 87 | ``` -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | #!powershell 2 | [CmdletBinding()] 3 | param ( 4 | [Parameter(Position=0)] 5 | [ValidateSet( 6 | "all", 7 | "credenumeratew_managed", 8 | "credenumeratew_unmanaged", 9 | "logon", 10 | "netstat", 11 | "networkparams", 12 | "unsafe_cast", 13 | "volumes")] 14 | [string] 15 | $Command="all" 16 | ) 17 | 18 | function Build-Command { 19 | param( 20 | [string]$Command 21 | ) 22 | if ( -not (Test-Path -Path ".\bin") ) { 23 | New-Item -Path ".\bin" -ItemType Directory 24 | } 25 | Write-Host "Building: ${command}" 26 | & go build -o ".\bin\${command}.exe" ".\${command}" 27 | } 28 | 29 | if ($Command -eq "all") { 30 | Build-Command -Command "credenumeratew_managed" 31 | Build-Command -Command "credenumeratew_unmanaged" 32 | Build-Command -Command "logon" 33 | Build-Command -Command "netstat" 34 | Build-Command -Command "networkparams" 35 | Build-Command -Command "unsafe_cast" 36 | Build-Command -Command "volumes" 37 | } else { 38 | Build-Command -Command $Command 39 | } -------------------------------------------------------------------------------- /credenumeratew_managed/README.md: -------------------------------------------------------------------------------- 1 | # Credential Enumerator (Managed) 2 | 3 | This example program lists all credentials found on the system 4 | using the [`CredEnumerateW`](https://docs.microsoft.com/en-us/windows/win32/api/wincred/nf-wincred-credenumeratew) API. It copies the unmanaged memory into regular 5 | Go structs and frees the memory using [`CredFree`](https://docs.microsoft.com/en-us/windows/win32/api/wincred/nf-wincred-credfree). 6 | 7 | It also defines the relevant API Structs: 8 | 9 | - [`FILETIME`](https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime) 10 | - [`SYSTEMTIME`](https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-systemtime) 11 | - [`CREDENTIALW`](https://docs.microsoft.com/en-us/windows/win32/api/wincred/ns-wincred-credentialw) 12 | - [`CREDENTIAL_ATTRIBUTEW`](https://docs.microsoft.com/en-us/windows/win32/api/wincred/ns-wincred-credential_attributew) 13 | 14 | When you run the program, it will enumerate your credentials and display them to the terminal. 15 | 16 | ``` 17 | > credenumeratew_managed.exe 18 | ---- LegacyGeneric:target=git:https://github.com --- 19 | Type: Generic 20 | UserName: "PersonalAccessToken" 21 | Cred(masked): abcd0123 22 | ---- MicrosoftAccount:target=SSO_POP_User:user=username@example.com --- 23 | Comment: "Microsoft_WindowsLive:SerializedMaterial:1232" 24 | Type: Generic 25 | UserName: "username@example.com" 26 | Attributes: 27 | Microsoft_WindowsLive:SerializedMaterial:0 (flags=0x0000): 256 bytes 28 | a1......AF== 29 | Microsoft_WindowsLive:SerializedMaterial:1 (flags=0x0000): 256 bytes 30 | b2......BG== 31 | Microsoft_WindowsLive:SerializedMaterial:2 (flags=0x0000): 256 bytes 32 | c3......CH== 33 | Microsoft_WindowsLive:SerializedMaterial:3 (flags=0x0000): 256 bytes 34 | d4......DI== 35 | Microsoft_WindowsLive:SerializedMaterial:4 (flags=0x0000): 208 bytes 36 | e5......EJ== 37 | 38 | ... 39 | 40 | ``` 41 | 42 | For your safety, it won't actually print out your credential secret values, it will print out the first few characters of their sha256 hash. 43 | It will also print out the credential attribute value as base64 since they may not be printable. (and are mostly uninteresting) 44 | -------------------------------------------------------------------------------- /credenumeratew_managed/credentials.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | "unsafe" 6 | ) 7 | 8 | type Credential struct { 9 | Name string 10 | Alias string 11 | Type string 12 | Comment string 13 | UserName string 14 | Credential []byte 15 | LastWritten time.Time 16 | Attributes []CredentialAttribute 17 | } 18 | 19 | type CredentialAttribute struct { 20 | Keyword string 21 | Flags uint32 22 | Value []byte 23 | } 24 | 25 | func CredEnumerate(filter string) ([]Credential, error) { 26 | var creds []Credential 27 | var err error 28 | var count uint32 29 | var items **_CREDENTIALW 30 | if filter != "" { 31 | var wstr *uint16 32 | wstr, err = UTF16PtrFromString(filter) 33 | if err != nil { 34 | return nil, err 35 | } 36 | err = CredEnumerateW(wstr, 0, &count, &items) 37 | } else { 38 | err = CredEnumerateW(nil, _CRED_ENUMERATE_ALL_CREDENTIALS, &count, &items) 39 | } 40 | if err != nil { 41 | return nil, err 42 | } 43 | // Free Immediately before returning 44 | defer CredFree(unsafe.Pointer(items)) 45 | 46 | var sz = unsafe.Sizeof(&_CREDENTIALW{}) 47 | creds = make([]Credential, 0, int(count)) 48 | for i := uint32(0); i < count; i++ { 49 | // pcred := *(**_CREDENTIALW)(unsafe.Pointer(uintptr(unsafe.Pointer(items)) + uintptr(i)*sz)) 50 | pcred := *(**_CREDENTIALW)(unsafe.Add(unsafe.Pointer(items), uintptr(i)*sz)) 51 | // Copy all data to Go structs in Managed Memory 52 | creds = append(creds, toCredential(pcred)) 53 | } 54 | return creds, nil 55 | } 56 | 57 | func toCredential(pcred *_CREDENTIALW) (credential Credential) { 58 | credential.Name = UTF16PtrToString(pcred.TargetName) 59 | credential.Alias = UTF16PtrToString(pcred.TargetAlias) 60 | credential.Comment = UTF16PtrToString(pcred.Comment) 61 | credential.UserName = UTF16PtrToString(pcred.UserName) 62 | credential.LastWritten = pcred.LastWritten.ToTime() 63 | switch pcred.Type { 64 | case _CRED_TYPE_DOMAIN_CERTIFICATE: 65 | credential.Type = "Domain Cert" 66 | case _CRED_TYPE_DOMAIN_EXTENDED: 67 | credential.Type = "Domain Extended" 68 | case _CRED_TYPE_DOMAIN_PASSWORD: 69 | credential.Type = "Domain Password" 70 | case _CRED_TYPE_DOMAIN_VISIBLE_PASSWORD: 71 | credential.Type = "Domain Visible Password" 72 | case _CRED_TYPE_GENERIC: 73 | credential.Type = "Generic" 74 | case _CRED_TYPE_GENERIC_CERTIFICATE: 75 | credential.Type = "Generic Certificate" 76 | default: 77 | credential.Type = "Unknown" 78 | } 79 | if pcred.CredentialBlobSize > 0 { 80 | n := int(pcred.CredentialBlobSize) 81 | val := make([]byte, n) 82 | copy(val, unsafe.Slice(pcred.CredentialBlob, n)) 83 | credential.Credential = val 84 | } 85 | if pcred.AttributeCount > 0 { 86 | credential.Attributes = make([]CredentialAttribute, 0, int(pcred.AttributeCount)) 87 | var attrsz uintptr = unsafe.Sizeof(_CREDENTIAL_ATTRIBUTEW{}) 88 | for i := uint32(0); i < pcred.AttributeCount; i++ { 89 | // pattr := (*_CREDENTIAL_ATTRIBUTEW)(unsafe.Pointer(uintptr(unsafe.Pointer(pcred.Attributes)) + uintptr(i)*attrsz)) 90 | pattr := (*_CREDENTIAL_ATTRIBUTEW)(unsafe.Add(unsafe.Pointer(pcred.Attributes), uintptr(i)*attrsz)) 91 | key := UTF16PtrToString(pattr.Keyword) 92 | n := int(pattr.ValueSize) 93 | var val []byte 94 | if n > 0 { 95 | val = make([]byte, n) 96 | // copy(val, (*(*[256]byte)(unsafe.Pointer(pattr.Value)))[:n:n]) 97 | copy(val, unsafe.Slice(pattr.Value, n)) 98 | } 99 | credential.Attributes = append(credential.Attributes, CredentialAttribute{ 100 | Keyword: key, 101 | Flags: pattr.Flags, 102 | Value: val, 103 | }) 104 | } 105 | } 106 | return credential 107 | } 108 | -------------------------------------------------------------------------------- /credenumeratew_managed/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/base64" 6 | "encoding/hex" 7 | "fmt" 8 | "log" 9 | ) 10 | 11 | //go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go syscall_windows.go 12 | 13 | func main() { 14 | creds, err := CredEnumerate("") 15 | if err != nil { 16 | log.Fatalf("error: %v", err) 17 | } 18 | for _, cred := range creds { 19 | fmt.Printf("---- %s ---\n", cred.Name) 20 | if cred.Alias != "" { 21 | fmt.Printf("Alias: %q\n", cred.Alias) 22 | } 23 | if cred.Comment != "" { 24 | fmt.Printf("Comment: %q\n", cred.Comment) 25 | } 26 | fmt.Printf("Type: %s\n", cred.Type) 27 | if cred.UserName != "" { 28 | fmt.Printf("UserName: %q\n", cred.UserName) 29 | } 30 | if len(cred.Credential) > 0 { 31 | hash := sha256.Sum256(cred.Credential) 32 | masked := hex.EncodeToString(hash[:])[:8] 33 | fmt.Printf("Cred(masked): %s\n", masked) 34 | } 35 | if len(cred.Attributes) > 0 { 36 | fmt.Println("Attributes:") 37 | for _, attr := range cred.Attributes { 38 | fmt.Printf("\t%s (flags=%#04x): %d bytes\n\t\t%s\n", attr.Keyword, attr.Flags, len(attr.Value), base64.StdEncoding.EncodeToString(attr.Value)) 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /credenumeratew_managed/syscall_windows.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "syscall" 5 | "unicode/utf16" 6 | "unsafe" 7 | ) 8 | 9 | //sys CredEnumerateW(filter *uint16, flags uint32, count *uint32, credentials ***_CREDENTIALW) (err error) [failretval==0] = advapi32.CredEnumerateW 10 | //sys CredFree(buffer unsafe.Pointer) = advapi32.CredFree 11 | //sys FileTimeToSystemTime(fileTime *_FILETIME, systemTime *_SYSTEMTIME) [failretval==0] (err error) = kernel32.FileTimeToSystemTime 12 | 13 | // UTF16PtrFromString converts a string to a UTF-16 C-String 14 | func UTF16PtrFromString(str string) (*uint16, error) { 15 | return syscall.UTF16PtrFromString(str) 16 | } 17 | 18 | const wcharSize = uintptr(2) 19 | 20 | // UTF16PtrToString is like syscall.UTF16ToString, but takes *uint16 21 | // as a parameter instead of []uint16. 22 | func UTF16PtrToString(p *uint16) string { 23 | if p == nil { 24 | return "" 25 | } 26 | end := unsafe.Pointer(p) 27 | var n int 28 | for *(*uint16)(end) != 0 { // Advance to the NULL terminator 29 | // end = unsafe.Pointer(uintptr(end) + wcharSize) 30 | end = unsafe.Add(end, wcharSize) 31 | n++ 32 | } 33 | // Convert *uint16 to []uint16 34 | wstr := unsafe.Slice(p, n) 35 | return string(utf16.Decode(wstr)) 36 | } 37 | -------------------------------------------------------------------------------- /credenumeratew_managed/types_windows.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | const _CRED_ENUMERATE_ALL_CREDENTIALS uint32 = 0x1 8 | 9 | /* 10 | typedef struct _FILETIME { 11 | DWORD dwLowDateTime; 12 | DWORD dwHighDateTime; 13 | } FILETIME, *PFILETIME, *LPFILETIME; 14 | */ 15 | type _FILETIME struct { 16 | LowDateTime uint32 17 | HighDateTime uint32 18 | } 19 | 20 | func (t *_FILETIME) ToTime() time.Time { 21 | var systime _SYSTEMTIME 22 | if err := FileTimeToSystemTime(t, &systime); err != nil { 23 | return time.Time{} 24 | } 25 | return systime.ToTime() 26 | } 27 | 28 | /* 29 | typedef struct _SYSTEMTIME { 30 | WORD wYear; 31 | WORD wMonth; 32 | WORD wDayOfWeek; 33 | WORD wDay; 34 | WORD wHour; 35 | WORD wMinute; 36 | WORD wSecond; 37 | WORD wMilliseconds; 38 | } SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME; 39 | */ 40 | type _SYSTEMTIME struct { 41 | Year uint16 42 | Month uint16 43 | DayOfWeek uint16 44 | Day uint16 45 | Hour uint16 46 | Minute uint16 47 | Second uint16 48 | Milliseconds uint16 49 | } 50 | 51 | func (s *_SYSTEMTIME) ToTime() time.Time { 52 | return time.Date( 53 | int(s.Year), 54 | time.Month(s.Month), 55 | int(s.Day), 56 | int(s.Hour), 57 | int(s.Minute), 58 | int(s.Second), 59 | int(s.Milliseconds)*1_000_000, time.UTC) 60 | } 61 | 62 | /* 63 | typedef struct _CREDENTIALW { 64 | DWORD Flags; 65 | DWORD Type; 66 | LPWSTR TargetName; 67 | LPWSTR Comment; 68 | FILETIME LastWritten; 69 | DWORD CredentialBlobSize; 70 | LPBYTE CredentialBlob; 71 | DWORD Persist; 72 | DWORD AttributeCount; 73 | PCREDENTIAL_ATTRIBUTEW Attributes; 74 | 75 | #if ... 76 | 77 | wchar_t *TargetAlias; 78 | 79 | #else 80 | 81 | LPWSTR TargetAlias; 82 | 83 | #endif 84 | #if ... 85 | 86 | wchar_t *UserName; 87 | 88 | #else 89 | 90 | LPWSTR UserName; 91 | 92 | #endif 93 | } CREDENTIALW, *PCREDENTIALW; 94 | */ 95 | type _CREDENTIALW struct { 96 | Flags uint32 97 | Type uint32 98 | TargetName *uint16 99 | Comment *uint16 100 | LastWritten _FILETIME 101 | CredentialBlobSize uint32 102 | CredentialBlob *byte 103 | Persist uint32 104 | AttributeCount uint32 105 | Attributes *_CREDENTIAL_ATTRIBUTEW 106 | TargetAlias *uint16 107 | UserName *uint16 108 | } 109 | 110 | const ( 111 | _CRED_TYPE_GENERIC uint32 = 0x1 112 | _CRED_TYPE_DOMAIN_PASSWORD uint32 = 0x2 113 | _CRED_TYPE_DOMAIN_CERTIFICATE uint32 = 0x3 114 | _CRED_TYPE_DOMAIN_VISIBLE_PASSWORD uint32 = 0x4 115 | _CRED_TYPE_GENERIC_CERTIFICATE uint32 = 0x5 116 | _CRED_TYPE_DOMAIN_EXTENDED uint32 = 0x6 117 | ) 118 | 119 | /* 120 | typedef struct _CREDENTIAL_ATTRIBUTEW { 121 | LPWSTR Keyword; 122 | DWORD Flags; 123 | DWORD ValueSize; 124 | LPBYTE Value; 125 | } CREDENTIAL_ATTRIBUTEW, *PCREDENTIAL_ATTRIBUTEW; 126 | */ 127 | type _CREDENTIAL_ATTRIBUTEW struct { 128 | Keyword *uint16 129 | Flags uint32 130 | ValueSize uint32 131 | Value *byte 132 | } 133 | -------------------------------------------------------------------------------- /credenumeratew_managed/zsyscall_windows.go: -------------------------------------------------------------------------------- 1 | // Code generated by 'go generate'; DO NOT EDIT. 2 | 3 | package main 4 | 5 | import ( 6 | "syscall" 7 | "unsafe" 8 | 9 | "golang.org/x/sys/windows" 10 | ) 11 | 12 | var _ unsafe.Pointer 13 | 14 | // Do the interface allocations only once for common 15 | // Errno values. 16 | const ( 17 | errnoERROR_IO_PENDING = 997 18 | ) 19 | 20 | var ( 21 | errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) 22 | errERROR_EINVAL error = syscall.EINVAL 23 | ) 24 | 25 | // errnoErr returns common boxed Errno values, to prevent 26 | // allocations at runtime. 27 | func errnoErr(e syscall.Errno) error { 28 | switch e { 29 | case 0: 30 | return errERROR_EINVAL 31 | case errnoERROR_IO_PENDING: 32 | return errERROR_IO_PENDING 33 | } 34 | // TODO: add more here, after collecting data on the common 35 | // error values see on Windows. (perhaps when running 36 | // all.bat?) 37 | return e 38 | } 39 | 40 | var ( 41 | modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") 42 | modkernel32 = windows.NewLazySystemDLL("kernel32.dll") 43 | 44 | procCredEnumerateW = modadvapi32.NewProc("CredEnumerateW") 45 | procCredFree = modadvapi32.NewProc("CredFree") 46 | procFileTimeToSystemTime = modkernel32.NewProc("FileTimeToSystemTime") 47 | ) 48 | 49 | func CredEnumerateW(filter *uint16, flags uint32, count *uint32, credentials ***_CREDENTIALW) (err error) { 50 | r1, _, e1 := syscall.Syscall6(procCredEnumerateW.Addr(), 4, uintptr(unsafe.Pointer(filter)), uintptr(flags), uintptr(unsafe.Pointer(count)), uintptr(unsafe.Pointer(credentials)), 0, 0) 51 | if r1 == 0 { 52 | err = errnoErr(e1) 53 | } 54 | return 55 | } 56 | 57 | func CredFree(buffer unsafe.Pointer) { 58 | syscall.Syscall(procCredFree.Addr(), 1, uintptr(buffer), 0, 0) 59 | return 60 | } 61 | 62 | func FileTimeToSystemTime(fileTime *_FILETIME, systemTime *_SYSTEMTIME) (err error) { 63 | r1, _, e1 := syscall.Syscall(procFileTimeToSystemTime.Addr(), 2, uintptr(unsafe.Pointer(fileTime)), uintptr(unsafe.Pointer(systemTime)), 0) 64 | if r1 == 0 { 65 | err = errnoErr(e1) 66 | } 67 | return 68 | } 69 | -------------------------------------------------------------------------------- /credenumeratew_unmanaged/README.md: -------------------------------------------------------------------------------- 1 | # Credential Enumerator (Unmanaged) 2 | 3 | This example program is identical in function the "[managed](../credenumeratew_managed)" variant, but instead it works directly with the unmanaged 4 | memory, providing a wrapper type to access and free it safely. It's more error-prone and cumbersome to work with, 5 | but serves as an example of working with unmanaged memory and guarding against unsafe access. 6 | -------------------------------------------------------------------------------- /credenumeratew_unmanaged/credentials.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | "unsafe" 7 | ) 8 | 9 | func CredEnumerate(filter string) (*Credentials, error) { 10 | var cred Credentials 11 | var err error 12 | if filter != "" { 13 | var wstr *uint16 14 | wstr, err = UTF16PtrFromString(filter) 15 | if err != nil { 16 | return nil, err 17 | } 18 | err = CredEnumerateW(wstr, 0, &cred.count, &cred.items) 19 | } else { 20 | err = CredEnumerateW(nil, _CRED_ENUMERATE_ALL_CREDENTIALS, &cred.count, &cred.items) 21 | } 22 | if err != nil { 23 | return nil, err 24 | } 25 | return &cred, nil 26 | } 27 | 28 | type Credentials struct { 29 | free bool 30 | count uint32 31 | items **_CREDENTIALW 32 | } 33 | 34 | func (c *Credentials) ForEach(fn func(cred Credential) error) error { 35 | if c.free { 36 | return fmt.Errorf("memory freed") 37 | } 38 | var sz = unsafe.Sizeof(&_CREDENTIALW{}) 39 | for i := uint32(0); i < c.count; i++ { 40 | // pcred := *(**_CREDENTIALW)(unsafe.Pointer(uintptr(unsafe.Pointer(c.items)) + uintptr(i)*sz)) 41 | pcred := *(**_CREDENTIALW)(unsafe.Add(unsafe.Pointer(c.items), uintptr(i)*sz)) 42 | if err := fn(Credential{ 43 | free: &c.free, 44 | pcred: pcred, 45 | }); err != nil { 46 | return err 47 | } 48 | } 49 | return nil 50 | } 51 | 52 | func (c *Credentials) Free() { 53 | if !c.free { 54 | CredFree(unsafe.Pointer(c.items)) 55 | c.free = true 56 | } 57 | } 58 | 59 | type Credential struct { 60 | free *bool 61 | pcred *_CREDENTIALW 62 | } 63 | 64 | func (c *Credential) Name() string { 65 | if *c.free { 66 | return "" 67 | } 68 | return UTF16PtrToString(c.pcred.TargetName) 69 | } 70 | 71 | func (c *Credential) Alias() string { 72 | if *c.free { 73 | return "" 74 | } 75 | return UTF16PtrToString(c.pcred.TargetAlias) 76 | } 77 | 78 | func (c *Credential) Comment() string { 79 | if *c.free { 80 | return "" 81 | } 82 | return UTF16PtrToString(c.pcred.Comment) 83 | } 84 | 85 | func (c *Credential) UserName() string { 86 | if *c.free { 87 | return "" 88 | } 89 | return UTF16PtrToString(c.pcred.UserName) 90 | } 91 | 92 | func (c *Credential) Credential() []byte { 93 | if *c.free { 94 | return nil 95 | } 96 | if c.pcred.CredentialBlobSize == 0 { 97 | return nil 98 | } 99 | n := int(c.pcred.CredentialBlobSize) 100 | return unsafe.Slice(c.pcred.CredentialBlob, n) 101 | } 102 | 103 | func (c *Credential) LastWritten() time.Time { 104 | if *c.free { 105 | return time.Time{} 106 | } 107 | return c.pcred.LastWritten.ToTime() 108 | } 109 | 110 | func (c *Credential) Type() string { 111 | if *c.free { 112 | return "" 113 | } 114 | switch c.pcred.Type { 115 | case _CRED_TYPE_DOMAIN_CERTIFICATE: 116 | return "Domain Cert" 117 | case _CRED_TYPE_DOMAIN_EXTENDED: 118 | return "Domain Extended" 119 | case _CRED_TYPE_DOMAIN_PASSWORD: 120 | return "Domain Password" 121 | case _CRED_TYPE_DOMAIN_VISIBLE_PASSWORD: 122 | return "Domain Visible Password" 123 | case _CRED_TYPE_GENERIC: 124 | return "Generic" 125 | case _CRED_TYPE_GENERIC_CERTIFICATE: 126 | return "Generic Certificate" 127 | default: 128 | return "Unknown" 129 | } 130 | } 131 | 132 | type CredentialAttribute struct { 133 | Keyword string 134 | Flags uint32 135 | Value []byte 136 | } 137 | 138 | func (c *Credential) Attributes() []CredentialAttribute { 139 | if *c.free { 140 | return nil 141 | } 142 | if c.pcred.AttributeCount == 0 { 143 | return nil 144 | } 145 | attrs := make([]CredentialAttribute, 0, int(c.pcred.AttributeCount)) 146 | var sz = unsafe.Sizeof(_CREDENTIAL_ATTRIBUTEW{}) 147 | for i := uint32(0); i < c.pcred.AttributeCount; i++ { 148 | // pattr := (*_CREDENTIAL_ATTRIBUTEW)(unsafe.Pointer(uintptr(unsafe.Pointer(c.pcred.Attributes)) + uintptr(i)*sz)) 149 | pattr := (*_CREDENTIAL_ATTRIBUTEW)(unsafe.Add(unsafe.Pointer(c.pcred.Attributes), uintptr(i)*sz)) 150 | key := UTF16PtrToString(pattr.Keyword) 151 | n := int(pattr.ValueSize) 152 | var val []byte 153 | if n > 0 { 154 | val = make([]byte, n) 155 | // copy(val, (*(*[256]byte)(unsafe.Pointer(pattr.Value)))[:n:n]) 156 | copy(val, unsafe.Slice(pattr.Value, n)) 157 | } 158 | attrs = append(attrs, CredentialAttribute{ 159 | Keyword: key, 160 | Flags: pattr.Flags, 161 | Value: val, 162 | }) 163 | } 164 | return attrs 165 | } 166 | -------------------------------------------------------------------------------- /credenumeratew_unmanaged/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/base64" 6 | "encoding/hex" 7 | "fmt" 8 | "log" 9 | ) 10 | 11 | //go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go syscall_windows.go 12 | 13 | func main() { 14 | ce, err := CredEnumerate("") 15 | if err != nil { 16 | log.Fatalf("error: %v", err) 17 | } 18 | defer ce.Free() 19 | ce.ForEach(func(cred Credential) error { 20 | fmt.Printf("---- %s ---\n", cred.Name()) 21 | if alias := cred.Alias(); alias != "" { 22 | fmt.Printf("Alias: %q\n", alias) 23 | } 24 | if comment := cred.Comment(); comment != "" { 25 | fmt.Printf("Comment: %q\n", comment) 26 | } 27 | fmt.Printf("Type: %s\n", cred.Type()) 28 | if username := cred.UserName(); username != "" { 29 | fmt.Printf("UserName: %q\n", username) 30 | } 31 | if c := cred.Credential(); c != nil { 32 | hash := sha256.Sum256(c) 33 | masked := hex.EncodeToString(hash[:])[:8] 34 | fmt.Printf("Cred(masked): %s\n", masked) 35 | } 36 | if attrs := cred.Attributes(); len(attrs) > 0 { 37 | fmt.Println("Attributes:") 38 | for _, attr := range cred.Attributes() { 39 | fmt.Printf("\t%s (flags=%#04x): %d bytes\n\t\t%s\n", attr.Keyword, attr.Flags, len(attr.Value), base64.StdEncoding.EncodeToString(attr.Value)) 40 | } 41 | } 42 | return nil 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /credenumeratew_unmanaged/syscall_windows.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "syscall" 5 | "unicode/utf16" 6 | "unsafe" 7 | ) 8 | 9 | //sys CredEnumerateW(filter *uint16, flags uint32, count *uint32, credentials ***_CREDENTIALW) (err error) [failretval==0] = advapi32.CredEnumerateW 10 | //sys CredFree(buffer unsafe.Pointer) = advapi32.CredFree 11 | //sys FileTimeToSystemTime(fileTime *_FILETIME, systemTime *_SYSTEMTIME) [failretval==0] (err error) = kernel32.FileTimeToSystemTime 12 | 13 | // UTF16PtrFromString converts a string to a UTF-16 C-String 14 | func UTF16PtrFromString(str string) (*uint16, error) { 15 | return syscall.UTF16PtrFromString(str) 16 | } 17 | 18 | const wcharSize = uintptr(2) 19 | 20 | // UTF16PtrToString is like syscall.UTF16ToString, but takes *uint16 21 | // as a parameter instead of []uint16. 22 | func UTF16PtrToString(p *uint16) string { 23 | if p == nil { 24 | return "" 25 | } 26 | end := unsafe.Pointer(p) 27 | var n int 28 | for *(*uint16)(end) != 0 { // Advance to the NULL terminator 29 | // end = unsafe.Pointer(uintptr(end) + wcharSize) 30 | end = unsafe.Add(end, wcharSize) 31 | n++ 32 | } 33 | // Convert *uint16 to []uint16 34 | wstr := unsafe.Slice(p, n) 35 | return string(utf16.Decode(wstr)) 36 | } 37 | -------------------------------------------------------------------------------- /credenumeratew_unmanaged/types_windows.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | const _CRED_ENUMERATE_ALL_CREDENTIALS uint32 = 0x1 8 | 9 | /* 10 | typedef struct _FILETIME { 11 | DWORD dwLowDateTime; 12 | DWORD dwHighDateTime; 13 | } FILETIME, *PFILETIME, *LPFILETIME; 14 | */ 15 | type _FILETIME struct { 16 | LowDateTime uint32 17 | HighDateTime uint32 18 | } 19 | 20 | func (t *_FILETIME) ToTime() time.Time { 21 | var systime _SYSTEMTIME 22 | if err := FileTimeToSystemTime(t, &systime); err != nil { 23 | return time.Time{} 24 | } 25 | return systime.ToTime() 26 | } 27 | 28 | /* 29 | typedef struct _SYSTEMTIME { 30 | WORD wYear; 31 | WORD wMonth; 32 | WORD wDayOfWeek; 33 | WORD wDay; 34 | WORD wHour; 35 | WORD wMinute; 36 | WORD wSecond; 37 | WORD wMilliseconds; 38 | } SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME; 39 | */ 40 | type _SYSTEMTIME struct { 41 | Year uint16 42 | Month uint16 43 | DayOfWeek uint16 44 | Day uint16 45 | Hour uint16 46 | Minute uint16 47 | Second uint16 48 | Milliseconds uint16 49 | } 50 | 51 | func (s *_SYSTEMTIME) ToTime() time.Time { 52 | return time.Date( 53 | int(s.Year), 54 | time.Month(s.Month), 55 | int(s.Day), 56 | int(s.Hour), 57 | int(s.Minute), 58 | int(s.Second), 59 | int(s.Milliseconds)*1_000_000, time.UTC) 60 | } 61 | 62 | /* 63 | typedef struct _CREDENTIALW { 64 | DWORD Flags; 65 | DWORD Type; 66 | LPWSTR TargetName; 67 | LPWSTR Comment; 68 | FILETIME LastWritten; 69 | DWORD CredentialBlobSize; 70 | LPBYTE CredentialBlob; 71 | DWORD Persist; 72 | DWORD AttributeCount; 73 | PCREDENTIAL_ATTRIBUTEW Attributes; 74 | 75 | #if ... 76 | 77 | wchar_t *TargetAlias; 78 | 79 | #else 80 | 81 | LPWSTR TargetAlias; 82 | 83 | #endif 84 | #if ... 85 | 86 | wchar_t *UserName; 87 | 88 | #else 89 | 90 | LPWSTR UserName; 91 | 92 | #endif 93 | } CREDENTIALW, *PCREDENTIALW; 94 | */ 95 | type _CREDENTIALW struct { 96 | Flags uint32 97 | Type uint32 98 | TargetName *uint16 99 | Comment *uint16 100 | LastWritten _FILETIME 101 | CredentialBlobSize uint32 102 | CredentialBlob *byte 103 | Persist uint32 104 | AttributeCount uint32 105 | Attributes *_CREDENTIAL_ATTRIBUTEW 106 | TargetAlias *uint16 107 | UserName *uint16 108 | } 109 | 110 | const ( 111 | _CRED_TYPE_GENERIC uint32 = 0x1 112 | _CRED_TYPE_DOMAIN_PASSWORD uint32 = 0x2 113 | _CRED_TYPE_DOMAIN_CERTIFICATE uint32 = 0x3 114 | _CRED_TYPE_DOMAIN_VISIBLE_PASSWORD uint32 = 0x4 115 | _CRED_TYPE_GENERIC_CERTIFICATE uint32 = 0x5 116 | _CRED_TYPE_DOMAIN_EXTENDED uint32 = 0x6 117 | ) 118 | 119 | /* 120 | typedef struct _CREDENTIAL_ATTRIBUTEW { 121 | LPWSTR Keyword; 122 | DWORD Flags; 123 | DWORD ValueSize; 124 | LPBYTE Value; 125 | } CREDENTIAL_ATTRIBUTEW, *PCREDENTIAL_ATTRIBUTEW; 126 | */ 127 | type _CREDENTIAL_ATTRIBUTEW struct { 128 | Keyword *uint16 129 | Flags uint32 130 | ValueSize uint32 131 | Value *byte 132 | } 133 | -------------------------------------------------------------------------------- /credenumeratew_unmanaged/zsyscall_windows.go: -------------------------------------------------------------------------------- 1 | // Code generated by 'go generate'; DO NOT EDIT. 2 | 3 | package main 4 | 5 | import ( 6 | "syscall" 7 | "unsafe" 8 | 9 | "golang.org/x/sys/windows" 10 | ) 11 | 12 | var _ unsafe.Pointer 13 | 14 | // Do the interface allocations only once for common 15 | // Errno values. 16 | const ( 17 | errnoERROR_IO_PENDING = 997 18 | ) 19 | 20 | var ( 21 | errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) 22 | errERROR_EINVAL error = syscall.EINVAL 23 | ) 24 | 25 | // errnoErr returns common boxed Errno values, to prevent 26 | // allocations at runtime. 27 | func errnoErr(e syscall.Errno) error { 28 | switch e { 29 | case 0: 30 | return errERROR_EINVAL 31 | case errnoERROR_IO_PENDING: 32 | return errERROR_IO_PENDING 33 | } 34 | // TODO: add more here, after collecting data on the common 35 | // error values see on Windows. (perhaps when running 36 | // all.bat?) 37 | return e 38 | } 39 | 40 | var ( 41 | modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") 42 | modkernel32 = windows.NewLazySystemDLL("kernel32.dll") 43 | 44 | procCredEnumerateW = modadvapi32.NewProc("CredEnumerateW") 45 | procCredFree = modadvapi32.NewProc("CredFree") 46 | procFileTimeToSystemTime = modkernel32.NewProc("FileTimeToSystemTime") 47 | ) 48 | 49 | func CredEnumerateW(filter *uint16, flags uint32, count *uint32, credentials ***_CREDENTIALW) (err error) { 50 | r1, _, e1 := syscall.Syscall6(procCredEnumerateW.Addr(), 4, uintptr(unsafe.Pointer(filter)), uintptr(flags), uintptr(unsafe.Pointer(count)), uintptr(unsafe.Pointer(credentials)), 0, 0) 51 | if r1 == 0 { 52 | err = errnoErr(e1) 53 | } 54 | return 55 | } 56 | 57 | func CredFree(buffer unsafe.Pointer) { 58 | syscall.Syscall(procCredFree.Addr(), 1, uintptr(buffer), 0, 0) 59 | return 60 | } 61 | 62 | func FileTimeToSystemTime(fileTime *_FILETIME, systemTime *_SYSTEMTIME) (err error) { 63 | r1, _, e1 := syscall.Syscall(procFileTimeToSystemTime.Addr(), 2, uintptr(unsafe.Pointer(fileTime)), uintptr(unsafe.Pointer(systemTime)), 0) 64 | if r1 == 0 { 65 | err = errnoErr(e1) 66 | } 67 | return 68 | } 69 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/justenwalker/gophercon-2020-winapi 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/manifoldco/promptui v0.9.0 7 | golang.org/x/sys v0.7.0 8 | golang.org/x/tools v0.8.0 9 | ) 10 | 11 | require ( 12 | github.com/chzyer/readline v1.5.1 // indirect 13 | golang.org/x/mod v0.10.0 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 2 | github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= 3 | github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= 4 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 5 | github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= 6 | github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= 7 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 8 | github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= 9 | github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= 10 | github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= 11 | github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= 12 | golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= 13 | golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 14 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 15 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 16 | golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 17 | golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= 18 | golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 19 | golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= 20 | golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= 21 | -------------------------------------------------------------------------------- /logon/README.md: -------------------------------------------------------------------------------- 1 | # LogonUser 2 | 3 | This example program that validates the user's credentials and displays the account information using the [`LogonUserW`](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonuserw) API. 4 | 5 | When you run the program, it will prompt for a Username and Password. If the password validates correctly, it will use your access token to look up your account and user groups and print them as JSON. 6 | 7 | ``` 8 | > logon.exe 9 | Username: Justen 10 | Password: ****** 11 | Checking Password 12 | Welcome, Justen 13 | { 14 | "sid": "S-1-5-21-0001112223-102030201-1029384756-1001", 15 | "name": "Justen", 16 | "domain": "COMPUTER", 17 | "type": "User", 18 | "groups": [ 19 | { 20 | "sid": "S-1-16-12288", 21 | "name": "High Mandatory Level", 22 | "domain": "Mandatory Label", 23 | "type": "Label" 24 | }, 25 | { 26 | "sid": "S-1-1-0", 27 | "name": "Everyone", 28 | "type": "WellKnownGroup" 29 | }, 30 | { 31 | "sid": "S-1-5-114", 32 | "name": "Local account and member of Administrators group", 33 | "domain": "NT AUTHORITY", 34 | "type": "WellKnownGroup" 35 | }, 36 | ... 37 | ] 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /logon/logon.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.org/x/sys/windows" 7 | ) 8 | 9 | type User struct { 10 | SID string `json:"sid"` 11 | Name string `json:"name"` 12 | Domain string `json:"domain,omitempty"` 13 | Type string `json:"type"` 14 | Groups []Group `json:"groups,omitempty"` 15 | } 16 | 17 | type Group struct { 18 | SID string `json:"sid"` 19 | Name string `json:"name"` 20 | Domain string `json:"domain,omitempty"` 21 | Type string `json:"type"` 22 | } 23 | 24 | func LogonUser(username, domain, password string) (*User, error) { 25 | var ( 26 | d *uint16 27 | u *uint16 28 | p *uint16 29 | ) 30 | if domain != "" { 31 | d, _ = windows.UTF16PtrFromString(domain) 32 | } 33 | if username != "" { 34 | u, _ = windows.UTF16PtrFromString(username) 35 | } 36 | if password != "" { 37 | p, _ = windows.UTF16PtrFromString(password) 38 | } 39 | token, err := lookupUser(u, d, p) 40 | if err != nil { 41 | return nil, err 42 | } 43 | defer token.Close() 44 | var user User 45 | tokenuser, err := token.GetTokenUser() 46 | if err != nil { 47 | return nil, fmt.Errorf("GetTokenUser: %w", err) 48 | } 49 | acc, domain, use, err := tokenuser.User.Sid.LookupAccount("") 50 | if err != nil { 51 | return nil, fmt.Errorf("LookupAccount(user=%s): %w", tokenuser.User.Sid.String(), err) 52 | } 53 | user.SID = tokenuser.User.Sid.String() 54 | user.Name = acc 55 | user.Type = SidType(use).Name() 56 | user.Domain = domain 57 | 58 | tokengroups, err := token.GetTokenGroups() 59 | if err != nil { 60 | return nil, fmt.Errorf("GetTokenGroups: %w", err) 61 | } 62 | for _, tokengroup := range tokengroups.AllGroups() { 63 | acc, domain, use, err := tokengroup.Sid.LookupAccount("") 64 | if err != nil { 65 | return nil, fmt.Errorf("LookupAccount(group=%s): %w", tokengroup.Sid.String(), err) 66 | } 67 | user.Groups = append(user.Groups, Group{ 68 | SID: tokengroup.Sid.String(), 69 | Name: acc, 70 | Domain: domain, 71 | Type: SidType(use).Name(), 72 | }) 73 | } 74 | return &user, nil 75 | } 76 | 77 | func lookupUser(u *uint16, d *uint16, p *uint16) (*windows.Token, error) { 78 | var token windows.Token 79 | err := _LogonUser(u, d, p, _LOGON32_LOGON_NETWORK, _LOGON32_PROVIDER_DEFAULT, &token) 80 | if err == nil { 81 | // default lookup succeeded 82 | return &token, nil 83 | } 84 | // if not, try NEW_CREDENTIALS instead 85 | if err := _LogonUser(u, d, p, _LOGON32_LOGON_NEW_CREDENTIALS, _LOGON32_PROVIDER_WINNT50, &token); err == nil { 86 | return &token, nil 87 | } 88 | return nil, err 89 | } 90 | -------------------------------------------------------------------------------- /logon/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "github.com/manifoldco/promptui" 10 | ) 11 | 12 | //go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go syscall_windows.go 13 | 14 | func main() { 15 | var domain string 16 | username, err := readLine("Username", false) 17 | die("failed to read username", err) 18 | password, err := readLine("Password", true) 19 | die("failed to read password", err) 20 | fmt.Println("Checking Password") 21 | if i := strings.IndexByte(username, '\\'); i != -1 { 22 | username, domain = string(username[0:i]), string(username[i+1:]) 23 | } 24 | user, err := LogonUser(username, domain, password) 25 | die("Login failed", err) 26 | fmt.Printf("Welcome, %s\n", user.Name) 27 | js, _ := json.MarshalIndent(user, "", " ") 28 | fmt.Printf("%s\n", string(js)) 29 | } 30 | 31 | func die(msg string, err error) { 32 | if err != nil { 33 | fmt.Fprintln(os.Stderr, "Login failed:", err) 34 | os.Exit(1) 35 | } 36 | } 37 | 38 | func readLine(label string, secret bool) (string, error) { 39 | prompt := promptui.Prompt{ 40 | Label: label, 41 | HideEntered: true, 42 | } 43 | if secret { 44 | prompt.Mask = '*' 45 | } 46 | result, err := prompt.Run() 47 | 48 | if err != nil { 49 | return "", fmt.Errorf("prompt failed: %w", err) 50 | } 51 | return result, nil 52 | } 53 | -------------------------------------------------------------------------------- /logon/sidtype_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=SidType"; DO NOT EDIT. 2 | 3 | package main 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[SidTypeUser-1] 12 | _ = x[SidTypeGroup-2] 13 | _ = x[SidTypeDomain-3] 14 | _ = x[SidTypeAlias-4] 15 | _ = x[SidTypeWellKnownGroup-5] 16 | _ = x[SidTypeDeletedAccount-6] 17 | _ = x[SidTypeInvalid-7] 18 | _ = x[SidTypeUnknown-8] 19 | _ = x[SidTypeComputer-9] 20 | _ = x[SidTypeLabel-10] 21 | _ = x[SidTypeLogonSession-11] 22 | } 23 | 24 | const _SidType_name = "SidTypeUserSidTypeGroupSidTypeDomainSidTypeAliasSidTypeWellKnownGroupSidTypeDeletedAccountSidTypeInvalidSidTypeUnknownSidTypeComputerSidTypeLabelSidTypeLogonSession" 25 | 26 | var _SidType_index = [...]uint8{0, 11, 23, 36, 48, 69, 90, 104, 118, 133, 145, 164} 27 | 28 | func (i SidType) String() string { 29 | i -= 1 30 | if i >= SidType(len(_SidType_index)-1) { 31 | return "SidType(" + strconv.FormatInt(int64(i+1), 10) + ")" 32 | } 33 | return _SidType_name[_SidType_index[i]:_SidType_index[i+1]] 34 | } 35 | -------------------------------------------------------------------------------- /logon/syscall_windows.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "strings" 4 | 5 | // BOOL LogonUserA( 6 | // LPCWSTR lpszUsername, 7 | // LPCWSTR lpszDomain, 8 | // LPCWSTR lpszPassword, 9 | // DWORD dwLogonType, 10 | // DWORD dwLogonProvider, 11 | // PHANDLE phToken 12 | // ); 13 | // 14 | //sys _LogonUser(username *uint16, domain *uint16, password *uint16, logonType uint32, logonProvider uint32, token *windows.Token) (err error) = advapi32.LogonUserW 15 | 16 | // Logon types 17 | const ( 18 | _LOGON32_LOGON_INTERACTIVE uint32 = 2 19 | _LOGON32_LOGON_NETWORK uint32 = 3 20 | _LOGON32_LOGON_BATCH uint32 = 4 21 | _LOGON32_LOGON_SERVICE uint32 = 5 22 | _LOGON32_LOGON_UNLOCK uint32 = 7 23 | _LOGON32_LOGON_NETWORK_CLEARTEXT uint32 = 8 24 | _LOGON32_LOGON_NEW_CREDENTIALS uint32 = 9 25 | ) 26 | 27 | // Logon providers 28 | const ( 29 | _LOGON32_PROVIDER_DEFAULT uint32 = 0 30 | _LOGON32_PROVIDER_WINNT40 uint32 = 2 31 | _LOGON32_PROVIDER_WINNT50 uint32 = 3 32 | ) 33 | 34 | //go:generate go run golang.org/x/tools/cmd/stringer -type=SidType 35 | type SidType uint32 36 | 37 | const ( 38 | SidTypeUser SidType = iota + 1 39 | SidTypeGroup 40 | SidTypeDomain 41 | SidTypeAlias 42 | SidTypeWellKnownGroup 43 | SidTypeDeletedAccount 44 | SidTypeInvalid 45 | SidTypeUnknown 46 | SidTypeComputer 47 | SidTypeLabel 48 | SidTypeLogonSession 49 | ) 50 | 51 | func (s SidType) Name() string { 52 | return strings.TrimPrefix(s.String(), "SidType") 53 | } 54 | -------------------------------------------------------------------------------- /logon/zsyscall_windows.go: -------------------------------------------------------------------------------- 1 | // Code generated by 'go generate'; DO NOT EDIT. 2 | 3 | package main 4 | 5 | import ( 6 | "syscall" 7 | "unsafe" 8 | 9 | "golang.org/x/sys/windows" 10 | ) 11 | 12 | var _ unsafe.Pointer 13 | 14 | // Do the interface allocations only once for common 15 | // Errno values. 16 | const ( 17 | errnoERROR_IO_PENDING = 997 18 | ) 19 | 20 | var ( 21 | errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) 22 | errERROR_EINVAL error = syscall.EINVAL 23 | ) 24 | 25 | // errnoErr returns common boxed Errno values, to prevent 26 | // allocations at runtime. 27 | func errnoErr(e syscall.Errno) error { 28 | switch e { 29 | case 0: 30 | return errERROR_EINVAL 31 | case errnoERROR_IO_PENDING: 32 | return errERROR_IO_PENDING 33 | } 34 | // TODO: add more here, after collecting data on the common 35 | // error values see on Windows. (perhaps when running 36 | // all.bat?) 37 | return e 38 | } 39 | 40 | var ( 41 | modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") 42 | 43 | procLogonUserW = modadvapi32.NewProc("LogonUserW") 44 | ) 45 | 46 | func _LogonUser(username *uint16, domain *uint16, password *uint16, logonType uint32, logonProvider uint32, token *windows.Token) (err error) { 47 | r1, _, e1 := syscall.Syscall6(procLogonUserW.Addr(), 6, uintptr(unsafe.Pointer(username)), uintptr(unsafe.Pointer(domain)), uintptr(unsafe.Pointer(password)), uintptr(logonType), uintptr(logonProvider), uintptr(unsafe.Pointer(token))) 48 | if r1 == 0 { 49 | err = errnoErr(e1) 50 | } 51 | return 52 | } 53 | -------------------------------------------------------------------------------- /netstat/README.md: -------------------------------------------------------------------------------- 1 | # netstat 2 | 3 | A toy netstat re-implementation using [`GetExtendedTCPTable`](https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getextendedtcptable). 4 | 5 | It also defines the relevant API Structs: 6 | 7 | - [`MIB_TCPTABLE_OWNER_PID`](https://docs.microsoft.com/en-us/windows/win32/api/tcpmib/ns-tcpmib-mib_tcptable_owner_pid) 8 | - [`MIB_TCPROW_OWNER_PID`](https://docs.microsoft.com/en-us/windows/win32/api/tcpmib/ns-tcpmib-mib_tcprow_owner_pid) 9 | 10 | This demonstrates a type of API memory exchange where the API call expects to be given a buffer to write into. In this example, we allocated a buffer in Go's managed memory, and the API will write into the buffer 11 | if it is large enough, or it will request more memory if it is too small. We repeat the process of growing the bufer 12 | and calling the API function until it succeeds and, in this case, gives back a TCP connection table. 13 | 14 | ``` 15 | > netstat.exe 16 | t> .\netstat.exe 17 | PID LOCAL REMOTE STATE 18 | 14432 127.0.0.1:2015 LISTEN 19 | 14432 127.0.0.1:2015 127.0.0.1:54550 ESTABLISHED 20 | 5384 127.0.0.1:49671 127.0.0.1:49672 ESTABLISHED 21 | 5384 127.0.0.1:49672 127.0.0.1:49671 ESTABLISHED 22 | 23 | ... 24 | ``` 25 | 26 | A slightly simpler example of calling APIs like this can be found in [networkparams](../networkparams). -------------------------------------------------------------------------------- /netstat/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | //go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go syscall_windows.go 9 | 10 | func main() { 11 | rows, err := GetTCPTable() 12 | if err != nil { 13 | log.Fatalf("error: %v", err) 14 | } 15 | fmt.Printf("%-4s\t%-21s\t%-21s\t%s\n", "PID", "LOCAL", "REMOTE", "STATE") 16 | for _, row := range rows { 17 | remote := row.Remote.String() 18 | if remote == ":0" { 19 | remote = "" 20 | } 21 | fmt.Printf("% 4d\t%-21s\t%-21s\t%s\n", row.PID, row.Local, remote, row.State) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /netstat/syscall_windows.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //sys _GetExtendedTcpTable(tcpTable *byte, tableSize *uint32, order bool, ulAf uint32, tableClass _TCP_TABLE_CLASS, reserved uint32) (ret syscall.Errno) = iphlpapi.GetExtendedTcpTable 4 | -------------------------------------------------------------------------------- /netstat/tcpstate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type TcpState int 4 | 5 | const ( 6 | Closed = TcpState(_MIB_TCP_STATE_CLOSED) 7 | Listen = TcpState(_MIB_TCP_STATE_LISTEN) 8 | SynSent = TcpState(_MIB_TCP_STATE_SYN_SENT) 9 | SynReceived = TcpState(_MIB_TCP_STATE_SYN_RCVD) 10 | Established = TcpState(_MIB_TCP_STATE_ESTAB) 11 | FinWait1 = TcpState(_MIB_TCP_STATE_FIN_WAIT1) 12 | FinWait2 = TcpState(_MIB_TCP_STATE_FIN_WAIT2) 13 | CloseWait = TcpState(_MIB_TCP_STATE_CLOSE_WAIT) 14 | Closing = TcpState(_MIB_TCP_STATE_CLOSING) 15 | LastAck = TcpState(_MIB_TCP_STATE_LAST_ACK) 16 | TimeWait = TcpState(_MIB_TCP_STATE_TIME_WAIT) 17 | DeleteTCB = TcpState(_MIB_TCP_STATE_DELETE_TCB) 18 | ) 19 | 20 | func (s TcpState) String() string { 21 | switch s { 22 | case Closed: 23 | return "CLOSED" 24 | case Listen: 25 | return "LISTEN" 26 | case SynSent: 27 | return "SYN_SENT" 28 | case SynReceived: 29 | return "SYN_RCVD" 30 | case Established: 31 | return "ESTABLISHED" 32 | case FinWait1: 33 | return "FIN_WAIT1" 34 | case FinWait2: 35 | return "FIN_WAIT2" 36 | case CloseWait: 37 | return "CLOSE_WAIT" 38 | case Closing: 39 | return "CLOSING" 40 | case LastAck: 41 | return "LAST_ACK" 42 | case TimeWait: 43 | return "TIME_WAIT" 44 | case DeleteTCB: 45 | return "DELETE_TCB" 46 | default: 47 | return "UNKNOWN" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /netstat/tcptable.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/binary" 5 | "net" 6 | "unsafe" 7 | ) 8 | 9 | type TableRow struct { 10 | Local *net.TCPAddr 11 | Remote *net.TCPAddr 12 | State TcpState 13 | PID int 14 | } 15 | 16 | func GetTCPTable() ([]TableRow, error) { 17 | var tableSize uint32 18 | var tcpTable []byte 19 | // query for the buffer size by sending null/0 20 | ret := _GetExtendedTcpTable(nil, &tableSize, true, _AF_INET4, _TCP_TABLE_OWNER_PID_ALL, 0) 21 | for { 22 | if ret == _NO_ERROR { 23 | break 24 | } 25 | if ret == _ERROR_INSUFFICIENT_BUFFER { 26 | tcpTable = make([]byte, int(tableSize)) 27 | ret = _GetExtendedTcpTable(&tcpTable[0], &tableSize, true, _AF_INET4, _TCP_TABLE_OWNER_PID_ALL, 0) 28 | continue 29 | } 30 | return nil, ret 31 | } 32 | return convertToTableRows(tcpTable), nil 33 | } 34 | 35 | func convertToTableRows(raw []byte) (result []TableRow) { 36 | if len(raw) == 0 { 37 | return 38 | } 39 | table := (*_MIB_TCPTABLE_OWNER_PID)(unsafe.Pointer(&raw[0])) 40 | n := int(table.NumEntries) 41 | if n == 0 { 42 | return 43 | } 44 | result = make([]TableRow, n) 45 | rows := unsafe.Slice(&table.Table[0], table.NumEntries) 46 | for i, row := range rows { 47 | result[i] = TableRow{ 48 | Local: convertToTCPv4Addr(row.LocalAddr, row.LocalPort), 49 | Remote: convertToTCPv4Addr(row.RemoteAddr, row.RemotePort), 50 | PID: int(row.OwningPID), 51 | State: TcpState(row.State), 52 | } 53 | } 54 | return 55 | } 56 | 57 | func convertToTCPv4Addr(i uint32, p uint32) *net.TCPAddr { 58 | var ip net.IP 59 | if i > 0 { // convert from network byte order 60 | bytes := make([]byte, 4) 61 | binary.LittleEndian.PutUint32(bytes, i) 62 | ip = bytes 63 | } 64 | var port int 65 | if p > 0 { // convert from network byte order 66 | bytes := make([]byte, 2) 67 | binary.LittleEndian.PutUint16(bytes, uint16(p)) 68 | port = int(binary.BigEndian.Uint16(bytes)) 69 | } 70 | return &net.TCPAddr{ 71 | IP: ip, 72 | Port: port, 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /netstat/types_windows.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "syscall" 4 | 5 | const ( 6 | _NO_ERROR syscall.Errno = 0 7 | _ERROR_INVALID_PARAMETER syscall.Errno = 0x57 8 | _ERROR_INSUFFICIENT_BUFFER syscall.Errno = 0x7A 9 | ) 10 | 11 | const ( 12 | _AF_INET4 uint32 = 2 13 | _AF_INET6 uint32 = 23 14 | ) 15 | 16 | type _MIB_TCP_STATE uint32 17 | 18 | const ( 19 | _MIB_TCP_STATE_CLOSED _MIB_TCP_STATE = iota + 1 20 | _MIB_TCP_STATE_LISTEN 21 | _MIB_TCP_STATE_SYN_SENT 22 | _MIB_TCP_STATE_SYN_RCVD 23 | _MIB_TCP_STATE_ESTAB 24 | _MIB_TCP_STATE_FIN_WAIT1 25 | _MIB_TCP_STATE_FIN_WAIT2 26 | _MIB_TCP_STATE_CLOSE_WAIT 27 | _MIB_TCP_STATE_CLOSING 28 | _MIB_TCP_STATE_LAST_ACK 29 | _MIB_TCP_STATE_TIME_WAIT 30 | _MIB_TCP_STATE_DELETE_TCB 31 | ) 32 | 33 | type _TCP_TABLE_CLASS int 34 | 35 | const ( 36 | _TCP_TABLE_BASIC_LISTENER _TCP_TABLE_CLASS = iota 37 | _TCP_TABLE_BASIC_CONNECTIONS 38 | _TCP_TABLE_BASIC_ALL 39 | _TCP_TABLE_OWNER_PID_LISTENER 40 | _TCP_TABLE_OWNER_PID_CONNECTIONS 41 | _TCP_TABLE_OWNER_PID_ALL 42 | _TCP_TABLE_OWNER_MODULE_LISTENER 43 | _TCP_TABLE_OWNER_MODULE_CONNECTIONS 44 | _TCP_TABLE_OWNER_MODULE_ALL 45 | ) 46 | 47 | /* 48 | typedef struct _MIB_TCPTABLE_OWNER_PID { 49 | DWORD dwNumEntries; 50 | MIB_TCPROW_OWNER_PID table[ANY_SIZE]; 51 | } MIB_TCPTABLE_OWNER_PID, *PMIB_TCPTABLE_OWNER_PID; 52 | */ 53 | type _MIB_TCPTABLE_OWNER_PID struct { 54 | NumEntries uint32 55 | Table [1]_MIB_TCPROW_OWNER_PID 56 | } 57 | 58 | /* 59 | typedef struct _MIB_TCPROW_OWNER_PID { 60 | DWORD dwState; 61 | DWORD dwLocalAddr; 62 | DWORD dwLocalPort; 63 | DWORD dwRemoteAddr; 64 | DWORD dwRemotePort; 65 | DWORD dwOwningPid; 66 | } MIB_TCPROW_OWNER_PID, *PMIB_TCPROW_OWNER_PID; 67 | */ 68 | type _MIB_TCPROW_OWNER_PID struct { 69 | State uint32 70 | LocalAddr uint32 71 | LocalPort uint32 72 | RemoteAddr uint32 73 | RemotePort uint32 74 | OwningPID uint32 75 | } 76 | -------------------------------------------------------------------------------- /netstat/zsyscall_windows.go: -------------------------------------------------------------------------------- 1 | // Code generated by 'go generate'; DO NOT EDIT. 2 | 3 | package main 4 | 5 | import ( 6 | "syscall" 7 | "unsafe" 8 | 9 | "golang.org/x/sys/windows" 10 | ) 11 | 12 | var _ unsafe.Pointer 13 | 14 | // Do the interface allocations only once for common 15 | // Errno values. 16 | const ( 17 | errnoERROR_IO_PENDING = 997 18 | ) 19 | 20 | var ( 21 | errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) 22 | errERROR_EINVAL error = syscall.EINVAL 23 | ) 24 | 25 | // errnoErr returns common boxed Errno values, to prevent 26 | // allocations at runtime. 27 | func errnoErr(e syscall.Errno) error { 28 | switch e { 29 | case 0: 30 | return errERROR_EINVAL 31 | case errnoERROR_IO_PENDING: 32 | return errERROR_IO_PENDING 33 | } 34 | // TODO: add more here, after collecting data on the common 35 | // error values see on Windows. (perhaps when running 36 | // all.bat?) 37 | return e 38 | } 39 | 40 | var ( 41 | modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll") 42 | 43 | procGetExtendedTcpTable = modiphlpapi.NewProc("GetExtendedTcpTable") 44 | ) 45 | 46 | func _GetExtendedTcpTable(tcpTable *byte, tableSize *uint32, order bool, ulAf uint32, tableClass _TCP_TABLE_CLASS, reserved uint32) (ret syscall.Errno) { 47 | var _p0 uint32 48 | if order { 49 | _p0 = 1 50 | } 51 | r0, _, _ := syscall.Syscall6(procGetExtendedTcpTable.Addr(), 6, uintptr(unsafe.Pointer(tcpTable)), uintptr(unsafe.Pointer(tableSize)), uintptr(_p0), uintptr(ulAf), uintptr(tableClass), uintptr(reserved)) 52 | ret = syscall.Errno(r0) 53 | return 54 | } 55 | -------------------------------------------------------------------------------- /networkparams/README.md: -------------------------------------------------------------------------------- 1 | # Network Params 2 | 3 | This program lists your computer's network parameters using [`GetNetworkParams`](https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getnetworkparams) 4 | 5 | It also defines the relevant API Structs: 6 | 7 | - [`FIXED_INFO`](https://docs.microsoft.com/en-us/windows/win32/api/iptypes/ns-iptypes-fixed_info_w2ksp1) 8 | - [`IP_ADDR_STRING `](https://docs.microsoft.com/en-us/windows/win32/api/iptypes/ns-iptypes-ip_addr_string) 9 | 10 | The interesting bits are the Host Name, Domain Name (dns search domain), and the list of DNS servers your computer uses for name resolution. 11 | This an example of providing a buffer to an API that has a different error to check for insufficient buffer size vs [netstat](../netstat). 12 | 13 | ``` 14 | > networkparams.exe 15 | Host Name: computer01 16 | Domain Name: 17 | DNS Servers: 18 | 192.168.1.1 19 | Node Type: Hybrid 20 | Routing: disabled 21 | ARP Proxy: disabled 22 | DNS: disabled 23 | ``` -------------------------------------------------------------------------------- /networkparams/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | // This implements the example program 9 | // https://docs.microsoft.com/en-us/windows/win32/api/iptypes/ns-iptypes-fixed_info_w2ksp1#examples 10 | 11 | //go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go syscall_windows.go 12 | 13 | func main() { 14 | np, err := GetNetworkParams() 15 | if err != nil { 16 | fmt.Println("GetNetworkParams:", err) 17 | os.Exit(1) 18 | } 19 | fmt.Println("Host Name:", np.HostName) 20 | fmt.Println("Domain Name:", np.DomainName) 21 | fmt.Println("DNS Servers:") 22 | for _, addr := range np.DNSServerList { 23 | fmt.Printf("\t%s\n", addr) 24 | } 25 | fmt.Println("Node Type:", np.NodeType) 26 | if np.EnableRouting { 27 | fmt.Println("Routing: enabled") 28 | } else { 29 | fmt.Println("Routing: disabled") 30 | } 31 | if np.EnableProxy { 32 | fmt.Println("ARP Proxy: enabled") 33 | } else { 34 | fmt.Println("ARP Proxy: disabled") 35 | } 36 | if np.EnableDNS { 37 | fmt.Println("DNS: enabled") 38 | } else { 39 | fmt.Println("DNS: disabled") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /networkparams/networkparams.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "strconv" 6 | "unsafe" 7 | ) 8 | 9 | type NodeType int 10 | 11 | const ( 12 | BroadcastNodeType = NodeType(_BROADCAST_NODETYPE) 13 | PeerToPeerNodeType = NodeType(_PEER_TO_PEER_NODETYPE) 14 | MixedNodeType = NodeType(_MIXED_NODETYPE) 15 | HybridNodeType = NodeType(_HYBRID_NODETYPE) 16 | ) 17 | 18 | func (n NodeType) String() string { 19 | switch n { 20 | case BroadcastNodeType: 21 | return "Broadcast" 22 | case PeerToPeerNodeType: 23 | return "Peer to Peer" 24 | case MixedNodeType: 25 | return "Mixed" 26 | case HybridNodeType: 27 | return "Hybrid" 28 | } 29 | return "Unknown " + strconv.FormatUint(uint64(n), 10) 30 | } 31 | 32 | type NetworkParams struct { 33 | HostName string 34 | DomainName string 35 | DNSServerList []net.IP 36 | NodeType NodeType 37 | ScopeID string 38 | EnableRouting bool 39 | EnableProxy bool 40 | EnableDNS bool 41 | } 42 | 43 | func GetNetworkParams() (*NetworkParams, error) { 44 | bufSize := uint32(unsafe.Sizeof(_FIXED_INFO{})) 45 | buffer := make([]byte, int(bufSize)) 46 | for { 47 | ret := _GetNetworkParams(&buffer[0], &bufSize) 48 | if ret == _ERROR_SUCCESS { 49 | break 50 | } 51 | if ret != _ERROR_BUFFER_OVERFLOW { 52 | return nil, ret 53 | } 54 | buffer = make([]byte, int(bufSize)) 55 | } 56 | pFixedInfo := (*_FIXED_INFO)(unsafe.Pointer(&buffer[0])) 57 | return convertToNetworkParams(pFixedInfo) 58 | } 59 | 60 | func convertToNetworkParams(pFixedInfo *_FIXED_INFO) (*NetworkParams, error) { 61 | var np NetworkParams 62 | np.HostName = sliceToString(pFixedInfo.HostName[:]) 63 | np.DomainName = sliceToString(pFixedInfo.DomainName[:]) 64 | for ipAddr := &pFixedInfo.DnsServerList; ipAddr != nil; ipAddr = ipAddr.Next { 65 | np.DNSServerList = append(np.DNSServerList, net.ParseIP(sliceToString(ipAddr.IpAddress[:])).To4()) 66 | } 67 | np.NodeType = NodeType(pFixedInfo.NodeType) 68 | np.ScopeID = sliceToString(pFixedInfo.ScopeId[:]) 69 | np.EnableRouting = pFixedInfo.EnableRouting != 0 70 | np.EnableProxy = pFixedInfo.EnableProxy != 0 71 | np.EnableDNS = pFixedInfo.EnableDNS != 0 72 | return &np, nil 73 | } 74 | 75 | func sliceToString(b []byte) string { 76 | for i := range b { 77 | if b[i] == 0 { 78 | return string(b[0:i]) 79 | } 80 | } 81 | return string(b) 82 | } 83 | -------------------------------------------------------------------------------- /networkparams/syscall_windows.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | IPHLPAPI_DLL_LINKAGE DWORD GetNetworkParams( 5 | PFIXED_INFO pFixedInfo, 6 | PULONG pOutBufLen 7 | ); 8 | */ 9 | 10 | //sys _GetNetworkParams(fixedInfo *byte, outBufLen *uint32) (ret syscall.Errno) = iphlpapi.GetNetworkParams 11 | -------------------------------------------------------------------------------- /networkparams/types_windows.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "syscall" 4 | 5 | const ( 6 | _ERROR_SUCCESS syscall.Errno = 0 7 | _ERROR_BUFFER_OVERFLOW syscall.Errno = 0x6F 8 | ) 9 | 10 | const ( 11 | _MAX_HOSTNAME_LEN = 128 12 | _MAX_DOMAIN_LEN = 128 13 | _MAX_SCOPE_ID_LEN = 256 14 | ) 15 | 16 | const ( 17 | _BROADCAST_NODETYPE uint32 = 0x0001 18 | _PEER_TO_PEER_NODETYPE uint32 = 0x0002 19 | _MIXED_NODETYPE uint32 = 0x0004 20 | _HYBRID_NODETYPE uint32 = 0x0008 21 | ) 22 | 23 | /* 24 | typedef struct { 25 | char HostName[MAX_HOSTNAME_LEN + 4]; 26 | char DomainName[MAX_DOMAIN_NAME_LEN + 4]; 27 | PIP_ADDR_STRING CurrentDnsServer; 28 | IP_ADDR_STRING DnsServerList; 29 | UINT NodeType; 30 | char ScopeId[MAX_SCOPE_ID_LEN + 4]; 31 | UINT EnableRouting; 32 | UINT EnableProxy; 33 | UINT EnableDns; 34 | } FIXED_INFO_W2KSP1, *PFIXED_INFO_W2KSP1; 35 | */ 36 | type _FIXED_INFO struct { 37 | HostName [_MAX_HOSTNAME_LEN + 4]byte 38 | DomainName [_MAX_DOMAIN_LEN + 4]byte 39 | CurrentDnsServer *_IP_ADDR_STRING 40 | DnsServerList _IP_ADDR_STRING 41 | NodeType uint32 42 | ScopeId [_MAX_SCOPE_ID_LEN + 4]byte 43 | EnableRouting uint32 44 | EnableProxy uint32 45 | EnableDNS uint32 46 | } 47 | 48 | /* 49 | typedef struct _IP_ADDR_STRING { 50 | struct _IP_ADDR_STRING *Next; 51 | IP_ADDRESS_STRING IpAddress; 52 | IP_MASK_STRING IpMask; 53 | DWORD Context; 54 | } IP_ADDR_STRING, *PIP_ADDR_STRING; 55 | */ 56 | type _IP_ADDR_STRING struct { 57 | Next *_IP_ADDR_STRING 58 | IpAddress _IP_ADDRESS_STRING 59 | IpMask _IP_ADDRESS_STRING 60 | Context uint32 61 | } 62 | 63 | type _IP_ADDRESS_STRING [16]byte 64 | -------------------------------------------------------------------------------- /networkparams/zsyscall_windows.go: -------------------------------------------------------------------------------- 1 | // Code generated by 'go generate'; DO NOT EDIT. 2 | 3 | package main 4 | 5 | import ( 6 | "syscall" 7 | "unsafe" 8 | 9 | "golang.org/x/sys/windows" 10 | ) 11 | 12 | var _ unsafe.Pointer 13 | 14 | // Do the interface allocations only once for common 15 | // Errno values. 16 | const ( 17 | errnoERROR_IO_PENDING = 997 18 | ) 19 | 20 | var ( 21 | errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) 22 | errERROR_EINVAL error = syscall.EINVAL 23 | ) 24 | 25 | // errnoErr returns common boxed Errno values, to prevent 26 | // allocations at runtime. 27 | func errnoErr(e syscall.Errno) error { 28 | switch e { 29 | case 0: 30 | return errERROR_EINVAL 31 | case errnoERROR_IO_PENDING: 32 | return errERROR_IO_PENDING 33 | } 34 | // TODO: add more here, after collecting data on the common 35 | // error values see on Windows. (perhaps when running 36 | // all.bat?) 37 | return e 38 | } 39 | 40 | var ( 41 | modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll") 42 | 43 | procGetNetworkParams = modiphlpapi.NewProc("GetNetworkParams") 44 | ) 45 | 46 | func _GetNetworkParams(fixedInfo *byte, outBufLen *uint32) (ret syscall.Errno) { 47 | r0, _, _ := syscall.Syscall(procGetNetworkParams.Addr(), 2, uintptr(unsafe.Pointer(fixedInfo)), uintptr(unsafe.Pointer(outBufLen)), 0) 48 | ret = syscall.Errno(r0) 49 | return 50 | } 51 | -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | package tools 5 | 6 | import ( 7 | _ "golang.org/x/tools/cmd/stringer" 8 | ) 9 | -------------------------------------------------------------------------------- /unsafe_cast/README.md: -------------------------------------------------------------------------------- 1 | # Unsafe Cast 2 | 3 | This is an example of using `unsafe.Pointer` to convert a type between two different forms (like a C union). 4 | This is legal because the two types are the same memory size, so when you dereference them, you are not extending into 5 | unallocated memory. 6 | 7 | You can always cast to a value where the size is equal to or less than the original size, 8 | but the result may be surprising. 9 | 10 | For example, take note of the values you get when it is run. 11 | Given `t1.a=0xC0DECAFE`, you might expect `t2.a` to be `0xC0DE` and t2.b to be `0xCAFE`, 12 | but instead you get: 13 | 14 | ``` 15 | t1.a=0xC0DECAFE 16 | 17 | t2.a=0xCAFE 18 | t2.b=0xC0DE 19 | ``` 20 | 21 | Why? 22 | 23 | This is the [Endianness](https://en.wikipedia.org/wiki/Endianness) of the memory sequencing mode of the CPU leaking through. 24 | The memory for `T1` is actually laid-out physically as "Little Endian", or Lower-order bytes first (Intel x86/amd64 are LE). 25 | 26 | ``` 27 | bytes 0 1 2 3 28 | FE CA DE 0C 29 | |--|--|--|--| 30 | \ / 31 | \_________/ 32 | | 33 | t1.a 34 | 0xC0DECAFE 35 | ``` 36 | 37 | So when we access them as T2, those fields correspond to 38 | 39 | ``` 40 | bytes 0 1 2 3 41 | FE CA DE 0C 42 | |--|--|--|--| 43 | / / \ \ 44 | /_____/ \_____\ 45 | | | 46 | t2.a t2.b 47 | 0xCAFE 0xCODE 48 | ``` 49 | 50 | When printed, they display as "Big Endian" (Higher order bytes first) 51 | which is more natural for a human to read. This mode also happens to be "Network Byte Order", the agreed-upon byte ordering when transmitting data over a network. 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /unsafe_cast/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "unsafe" 6 | ) 7 | 8 | type T1 struct { 9 | a uint32 10 | } 11 | type T2 struct { 12 | a uint16 13 | b uint16 14 | } 15 | 16 | func main() { 17 | t1 := T1{a: 0xC0DECAFE} 18 | t2 := *(*T2)(unsafe.Pointer(&t1)) 19 | fmt.Printf("sizeof(T1)=%d\n", unsafe.Sizeof(t1)) 20 | fmt.Printf("sizeof(T2)=%d\n", unsafe.Sizeof(t2)) 21 | fmt.Println() 22 | fmt.Printf("t1.a=0x%X\n\n", t1.a) 23 | fmt.Printf("t2.a=0x%X\nt2.b=0x%X\n\n", t2.a, t2.b) 24 | // Output: 25 | //sizeof(T1)=4 26 | //sizeof(T2)=4 27 | // 28 | //t1.a=0xC0DECAFE 29 | // 30 | //t2.a=0xCAFE 31 | //t2.b=0xC0DE 32 | } 33 | -------------------------------------------------------------------------------- /volumes/README.md: -------------------------------------------------------------------------------- 1 | # List Volumes 2 | 3 | This example enumerates all volumes and gets information about them such as their DOS device name, and paths. 4 | 5 | This is pretty much a Go version of [Displaying Volume Paths](https://docs.microsoft.com/en-us/windows/win32/fileio/displaying-volume-paths) 6 | 7 | APIs Used: 8 | 9 | - For Enumerating Volumes: 10 | - [FindFirstVolume](https://docs.microsoft.com/en-us/windows/desktop/api/FileAPI/nf-fileapi-findfirstvolumew) 11 | - [FindNextVolume](https://docs.microsoft.com/en-us/windows/desktop/api/FileAPI/nf-fileapi-findnextvolumew) 12 | - [FindVolumeClose](https://docs.microsoft.com/en-us/windows/desktop/api/FileAPI/nf-fileapi-findvolumeclose) 13 | - For getting DOS Device Name: [QueryDosDevice](https://docs.microsoft.com/en-us/windows/desktop/api/FileAPI/nf-fileapi-querydosdevicew) 14 | - For getting volume paths: [GetVolumePathNamesForVolumeName](https://docs.microsoft.com/en-us/windows/desktop/api/FileAPI/nf-fileapi-getvolumepathnamesforvolumenamew) 15 | 16 | 17 | ```plain 18 | > volumes.exe 19 | Volume: \\?\Volume{60d6e9ac-9b32-4fa2-8060-d494beb9ad1a}\ 20 | DOS Device: \Device\HarddiskVolume2 21 | Paths: 22 | - C:\ 23 | 24 | Volume: \\?\Volume{eb0860c9-b2b1-47ea-af36-d89b77d463b5}\ 25 | DOS Device: \Device\HarddiskVolume1 26 | Paths: 27 | - D:\ 28 | 29 | Volume: \\?\Volume{18e402ba-23a0-11eb-9956-5c80b6c3d0bf}\ 30 | DOS Device: \Device\CdRom0 31 | Paths: 32 | - E:\ 33 | ``` -------------------------------------------------------------------------------- /volumes/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | func main() { 9 | vis, err := EnumerateVolumeInfo() 10 | if err != nil { 11 | log.Fatalln("EnumerateVolumeInfo error", err) 12 | } 13 | for _, vi := range vis { 14 | fmt.Println("Volume:", vi.Volume) 15 | fmt.Println("DOS Device:", vi.DOSDevice) 16 | if len(vi.Paths) > 0 { 17 | fmt.Println("Paths:") 18 | for _, path := range vi.Paths { 19 | fmt.Println(" -", path) 20 | } 21 | } 22 | fmt.Println("") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /volumes/volumes.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "golang.org/x/sys/windows" 6 | "strings" 7 | "unicode/utf16" 8 | ) 9 | 10 | type VolumeInfo struct { 11 | Volume string 12 | DOSDevice string 13 | Paths []string 14 | } 15 | 16 | // EnumerateVolumeInfo enumerates all of the volumes in the system, returning a slice of VolumeInfo for each of them. 17 | func EnumerateVolumeInfo() ([]VolumeInfo, error) { 18 | vols, err := EnumerateVolumes() 19 | if err != nil { 20 | return nil, fmt.Errorf("error enumerating volumes: %w", err) 21 | } 22 | infos := make([]VolumeInfo, 0, len(vols)) 23 | for _, vol := range vols { 24 | vi := VolumeInfo{ 25 | Volume: vol, 26 | } 27 | volName := strings.TrimRight(strings.TrimPrefix(vol, `\\?\`), `\`) 28 | vi.DOSDevice, err = GetDOSDevice(volName) 29 | if err != nil { 30 | return nil, fmt.Errorf("error getting DOS device: %w", err) 31 | } 32 | vi.Paths, err = GetVolumePaths(vol) 33 | if err != nil { 34 | return nil, fmt.Errorf("error getting volume paths: %w", err) 35 | } 36 | infos = append(infos, vi) 37 | } 38 | return infos, nil 39 | } 40 | 41 | // EnumerateVolumes returns all of the Volume GUID Paths in the system. 42 | // A Volume GUID path looks like: \\?\Volume{a15393d0-3c19-43de-b530-e6cd5e45e659}\ 43 | func EnumerateVolumes() (volumes []string, err error) { 44 | var VolumeName [windows.MAX_PATH]uint16 45 | hFind, err := windows.FindFirstVolume(&VolumeName[0], windows.MAX_PATH) 46 | if err != nil { 47 | return nil, fmt.Errorf("windows.FindFirstVolumeW: %w", err) 48 | } 49 | // ensure find handler is closed on exit 50 | defer func() { 51 | if vcerr := windows.FindVolumeClose(hFind); vcerr != nil { 52 | if err == nil { 53 | err = fmt.Errorf("windows.FindVolumeClose: %w", vcerr) 54 | } 55 | } 56 | }() 57 | for { 58 | vol := windows.UTF16ToString(VolumeName[:]) 59 | volumes = append(volumes, vol) 60 | if err := windows.FindNextVolume(hFind, &VolumeName[0], windows.MAX_PATH); err != nil { 61 | if err == windows.ERROR_NO_MORE_FILES { 62 | break 63 | } 64 | return nil, fmt.Errorf("windows.FindNextVolumeW: %w", err) 65 | } 66 | } 67 | return volumes, nil 68 | } 69 | 70 | // GetDOSDevice returns the DOS device for the given a deviceName. 71 | // To get the DOS device for a volume, provide the Volume GUID as the deviceName: Volume{a15393d0-3c19-43de-b530-e6cd5e45e659} 72 | // A DOS device path looks like: \Device\HarddiskVolume1 or \Device\CdRom0 73 | func GetDOSDevice(deviceName string) (string, error) { 74 | trimw, err := windows.UTF16FromString(strings.TrimRight(deviceName, `\`)) 75 | if err != nil { 76 | return "", fmt.Errorf("unable to convert volume name to utf-16: %w", err) 77 | } 78 | var DeviceName [windows.MAX_PATH]uint16 79 | n, err := windows.QueryDosDevice(&trimw[0], &DeviceName[0], windows.MAX_PATH) 80 | if err != nil { 81 | return "", fmt.Errorf("windows.GetDOSDevice: %w", err) 82 | } 83 | return windows.UTF16ToString(DeviceName[:n]), nil 84 | } 85 | 86 | // GetVolumePaths gets the paths associated with the given volume 87 | // the volume should be in the form: \\?\Volume{a15393d0-3c19-43de-b530-e6cd5e45e659}\ 88 | func GetVolumePaths(volume string) (paths []string, err error) { 89 | deviceNameW, err := windows.UTF16FromString(volume) 90 | if err != nil { 91 | return nil, fmt.Errorf("unable to convert device name to utf-16: %w", err) 92 | } 93 | var bufferLen = uint32(windows.MAX_PATH) + 1 94 | var buffer []uint16 95 | for { 96 | buffer = make([]uint16, bufferLen) 97 | if err := windows.GetVolumePathNamesForVolumeName(&deviceNameW[0], &buffer[0], bufferLen, &bufferLen); err != nil { 98 | if err == windows.ERROR_MORE_DATA { 99 | continue 100 | } 101 | return nil, fmt.Errorf("windows.GetVolumePathNamesForVolumeNameW: %w", err) 102 | } 103 | break 104 | } 105 | return utf16BufferToStrings(buffer), nil 106 | } 107 | 108 | // utf16BufferToStrings a UTF-16 buffer and converts it into a slice of Go strings. 109 | // The buffer should be an array of null-terminated UTF-16 strings terminated by an additional NULL character. 110 | // For example: "Hello\0Goodbye\0\0" (assuming all chars are 2-bytes wide) 111 | func utf16BufferToStrings(buffer []uint16) (result []string) { 112 | for len(buffer) > 0 { 113 | for i, v := range buffer { 114 | if v == 0 { // found null terminator 115 | if i == 0 { // empty string means we've reached the end 116 | return 117 | } 118 | result = append(result, string(utf16.Decode(buffer[0:i]))) 119 | buffer = buffer[i+1:] 120 | break 121 | } 122 | } 123 | } 124 | return result 125 | } 126 | -------------------------------------------------------------------------------- /volumes/volumes_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | "unicode/utf16" 6 | ) 7 | 8 | func TestUTF16BufferToStrings(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | buffer []uint16 12 | expected []string 13 | }{ 14 | {"nil", nil, nil}, 15 | {"zero", []uint16{0}, []string{}}, 16 | {"one", makeUTF16Buffer("foo"), []string{"foo"}}, 17 | {"two", makeUTF16Buffer("foo", "bar"), []string{"foo", "bar"}}, 18 | {"three", makeUTF16Buffer("foo", "bar", "baz"), []string{"foo", "bar", "baz"}}, 19 | } 20 | for _, test := range tests { 21 | t.Run(test.name, func(t *testing.T) { 22 | result := utf16BufferToStrings(test.buffer) 23 | if a, b := len(result), len(test.expected); a != b { 24 | t.Fatalf("lengths do not match. got=%d, expected=%d", a, b) 25 | } 26 | for i, a := range result { 27 | b := test.expected[i] 28 | if a != b { 29 | t.Fatalf("string[%d] does not match. got=%s, expected=%s", i, a, b) 30 | } 31 | } 32 | }) 33 | } 34 | } 35 | 36 | func makeUTF16Buffer(strs ...string) []uint16 { 37 | var buffer []uint16 38 | for _, str := range strs { 39 | enc := utf16.Encode([]rune(str)) 40 | enc = append(enc, 0) 41 | buffer = append(buffer, enc...) 42 | } 43 | return append(buffer, 0) 44 | } 45 | --------------------------------------------------------------------------------