github.com/iDigitalFlame/xmt@v0.5.4/device/winapi/c_compat.go (about) 1 //go:build windows && !go1.11 2 // +build windows,!go1.11 3 4 // Copyright (C) 2020 - 2023 iDigitalFlame 5 // 6 // This program is free software: you can redistribute it and/or modify 7 // it under the terms of the GNU General Public License as published by 8 // the Free Software Foundation, either version 3 of the License, or 9 // any later version. 10 // 11 // This program is distributed in the hope that it will be useful, 12 // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 // GNU General Public License for more details. 15 // 16 // You should have received a copy of the GNU General Public License 17 // along with this program. If not, see <https://www.gnu.org/licenses/>. 18 // 19 20 package winapi 21 22 import ( 23 "strings" 24 "sync" 25 "syscall" 26 "unsafe" 27 ) 28 29 const ( 30 tokenPerms = 0x2 31 fallbackLoad = true 32 ) 33 34 var dllKernelOrAdvapi = dllAdvapi32 35 36 var adminGroupSID = [16]byte{1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0} 37 38 var compatOnce struct { 39 sync.Once 40 v uint8 41 c, x bool 42 } 43 44 // IsWindows7 returns true if the underlying device runs at least Windows 7 45 // (>=6.2) and built using <= go1.10. 46 // 47 // If built using >= go1.11, this function always returns true. 48 func IsWindows7() bool { 49 compatOnce.Do(checkCompatFunc) 50 return compatOnce.v >= 2 51 } 52 53 // EmptyWorkingSet Windows API Call wrapper 54 // 55 // Removes as many pages as possible from the working set of the specified 56 // process. 57 // 58 // https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-emptyworkingset 59 // 60 // Wraps the 'SetProcessWorkingSetSizeEx' call instead to prevent having to track 61 // the 'EmptyWorkingSet' function between kernel32.dll and psapi.dll. 62 // 63 // This function will fallback to 'SetProcessWorkingSetSize' if the underlying 64 // device is Windows Xp and built using <= go1.10. 65 func EmptyWorkingSet() { 66 if compatOnce.Do(checkCompatFunc); compatOnce.c { 67 syscallN(funcSetProcessWorkingSetSizeEx.address(), CurrentProcess, invalid, invalid, 0) 68 } else { 69 syscallN(funcSetProcessWorkingSetSize.address(), CurrentProcess, invalid, invalid) 70 } 71 } 72 func checkCompatFunc() { 73 switch m, n, _ := GetVersionNumbers(); { 74 case m >= 10: 75 compatOnce.v = 3 76 case m > 6: 77 fallthrough 78 case m == 6 && n >= 1: 79 compatOnce.v = 2 80 case m == 6 && n == 0: 81 compatOnce.v = 1 82 default: 83 } 84 compatOnce.c = funcRtlCopyMappedMemory.find() == nil 85 compatOnce.x = funcCreateProcessWithToken.find() == nil 86 } 87 88 // IsWindowsXp returns true if the underlying device is Windows Xp and NOT Server 89 // 2003. 90 // 91 // If built using >= go1.11, this function always returns false. 92 func IsWindowsXp() bool { 93 compatOnce.Do(checkCompatFunc) 94 return !compatOnce.x 95 } 96 97 // IsWindows10 returns true if the underlying device runs at least Windows 10 98 // (>=10). 99 func IsWindows10() bool { 100 compatOnce.Do(checkCompatFunc) 101 return compatOnce.v >= 3 102 } 103 104 // IsWindowsVista returns true if the underlying device runs at least Windows Vista 105 // (>=6) and built using <= go1.10. 106 // 107 // If built using >= go1.11, this function always returns true. 108 func IsWindowsVista() bool { 109 compatOnce.Do(checkCompatFunc) 110 return compatOnce.v >= 1 111 } 112 113 // UserInAdminGroup returns true if the current thread or process token user is 114 // part of the Administrators group. This is only used if the device is older than 115 // Windows Vista and built using <= go1.10. 116 // 117 // If built using >= go1.11, this function always returns false. 118 func UserInAdminGroup() bool { 119 var t uintptr 120 // 0x8 - TOKEN_QUERY 121 OpenThreadToken(CurrentThread, 0x8, true, &t) 122 var ( 123 a uint32 124 r, _, _ = syscallN(funcCheckTokenMembership.address(), t, uintptr(unsafe.Pointer(&adminGroupSID)), uintptr(unsafe.Pointer(&a))) 125 ) 126 if t > 0 { 127 CloseHandle(t) 128 } 129 return r > 0 && a > 0 130 } 131 132 // IsTokenElevated returns true if this token has a High or System privileges. 133 // 134 // Always returns false on any systems older than Windows Vista. 135 func IsTokenElevated(h uintptr) bool { 136 if !IsWindowsVista() { 137 var t uintptr 138 // 0x8 - TOKEN_QUERY 139 if err := DuplicateTokenEx(h, 0x8, nil, 1, 2, &t); err != nil { 140 return false 141 } 142 var ( 143 a uint32 144 r, _, _ = syscallN(funcCheckTokenMembership.address(), t, uintptr(unsafe.Pointer(&adminGroupSID)), uintptr(unsafe.Pointer(&a))) 145 ) 146 CloseHandle(t) 147 return r > 0 && a > 0 148 } 149 var ( 150 e, n uint32 151 err = GetTokenInformation(h, 0x14, (*byte)(unsafe.Pointer(&e)), 4, &n) 152 // 0x14 - TokenElevation 153 ) 154 return err == nil && n == 4 && e != 0 155 } 156 157 // IsWow64Process Windows API Call 158 // 159 // Determines whether the specified process is running under WOW64 or an 160 // Intel64 of x64 processor. 161 // 162 // https://docs.microsoft.com/en-us/windows/win32/api/wow64apiset/nf-wow64apiset-iswow64process 163 func IsWow64Process(h uintptr) (bool, error) { 164 if funcRtlWow64GetProcessMachines.find() != nil { 165 // Windows Vista does not have 'RtlWow64GetProcessMachines'. Do another 166 // check with 'NtQueryInformationProcess' 167 var ( 168 v uint32 169 r, _, _ = syscallN(funcNtQueryInformationProcess.address(), h, 0x1A, uintptr(unsafe.Pointer(&v)), 4, 0) 170 // 0x1A - ProcessWow64Information 171 ) 172 if r > 0 { 173 return false, formatNtError(r) 174 } 175 return v > 0, nil 176 } 177 var p, n uint16 178 if r, _, _ := syscallN(funcRtlWow64GetProcessMachines.address(), h, uintptr(unsafe.Pointer(&p)), uintptr(unsafe.Pointer(&n))); r > 0 { 179 return false, formatNtError(r) 180 } 181 return p > 0, nil 182 } 183 184 // CancelIoEx Windows API Call 185 // 186 // Marks any outstanding I/O operations for the specified file handle. The 187 // function only cancels I/O operations in the current process, regardless of 188 // which thread created the I/O operation. 189 // 190 // https://docs.microsoft.com/en-us/windows/win32/fileio/cancelioex-func 191 // 192 // Re-targeted to use 'NtCancelIoFileEx' instead. 193 // https://learn.microsoft.com/en-us/windows/win32/devnotes/nt-cancel-io-file-ex 194 // 195 // NOTE(dij): ^ THIS IS WRONG! It forgets the IO_STATUS_BLOCK entry at the end. 196 // 197 // NtCancelIoFileEx (HANDLE FileHandle, PIO_STATUS_BLOCK IoRequestToCancel, PIO_STATUS_BLOCK IoStatusBlock) 198 // 199 // This function will fallback to 'NtCancelIoFile' if the underlying device is 200 // older than Windows 7 and built using <= go1.10. 201 // 202 // Normally, Windows Vista would work, but this has a weird issue that causes 203 // it to wait forever. 204 func CancelIoEx(h uintptr, o *Overlapped) error { 205 var ( 206 s [4 + ptrSize]byte // IO_STATUS_BLOCK 207 r uintptr 208 ) 209 // NOTE(dij): Windows Vista "supports" NtCancelIoFileEx, but it seems to cause 210 // the thread to block forever, so we wait till Win7 to use it, 211 // which seems that bug doesn't exist. 212 if IsWindows7() { 213 r, _, _ = syscallN(funcNtCancelIoFileEx.address(), h, uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(&s))) 214 } else { 215 r, _, _ = syscallN(funcNtCancelIoFile.address(), h, uintptr(unsafe.Pointer(&s))) 216 } 217 if r > 0 { 218 return formatNtError(r) 219 } 220 return nil 221 } 222 func copyMemory(d uintptr, s uintptr, x uint32) { 223 if compatOnce.Do(checkCompatFunc); compatOnce.c { 224 syscallN(funcRtlCopyMappedMemory.address(), d, s, uintptr(x)) 225 } else { 226 syscallN(funcRtlMoveMemory.address(), d, s, uintptr(x)) 227 } 228 } 229 230 // RegDeleteTree Windows API Call 231 // 232 // Deletes the subkeys and values of the specified key recursively. 233 // 234 // https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regdeletetreew 235 // 236 // This function returns 'syscall.EINVAL' if the underlying device is older than 237 // Windows Vista and built using <= go1.10. 238 func RegDeleteTree(h uintptr, path string) error { 239 if !IsWindowsVista() { 240 return syscall.EINVAL 241 } 242 p, err := UTF16PtrFromString(path) 243 if err != nil { 244 return err 245 } 246 r, _, err1 := syscallN(funcRegDeleteTree.address(), h, uintptr(unsafe.Pointer(p))) 247 if r > 0 { 248 return unboxError(err1) 249 } 250 return nil 251 } 252 253 // EnumDrivers attempts to reterive the list of currently loaded drivers 254 // and will call the supplied function with the handle of each driver along with 255 // the base name of the driver file. 256 // 257 // The user supplied function can return an error that if non-nil, will stop 258 // Driver iteration immediately and will be returned by this function. 259 // 260 // Callers can return the special 'winapi.ErrNoMoreFiles' error that will stop 261 // iteration but will cause this function to return nil. This can be used to 262 // stop iteration without errors if needed. 263 func EnumDrivers(f func(uintptr, string) error) error { 264 if !IsWindows7() { 265 return enumDrivers(funcEnumDeviceDrivers.address(), funcGetDeviceDriverFileName.address(), f) 266 } 267 return enumDrivers(funcK32EnumDeviceDrivers.address(), funcK32GetDeviceDriverFileName.address(), f) 268 } 269 func getCurrentModuleInfo(h uintptr, i *modInfo) error { 270 var ( 271 r uintptr 272 err error 273 ) 274 if IsWindows7() { 275 r, _, err = syscallN(funcK32GetModuleInformation.address(), CurrentProcess, h, uintptr(unsafe.Pointer(i)), ptrSize*3) 276 } else { 277 r, _, err = syscallN(funcGetModuleInformation.address(), CurrentProcess, h, uintptr(unsafe.Pointer(i)), ptrSize*3) 278 } 279 if r == 0 { 280 return err 281 } 282 return nil 283 } 284 285 // RegDeleteKeyEx Windows API Call 286 // 287 // Deletes a subkey and its values. Note that key names are not case sensitive. 288 // ONLY DELETES EMPTY SUBKEYS. (invalid argument if non-empty) 289 // 290 // https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regdeletekeyexw 291 // 292 // This function will fallback to 'RegDeleteKey' if the underlying device is 293 // older than Windows Vista and built using <= go1.10. 294 func RegDeleteKeyEx(h uintptr, path string, f uint32) error { 295 p, err := UTF16PtrFromString(path) 296 if err != nil { 297 return err 298 } 299 var ( 300 r uintptr 301 e syscall.Errno 302 ) 303 if IsWindowsVista() { 304 r, _, e = syscallN(funcRegDeleteKeyEx.address(), h, uintptr(unsafe.Pointer(p)), uintptr(f), 0) 305 } else { 306 r, _, e = syscallN(funcRegDeleteKey.address(), h, uintptr(unsafe.Pointer(p))) 307 } 308 if r > 0 { 309 return unboxError(e) 310 } 311 return nil 312 } 313 314 // WinHTTPGetDefaultProxyConfiguration Windows API Call 315 // 316 // The WinHttpGetDefaultProxyConfiguration function retrieves the default WinHTTP 317 // proxy configuration from the registry. 318 // 319 // https://docs.microsoft.com/en-us/windows/win32/api/winhttp/nf-winhttp-winhttpgetdefaultproxyconfiguration 320 // 321 // This function returns 'syscall.EINVAL' if the underlying device is Windows Xp 322 // and built using <= go1.10. 323 func WinHTTPGetDefaultProxyConfiguration(i *ProxyInfo) error { 324 // We have to check before calling as it /might/ exist on some systems. 325 if funcWinHTTPGetDefaultProxyConfiguration.find() != nil { 326 return syscall.EINVAL 327 } 328 r, _, err := syscallN(funcWinHTTPGetDefaultProxyConfiguration.address(), uintptr(unsafe.Pointer(&i))) 329 if r == 0 { 330 return unboxError(err) 331 } 332 return nil 333 } 334 func enumDrivers(enum, name uintptr, f func(uintptr, string) error) error { 335 var ( 336 n uint32 337 r, _, err1 = syscallN(enum, 0, 0, uintptr(unsafe.Pointer(&n))) 338 ) 339 if r == 0 { 340 return unboxError(err1) 341 } 342 e := make([]uintptr, (n/uint32(ptrSize))+32) 343 r, _, err1 = syscallN(enum, uintptr(unsafe.Pointer(&e[0])), uintptr(n+uint32(32*ptrSize)), uintptr(unsafe.Pointer(&n))) 344 if r == 0 { 345 return unboxError(err1) 346 } 347 var ( 348 s [260]uint16 349 err error 350 b = UTF16ToString((*kernelSharedData)(unsafe.Pointer(kernelShared)).NtSystemRoot[:]) 351 ) 352 for i := range e { 353 if e[i] == 0 { 354 continue 355 } 356 if r, _, err1 = syscallN(name, e[i], uintptr(unsafe.Pointer(&s[0])), 260); r == 0 { 357 return unboxError(err1) 358 } 359 v := strings.Replace(UTF16ToString(s[:r]), sysRoot, b, 1) 360 if len(v) > 5 && v[0] == '\\' && v[1] == '?' && v[3] == '\\' { 361 v = v[4:] 362 } 363 if err = f(e[i], v); err != nil { 364 break 365 } 366 } 367 if err != nil && err == ErrNoMoreFiles { 368 return err 369 } 370 return nil 371 } 372 373 // NtCreateThreadEx Windows API Call 374 // 375 // Creates a thread that runs in the virtual address space of another process 376 // and optionally specifies extended attributes such as processor group affinity. 377 // 378 // http://pinvoke.net/default.aspx/ntdll/NtCreateThreadEx.html 379 // 380 // This function will fallback to 'CreateRemoteThread' if the underlying device 381 // is older than Windows Vista and built using <= go1.10. 382 func NtCreateThreadEx(h, address, args uintptr, suspended bool) (uintptr, error) { 383 if !IsWindowsVista() { 384 var s uint32 385 if suspended { 386 // 0x4 - CREATE_SUSPENDED 387 s = 0x4 388 } 389 // NOTE(dij): I hate that I have to call this function instead of it's 390 // Nt* counterpart. NtCreateThread needs to be "activated" and 391 // CSR must be notified. If you're reading this and want to see 392 // what I'm talking about, ReactOS has a good example of it 393 // https://doxygen.reactos.org/d0/d85/dll_2win32_2kernel32_2client_2thread_8c.html#a17cb3377438e48382207f54a8d045f07 394 r, _, err := syscallN(funcCreateRemoteThread.address(), h, 0, 0, address, args, uintptr(s), 0) 395 if r == 0 { 396 return 0, unboxError(err) 397 } 398 return r, nil 399 } 400 f := uint32(0x4) 401 // 0x4 - THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER 402 if suspended { 403 // 0x1 - CREATE_SUSPENDED 404 f |= 0x1 405 } 406 var ( 407 t uintptr 408 r, _, _ = syscallN( 409 funcNtCreateThreadEx.address(), uintptr(unsafe.Pointer(&t)), 0x10000000, 0, h, address, args, uintptr(f), 410 0, 0, 0, 0, 411 ) 412 ) 413 if r > 0 { 414 return 0, formatNtError(r) 415 } 416 return t, nil 417 } 418 419 // CreateProcessWithToken Windows API Call 420 // 421 // Creates a new process and its primary thread. The new process runs in the 422 // security context of the specified token. It can optionally load the user 423 // profile for the specified user. 424 // 425 // https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createprocesswithtokenw 426 // 427 // This function returns 'syscall.EINVAL' if the underlying device is Windows Xp 428 // and built using <= go1.10. 429 func CreateProcessWithToken(t uintptr, loginFlags uint32, name, cmd string, flags uint32, env []string, dir string, y *StartupInfo, x *StartupInfoEx, i *ProcessInformation) error { 430 if IsWindowsXp() { 431 return syscall.EINVAL 432 } 433 var ( 434 n, c, d, e *uint16 435 err error 436 ) 437 if len(name) > 0 { 438 if n, err = UTF16PtrFromString(name); err != nil { 439 return err 440 } 441 } 442 if len(cmd) > 0 { 443 if c, err = UTF16PtrFromString(cmd); err != nil { 444 return err 445 } 446 } 447 if len(dir) > 0 { 448 if d, err = UTF16PtrFromString(dir); err != nil { 449 return err 450 } 451 } 452 if len(env) > 0 { 453 if e, err = StringListToUTF16Block(env); err != nil { 454 return err 455 } 456 // 0x400 - CREATE_UNICODE_ENVIRONMENT 457 flags |= 0x400 458 } 459 var j unsafe.Pointer 460 if y == nil && x != nil { 461 // BUG(dij): For some reason adding this flag causes the function 462 // to return "invalid parameter", even this this IS THE ACCEPTED 463 // thing to do???! 464 // 465 // flags |= 0x80000 466 j = unsafe.Pointer(x) 467 } else { 468 j = unsafe.Pointer(y) 469 } 470 r, _, err1 := syscallN( 471 funcCreateProcessWithToken.address(), t, uintptr(loginFlags), uintptr(unsafe.Pointer(n)), uintptr(unsafe.Pointer(c)), 472 uintptr(flags), uintptr(unsafe.Pointer(e)), uintptr(unsafe.Pointer(d)), uintptr(j), uintptr(unsafe.Pointer(i)), 473 ) 474 if r == 0 { 475 return unboxError(err1) 476 } 477 return nil 478 }