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  }