golang.zx2c4.com/wireguard/windows@v0.5.4-0.20230123132234-dcc0eb72a04b/elevate/shellexecute.go (about)

     1  /* SPDX-License-Identifier: MIT
     2   *
     3   * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
     4   */
     5  
     6  package elevate
     7  
     8  import (
     9  	"path/filepath"
    10  	"runtime"
    11  	"syscall"
    12  	"unsafe"
    13  
    14  	"golang.org/x/sys/windows"
    15  	"golang.org/x/sys/windows/registry"
    16  
    17  	"golang.zx2c4.com/wireguard/windows/version"
    18  )
    19  
    20  const (
    21  	releaseOffset      = 2
    22  	shellExecuteOffset = 9
    23  
    24  	cSEE_MASK_DEFAULT = 0
    25  )
    26  
    27  func ShellExecute(program, arguments, directory string, show int32) (err error) {
    28  	var (
    29  		program16   *uint16
    30  		arguments16 *uint16
    31  		directory16 *uint16
    32  	)
    33  
    34  	if len(program) > 0 {
    35  		program16, _ = windows.UTF16PtrFromString(program)
    36  	}
    37  	if len(arguments) > 0 {
    38  		arguments16, _ = windows.UTF16PtrFromString(arguments)
    39  	}
    40  	if len(directory) > 0 {
    41  		directory16, _ = windows.UTF16PtrFromString(directory)
    42  	}
    43  
    44  	defer func() {
    45  		if err != nil && program16 != nil {
    46  			err = windows.ShellExecute(0, windows.StringToUTF16Ptr("runas"), program16, arguments16, directory16, show)
    47  		}
    48  	}()
    49  
    50  	if !version.IsRunningEVSigned() {
    51  		err = windows.ERROR_INSUFFICIENT_LOGON_INFO
    52  		return
    53  	}
    54  
    55  	var processToken windows.Token
    56  	err = windows.OpenProcessToken(windows.CurrentProcess(), windows.TOKEN_QUERY|windows.TOKEN_DUPLICATE, &processToken)
    57  	if err != nil {
    58  		return
    59  	}
    60  	defer processToken.Close()
    61  	if processToken.IsElevated() {
    62  		err = windows.ERROR_SUCCESS
    63  		return
    64  	}
    65  	if !TokenIsElevatedOrElevatable(processToken) {
    66  		err = windows.ERROR_ACCESS_DENIED
    67  		return
    68  	}
    69  	key, err := registry.OpenKey(registry.LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", registry.QUERY_VALUE)
    70  	if err != nil {
    71  		return
    72  	}
    73  	promptBehavior, _, err := key.GetIntegerValue("ConsentPromptBehaviorAdmin")
    74  	key.Close()
    75  	if err != nil {
    76  		return
    77  	}
    78  	if uint32(promptBehavior) == 0 {
    79  		err = windows.ERROR_SUCCESS
    80  		return
    81  	}
    82  	if uint32(promptBehavior) != 5 {
    83  		err = windows.ERROR_ACCESS_DENIED
    84  		return
    85  	}
    86  
    87  	key, err = registry.OpenKey(registry.LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\UAC\\COMAutoApprovalList", registry.QUERY_VALUE)
    88  	if err == nil {
    89  		var autoApproved uint64
    90  		autoApproved, _, err = key.GetIntegerValue("{3E5FC7F9-9A51-4367-9063-A120244FBEC7}")
    91  		key.Close()
    92  		if err != nil {
    93  			return
    94  		}
    95  		if uint32(autoApproved) == 0 {
    96  			err = windows.ERROR_ACCESS_DENIED
    97  			return
    98  		}
    99  	}
   100  	dataTableEntry, err := findCurrentDataTableEntry()
   101  	if err != nil {
   102  		return
   103  	}
   104  	windowsDirectory, err := windows.GetSystemWindowsDirectory()
   105  	if err != nil {
   106  		return
   107  	}
   108  	originalPath := dataTableEntry.FullDllName.Buffer
   109  	explorerPath := windows.StringToUTF16Ptr(filepath.Join(windowsDirectory, "explorer.exe"))
   110  	windows.RtlInitUnicodeString(&dataTableEntry.FullDllName, explorerPath)
   111  	defer func() {
   112  		windows.RtlInitUnicodeString(&dataTableEntry.FullDllName, originalPath)
   113  		runtime.KeepAlive(explorerPath)
   114  	}()
   115  
   116  	if err = windows.CoInitializeEx(0, windows.COINIT_APARTMENTTHREADED); err == nil {
   117  		defer windows.CoUninitialize()
   118  	}
   119  
   120  	var interfacePointer **[0xffff]uintptr
   121  	if err = windows.CoGetObject(
   122  		windows.StringToUTF16Ptr("Elevation:Administrator!new:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}"),
   123  		&windows.BIND_OPTS3{
   124  			CbStruct:     uint32(unsafe.Sizeof(windows.BIND_OPTS3{})),
   125  			ClassContext: windows.CLSCTX_LOCAL_SERVER,
   126  		},
   127  		&windows.GUID{0x6EDD6D74, 0xC007, 0x4E75, [8]byte{0xB7, 0x6A, 0xE5, 0x74, 0x09, 0x95, 0xE2, 0x4C}},
   128  		(**uintptr)(unsafe.Pointer(&interfacePointer)),
   129  	); err != nil {
   130  		return
   131  	}
   132  
   133  	defer syscall.SyscallN((*interfacePointer)[releaseOffset], uintptr(unsafe.Pointer(interfacePointer)))
   134  
   135  	if program16 == nil {
   136  		return
   137  	}
   138  
   139  	if ret, _, _ := syscall.SyscallN((*interfacePointer)[shellExecuteOffset],
   140  		uintptr(unsafe.Pointer(interfacePointer)),
   141  		uintptr(unsafe.Pointer(program16)),
   142  		uintptr(unsafe.Pointer(arguments16)),
   143  		uintptr(unsafe.Pointer(directory16)),
   144  		cSEE_MASK_DEFAULT,
   145  		uintptr(show),
   146  	); ret != uintptr(windows.ERROR_SUCCESS) {
   147  		err = syscall.Errno(ret)
   148  		return
   149  	}
   150  
   151  	err = nil
   152  	return
   153  }