如何以与 Windows 服务不同的用户身份运行进程
Posted
技术标签:
【中文标题】如何以与 Windows 服务不同的用户身份运行进程【英文标题】:How to run process as different user from windows service 【发布时间】:2017-09-22 20:30:41 【问题描述】:我创建了一个在用户登录之前已经启动的服务。该服务作为“网络服务”用户运行。有时,它必须运行需要以域用户身份运行的更新过程。必须考虑在更新过程开始时没有用户登录的情况。由于密码规则(需要不时更改),无法以域用户身份运行服务。 当需要运行更新过程时,从另一台机器获取域用户的密码。 我的问题是该服务能够使用 CreateProcessWithLogonW 将进程创建为域用户,但是一旦启动该进程,它就会立即被销毁。返回值为 0,我没有 stdout 也没有 stderr。我得到的唯一提示是事件日志中的一个条目,错误代码为 0xc0000142。 我还尝试了在网上找到的其他几种解决方案。但没有解决方案有效。例如,我也尝试过 LogonUser -> 调整权限 -> CreateProcessAsUser。 操作系统是 Windows 7。 更新程序只是一个控制台应用程序。我只需要返回码、标准输出和标准错误。进程启动时不应弹出任何窗口。 谁能帮我找到一个可行的解决方案?最好是 Python 中的一个例子。提前致谢。 最好的祝福, 马丁
更新: 目前我最终得到以下代码:
import os
import sys
import types
import subprocess
import ctypes
from ctypes import wintypes
import win32con
import win32event
import win32api
import win32security
kernel32 = ctypes.WinDLL('kernel32', use_last_error = True)
advapi32 = ctypes.WinDLL('advapi32', use_last_error = True)
ERROR_INVALID_HANDLE = 0x0006
INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value
INVALID_DWORD_VALUE = wintypes.DWORD(-1).value
DEBUG_PROCESS = 0x00000001
DEBUG_ONLY_THIS_PROCESS = 0x00000002
CREATE_SUSPENDED = 0x00000004
DETACHED_PROCESS = 0x00000008
CREATE_NEW_CONSOLE = 0x00000010
CREATE_NEW_PROCESS_GROUP = 0x00000200
CREATE_UNICODE_ENVIRONMENT = 0x00000400
CREATE_SEPARATE_WOW_VDM = 0x00000800
CREATE_SHARED_WOW_VDM = 0x00001000
INHERIT_PARENT_AFFINITY = 0x00010000
CREATE_PROTECTED_PROCESS = 0x00040000
EXTENDED_STARTUPINFO_PRESENT = 0x00080000
CREATE_BREAKAWAY_FROM_JOB = 0x01000000
CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000
CREATE_DEFAULT_ERROR_MODE = 0x04000000
CREATE_NO_WINDOW = 0x08000000
STARTF_USESHOWWINDOW = 0x00000001
STARTF_USESIZE = 0x00000002
STARTF_USEPOSITION = 0x00000004
STARTF_USECOUNTCHARS = 0x00000008
STARTF_USEFILLATTRIBUTE = 0x00000010
STARTF_RUNFULLSCREEN = 0x00000020
STARTF_FORCEONFEEDBACK = 0x00000040
STARTF_FORCEOFFFEEDBACK = 0x00000080
STARTF_USESTDHANDLES = 0x00000100
STARTF_USEHOTKEY = 0x00000200
STARTF_TITLEISLINKNAME = 0x00000800
STARTF_TITLEISAPPID = 0x00001000
STARTF_PREVENTPINNING = 0x00002000
SW_HIDE = 0
SW_SHOWNORMAL = 1
SW_SHOWMINIMIZED = 2
SW_SHOWMAXIMIZED = 3
SW_SHOWNOACTIVATE = 4
SW_SHOW = 5
SW_MINIMIZE = 6
SW_SHOWMINNOACTIVE = 7
SW_SHOWNA = 8
SW_RESTORE = 9
SW_SHOWDEFAULT = 10 # ~STARTUPINFO
SW_FORCEMINIMIZE = 11
LOGON_WITH_PROFILE = 0x00000001
LOGON_NETCREDENTIALS_ONLY = 0x00000002
STD_INPUT_HANDLE = wintypes.DWORD(-10).value
STD_OUTPUT_HANDLE = wintypes.DWORD(-11).value
STD_ERROR_HANDLE = wintypes.DWORD(-12).value
SYNCHRONIZE = 0x00100000
WAIT_OBJECT_0 = win32event.WAIT_OBJECT_0
WAIT_OBJECT_1 = WAIT_OBJECT_0 + 1
class HANDLE(wintypes.HANDLE):
__slots__ = ( 'closed', )
def __int__(self):
return self.value or 0
def Detach(self):
if not getattr(self, 'closed', False):
self.closed = True
value = int(self)
self.value = None
return value
raise ValueError("already closed")
def Close(self, CloseHandle=kernel32.CloseHandle):
if self and not getattr(self, 'closed', False):
CloseHandle(self.Detach())
__del__ = Close
def __repr__(self):
return "%s(%d)" % (self.__class__.__name__, int(self))
class PROCESS_INFORMATION(ctypes.Structure):
"""https://msdn.microsoft.com/en-us/library/ms684873"""
__slots__ = '_cached_hProcess', '_cached_hThread'
_fields_ = (('_hProcess', HANDLE),
('_hThread', HANDLE),
('dwProcessId', wintypes.DWORD),
('dwThreadId', wintypes.DWORD))
@property
def hProcess(self):
if not hasattr(self, '_cached_hProcess'):
self._cached_hProcess = self._hProcess
return self._cached_hProcess
@property
def hThread(self):
if not hasattr(self, '_cached_hThread'):
self._cached_hThread = self._hThread
return self._cached_hThread
def __del__(self):
try:
self.hProcess.Close()
finally:
self.hThread.Close()
LPPROCESS_INFORMATION = ctypes.POINTER(PROCESS_INFORMATION)
LPBYTE = ctypes.POINTER(wintypes.BYTE)
class STARTUPINFO(ctypes.Structure):
"""https://msdn.microsoft.com/en-us/library/ms686331"""
_fields_ = (('cb', wintypes.DWORD),
('lpReserved', wintypes.LPWSTR),
('lpDesktop', wintypes.LPWSTR),
('lpTitle', wintypes.LPWSTR),
('dwX', wintypes.DWORD),
('dwY', wintypes.DWORD),
('dwXSize', wintypes.DWORD),
('dwYSize', wintypes.DWORD),
('dwXCountChars', wintypes.DWORD),
('dwYCountChars', wintypes.DWORD),
('dwFillAttribute', wintypes.DWORD),
('dwFlags', wintypes.DWORD),
('wShowWindow', wintypes.WORD),
('cbReserved2', wintypes.WORD),
('lpReserved2', LPBYTE),
('hStdInput', wintypes.HANDLE),
('hStdOutput', wintypes.HANDLE),
('hStdError', wintypes.HANDLE))
def __init__(self, **kwds):
self.cb = ctypes.sizeof(self)
super(STARTUPINFO, self).__init__(**kwds)
class PROC_THREAD_ATTRIBUTE_LIST(ctypes.Structure):
pass
PPROC_THREAD_ATTRIBUTE_LIST = ctypes.POINTER(PROC_THREAD_ATTRIBUTE_LIST)
class STARTUPINFOEX(STARTUPINFO):
_fields_ = (('lpAttributeList', PPROC_THREAD_ATTRIBUTE_LIST),)
LPSTARTUPINFO = ctypes.POINTER(STARTUPINFO)
LPSTARTUPINFOEX = ctypes.POINTER(STARTUPINFOEX)
class SECURITY_ATTRIBUTES(ctypes.Structure):
_fields_ = (('nLength', wintypes.DWORD),
('lpSecurityDescriptor', wintypes.LPVOID),
('bInheritHandle', wintypes.BOOL))
def __init__(self, **kwds):
self.nLength = ctypes.sizeof(self)
super(SECURITY_ATTRIBUTES, self).__init__(**kwds)
LPSECURITY_ATTRIBUTES = ctypes.POINTER(SECURITY_ATTRIBUTES)
class HANDLE_IHV(HANDLE):
pass
class DWORD_IDV(wintypes.DWORD):
pass
def _check_ihv(result, func, args):
if result.value == INVALID_HANDLE_VALUE:
raise ctypes.WinError(ctypes.get_last_error())
return result.value
def _check_idv(result, func, args):
if result.value == INVALID_DWORD_VALUE:
raise ctypes.WinError(ctypes.get_last_error())
return result.value
def _check_bool(result, func, args):
if not result:
raise ctypes.WinError(ctypes.get_last_error())
return args
def WIN(func, restype, *argtypes):
func.restype = restype
func.argtypes = argtypes
if issubclass(restype, HANDLE_IHV):
func.errcheck = _check_ihv
elif issubclass(restype, DWORD_IDV):
func.errcheck = _check_idv
else:
func.errcheck = _check_bool
# https://msdn.microsoft.com/en-us/library/ms724211
WIN(kernel32.CloseHandle, wintypes.BOOL,
wintypes.HANDLE,) # _In_ HANDLE hObject
# https://msdn.microsoft.com/en-us/library/ms685086
WIN(kernel32.ResumeThread, DWORD_IDV,
wintypes.HANDLE,) # _In_ hThread
# https://msdn.microsoft.com/en-us/library/ms682425
WIN(kernel32.CreateProcessW, wintypes.BOOL,
wintypes.LPCWSTR, # _In_opt_ lpApplicationName
wintypes.LPWSTR, # _Inout_opt_ lpCommandLine
LPSECURITY_ATTRIBUTES, # _In_opt_ lpProcessAttributes
LPSECURITY_ATTRIBUTES, # _In_opt_ lpThreadAttributes
wintypes.BOOL, # _In_ bInheritHandles
wintypes.DWORD, # _In_ dwCreationFlags
wintypes.LPCWSTR, # _In_opt_ lpEnvironment
wintypes.LPCWSTR, # _In_opt_ lpCurrentDirectory
LPSTARTUPINFO, # _In_ lpStartupInfo
LPPROCESS_INFORMATION) # _Out_ lpProcessInformation
# https://msdn.microsoft.com/en-us/library/ms682429
WIN(advapi32.CreateProcessAsUserW, wintypes.BOOL,
wintypes.HANDLE, # _In_opt_ hToken
wintypes.LPCWSTR, # _In_opt_ lpApplicationName
wintypes.LPWSTR, # _Inout_opt_ lpCommandLine
LPSECURITY_ATTRIBUTES, # _In_opt_ lpProcessAttributes
LPSECURITY_ATTRIBUTES, # _In_opt_ lpThreadAttributes
wintypes.BOOL, # _In_ bInheritHandles
wintypes.DWORD, # _In_ dwCreationFlags
wintypes.LPCWSTR, # _In_opt_ lpEnvironment
wintypes.LPCWSTR, # _In_opt_ lpCurrentDirectory
LPSTARTUPINFO, # _In_ lpStartupInfo
LPPROCESS_INFORMATION) # _Out_ lpProcessInformation
# https://msdn.microsoft.com/en-us/library/ms682434
WIN(advapi32.CreateProcessWithTokenW, wintypes.BOOL,
wintypes.HANDLE, # _In_ hToken
wintypes.DWORD, # _In_ dwLogonFlags
wintypes.LPCWSTR, # _In_opt_ lpApplicationName
wintypes.LPWSTR, # _Inout_opt_ lpCommandLine
wintypes.DWORD, # _In_ dwCreationFlags
wintypes.LPCWSTR, # _In_opt_ lpEnvironment
wintypes.LPCWSTR, # _In_opt_ lpCurrentDirectory
LPSTARTUPINFO, # _In_ lpStartupInfo
LPPROCESS_INFORMATION) # _Out_ lpProcessInformation
# https://msdn.microsoft.com/en-us/library/ms682431
WIN(advapi32.CreateProcessWithLogonW, wintypes.BOOL,
wintypes.LPCWSTR, # _In_ lpUsername
wintypes.LPCWSTR, # _In_opt_ lpDomain
wintypes.LPCWSTR, # _In_ lpPassword
wintypes.DWORD, # _In_ dwLogonFlags
wintypes.LPCWSTR, # _In_opt_ lpApplicationName
wintypes.LPWSTR, # _Inout_opt_ lpCommandLine
wintypes.DWORD, # _In_ dwCreationFlags
wintypes.LPCWSTR, # _In_opt_ lpEnvironment
wintypes.LPCWSTR, # _In_opt_ lpCurrentDirectory
LPSTARTUPINFO, # _In_ lpStartupInfo
LPPROCESS_INFORMATION) # _Out_ lpProcessInformation
CREATION_TYPE_NORMAL = 0
CREATION_TYPE_LOGON = 1
CREATION_TYPE_TOKEN = 2
CREATION_TYPE_USER = 3
class CREATIONINFO(object):
__slots__ = ( 'dwCreationType',
'lpApplicationName', 'lpCommandLine', 'bUseShell',
'lpProcessAttributes', 'lpThreadAttributes', 'bInheritHandles',
'dwCreationFlags', 'lpEnvironment', 'lpCurrentDirectory',
'hToken', 'lpUsername', 'lpDomain', 'lpPassword', 'dwLogonFlags' )
def __init__(self, dwCreationType = CREATION_TYPE_NORMAL, lpApplicationName = None, lpCommandLine = None, bUseShell = False,
lpProcessAttributes = None, lpThreadAttributes = None, bInheritHandles = False, dwCreationFlags = 0,
lpEnvironment = None, lpCurrentDirectory = None, hToken = None, dwLogonFlags = 0, lpUsername = None,
lpDomain = None, lpPassword = None):
self.dwCreationType = dwCreationType
self.lpApplicationName = lpApplicationName
self.lpCommandLine = lpCommandLine
self.bUseShell = bUseShell
self.lpProcessAttributes = lpProcessAttributes
self.lpThreadAttributes = lpThreadAttributes
self.bInheritHandles = bInheritHandles
self.dwCreationFlags = dwCreationFlags
self.lpEnvironment = lpEnvironment
self.lpCurrentDirectory = lpCurrentDirectory
self.hToken = hToken
self.lpUsername = lpUsername
self.lpDomain = lpDomain
self.lpPassword = lpPassword
self.dwLogonFlags = dwLogonFlags
def create_environment(environ):
if environ is None:
return None
items = ['%s=%s' % (k, environ[k]) for k in sorted(environ)]
buf = '\x00'.join(items)
length = len(buf) + 2 if buf else 1
return ctypes.create_unicode_buffer(buf, length)
def create_process(commandline = None, creationinfo = None, startupinfo = None):
if creationinfo is None:
creationinfo = CREATIONINFO()
if startupinfo is None:
startupinfo = STARTUPINFO()
elif isinstance(startupinfo, subprocess.STARTUPINFO):
startupinfo = STARTUPINFO(dwFlags = startupinfo.dwFlags,
hStdInput = startupinfo.hStdInput,
hStdOutput = startupinfo.hStdOutput,
hStdError = startupinfo.hStdError,
wShowWindow = startupinfo.wShowWindow)
si, ci, pi = startupinfo, creationinfo, PROCESS_INFORMATION()
if commandline is None:
commandline = ci.lpCommandLine
if not commandline is None:
if ci.bUseShell:
si.dwFlags |= STARTF_USESHOWWINDOW
si.wShowWindow = SW_HIDE
comspec = os.environ.get("ComSpec", os.path.join(os.environ["SystemRoot"], "System32", "cmd.exe"))
commandline = '"" /c ""'.format(comspec, commandline)
commandline = ctypes.create_unicode_buffer(commandline)
dwCreationFlags = ci.dwCreationFlags | CREATE_UNICODE_ENVIRONMENT
lpEnvironment = create_environment(ci.lpEnvironment)
if ((dwCreationFlags & DETACHED_PROCESS)
and ((dwCreationFlags & CREATE_NEW_CONSOLE)
or (ci.dwCreationType == CREATION_TYPE_LOGON) or (ci.dwCreationType == CREATION_TYPE_TOKEN))):
raise RuntimeError('DETACHED_PROCESS is incompatible with CREATE_NEW_CONSOLE, which is implied for the logon and token creation types')
if ci.dwCreationType == CREATION_TYPE_NORMAL:
if not kernel32.CreateProcessW(ci.lpApplicationName, commandline, ci.lpProcessAttributes, ci.lpThreadAttributes, ci.bInheritHandles,
dwCreationFlags, lpEnvironment, ci.lpCurrentDirectory, ctypes.byref(si), ctypes.byref(pi)):
raise RuntimeError("CreateProcessW failed with error code %d!" % win32api.GetLastError())
elif ci.dwCreationType == CREATION_TYPE_LOGON:
if not advapi32.CreateProcessWithLogonW(ci.lpUsername, ci.lpDomain, ci.lpPassword, ci.dwLogonFlags, ci.lpApplicationName, commandline,
dwCreationFlags, lpEnvironment, ci.lpCurrentDirectory, ctypes.byref(si), ctypes.byref(pi)):
raise RuntimeError("CreateProcessWithLogonW failed with error code %d!" % win32api.GetLastError())
elif ci.dwCreationType == CREATION_TYPE_TOKEN:
if not advapi32.CreateProcessWithTokenW(ci.hToken, ci.dwLogonFlags, ci.lpApplicationName, commandline, dwCreationFlags, lpEnvironment,
ci.lpCurrentDirectory, ctypes.byref(si), ctypes.byref(pi)):
raise RuntimeError("CreateProcessWithTokenW failed with error code %d!" % win32api.GetLastError())
elif ci.dwCreationType == CREATION_TYPE_USER:
if not advapi32.CreateProcessAsUserW(ci.hToken, ci.lpApplicationName, commandline, ci.lpProcessAttributes, ci.lpThreadAttributes,
ci.bInheritHandles, dwCreationFlags, lpEnvironment, ci.lpCurrentDirectory, ctypes.byref(si),
ctypes.byref(pi)):
raise RuntimeError("CreateProcessAsUserW failed with error code %d!" % win32api.GetLastError())
else:
raise ValueError('invalid process creation type')
return pi
def LogonUser(domain, user, password, bNetwork = False):
return win32security.LogonUser(user, domain, password,
win32con.LOGON32_LOGON_NETWORK if bNetwork else win32con.LOGON32_LOGON_INTERACTIVE,
win32con.LOGON32_PROVIDER_DEFAULT)
def AdjustPriv(priv, bEnable = True, prc = None):
if prc is None:
prc = win32api.GetCurrentProcess()
htoken = win32security.OpenProcessToken(prc, win32security.TOKEN_ADJUST_PRIVILEGES | win32security.TOKEN_QUERY)
id = win32security.LookupPrivilegeValue(None, priv)
if bEnable:
newPriv = [ ( id, win32security.SE_PRIVILEGE_ENABLED ) ]
else:
newPriv = [ ( id, 0 ) ]
win32security.AdjustTokenPrivileges(htoken, 0, newPriv)
rc = win32api.GetLastError()
if rc:
print("AdjustPriv of %s failed with error code %d!" % (priv, rc))
class Popen(subprocess.Popen):
def __init__(self, *args, **kwds):
ci = self._creationinfo = kwds.pop('creationinfo', CREATIONINFO())
if kwds.pop('suspended', False):
ci.dwCreationFlags |= CREATE_SUSPENDED
self._child_started = False
super(Popen, self).__init__(*args, **kwds)
if sys.version_info[0] == 2:
def _execute_child(self, args, executable, preexec_fn, close_fds,
cwd, env, universal_newlines, startupinfo,
creationflags, shell, to_close, p2cread, p2cwrite,
c2pread, c2pwrite, errread, errwrite):
"""Execute program (MS Windows version)"""
commandline = (args if isinstance(args, types.StringTypes) else
subprocess.list2cmdline(args))
self._common_execute_child(executable, commandline, shell,
close_fds, creationflags, env, cwd,
startupinfo, p2cread, c2pwrite, errwrite, to_close)
else:
def _execute_child(self, args, executable, preexec_fn, close_fds,
pass_fds, cwd, env, startupinfo, creationflags,
shell, p2cread, p2cwrite, c2pread, c2pwrite, errread,
errwrite, restore_signals, start_new_session):
"""Execute program (MS Windows version)"""
assert not pass_fds, "pass_fds not supported on Windows."
commandline = (args if isinstance(args, str) else
subprocess.list2cmdline(args))
self._common_execute_child(executable, commandline, shell,
close_fds, creationflags, env, cwd,
startupinfo, p2cread, c2pwrite, errwrite)
def _common_execute_child(self, executable, commandline, shell,
close_fds, creationflags, env, cwd,
startupinfo, p2cread, c2pwrite, errwrite,
to_close=()):
ci = self._creationinfo
if not executable is None:
ci.lpApplicationName = executable
if commandline:
ci.lpCommandLine = commandline
if shell:
ci.bUseShell = shell
if not close_fds:
ci.bInheritHandles = int(not close_fds)
if creationflags:
ci.dwCreationFlags |= creationflags
if not env is None:
ci.lpEnvironment = env
if not cwd is None:
ci.lpCurrentDirectory = cwd
if startupinfo is None:
startupinfo = STARTUPINFO()
si = self._startupinfo = startupinfo
default = None if sys.version_info[0] == 2 else -1
if default not in ( p2cread, c2pwrite, errwrite ):
si.dwFlags |= STARTF_USESTDHANDLES
si.hStdInput = int(p2cread)
si.hStdOutput = int(c2pwrite)
si.hStdError = int(errwrite)
try:
pi = create_process(creationinfo = ci, startupinfo = si)
finally:
if sys.version_info[0] == 2:
if not p2cread is None:
p2cread.Close()
to_close.remove(p2cread)
if not c2pwrite is None:
c2pwrite.Close()
to_close.remove(c2pwrite)
if not errwrite is None:
errwrite.Close()
to_close.remove(errwrite)
else:
if p2cread != -1:
p2cread.Close()
if c2pwrite != -1:
c2pwrite.Close()
if errwrite != -1:
errwrite.Close()
if hasattr(self, '_devnull'):
os.close(self._devnull)
if not ci.dwCreationFlags & CREATE_SUSPENDED:
self._child_started = True
# Retain the process handle, but close the thread handle if it's no longer needed.
self._processinfo = pi
self._handle = pi.hProcess.Detach()
self.pid = pi.dwProcessId
if self._child_started:
pi.hThread.Close()
self.returncode = ctypes.WinError().winerror
def start(self):
if self._child_started:
raise RuntimeError("processes can only be started once")
hThread = self._processinfo.hThread
prev_count = kernel32.ResumeThread(hThread)
if prev_count > 1:
for _ in range(1, prev_count):
if kernel32.ResumeThread(hThread) <= 1:
break
else:
raise RuntimeError('cannot start the main thread')
# The thread's previous suspend count was 0 or 1, so it should be running now.
self._child_started = True
hThread.Close()
def __del__(self):
if not self._child_started:
try:
if hasattr(self, '_processinfo'):
self._processinfo.hThread.Close()
finally:
if hasattr(self, '_handle'):
self.terminate()
super(Popen, self).__del__()
def KillProcessTree(pid):
try:
import psutil
parent = psutil.Process(pid)
for child in parent.children(recursive = True):
child.kill()
parent.kill()
except:
pass
def RunAs(cmdLine, domain, user, password, bNetwork = False, cwd = None, bUseShell = False, bShow = True, hWaitStop = None, timeout = 0):
if cwd is None:
cwd = "C:\\Temp"
token = LogonUser(domain, user, password, bNetwork)
if not token:
raise RuntimeError("LogonUser failed with error code %d!" % win32api.GetLastError())
hToken = token.handle
if bShow:
ci = CREATIONINFO(CREATION_TYPE_USER, hToken = hToken, bUseShell = bUseShell)
si = None
else:
ci = CREATIONINFO(CREATION_TYPE_USER, hToken = hToken, dwCreationFlags = CREATE_NO_WINDOW, bUseShell = bUseShell)
si = STARTUPINFO(wShowWindow = SW_HIDE, dwFlags = CREATE_NEW_CONSOLE | STARTF_USESHOWWINDOW)
AdjustPriv(win32security.SE_TAKE_OWNERSHIP_NAME)
AdjustPriv(win32security.SE_TCB_NAME)
AdjustPriv(win32security.SE_CHANGE_NOTIFY_NAME)
AdjustPriv(win32security.SE_INCREASE_QUOTA_NAME)
AdjustPriv(win32security.SE_ASSIGNPRIMARYTOKEN_NAME)
AdjustPriv(win32security.SE_CREATE_TOKEN_NAME)
win32security.ImpersonateLoggedOnUser(hToken)
prc = Popen(cmdLine, creationinfo = ci, startupinfo = si, cwd = cwd, universal_newlines = True,
stdout = subprocess.PIPE, stderr = subprocess.PIPE, stdin = subprocess.PIPE)
hPrc = ctypes.windll.kernel32.OpenProcess(SYNCHRONIZE, False, prc.pid)
if timeout > 0:
timeout *= 1000
else:
timeout = win32event.INFINITE
if hWaitStop is None:
rc = win32event.WaitForSingleObject(hPrc, int(timeout))
else:
rc = win32event.WaitForMultipleObjects(( hPrc, hWaitStop ), 0, int(timeout))
win32security.RevertToSelf()
if rc != WAIT_OBJECT_0:
KillProcessTree(prc.pid)
return -1, "", "Timeout running command."
if rc == WAIT_OBJECT_1:
KillProcessTree(prc.pid)
return -2, "", "Command was cancelled."
return prc.returncode, prc.stdout.read(), prc.stderr.read()
cmdLine = r"C:\Windows\System32\cmd.exe /C timeout /T 5 > nul"
rc, stdOut, stdErr = RunAs(cmdLine, "DOMAIN", "USER", pw, timeout = 0, bNetwork = True)
但它不起作用。该进程是使用所需用户创建的,但当它启动时,它会立即被销毁。返回码为 0。stdout 上没有输出,也没有 stderr。 GetLastError 也返回 0。事件查看器中不显示任何条目。
【问题讨论】:
您是使用 Python 还是使用 C++ 创建服务?使用 Python,我建议查看“runas”实用程序。使用 C++ - CreateProcessAsUser。 首先,您应该调查Managed Service Accounts,通过允许您以域用户身份运行服务,这可能完全不需要这样做。 @HarryJohnston、CreateProcessWithLogonW
和 CreateProcessWithTokenW
将进程参数中的桌面信息默认为调用者的,例如“服务-0x0-3e7$\Default”。 OTOH,CreateProcessAsUser
可以在不更新安全描述符的情况下工作。它默认将桌面信息留空。在window-station connection rules 之后,如果子进程没有继承父进程的WindowStation,系统将创建一个以登录会话ID 命名的新进程。所以我们需要通过STARTUPINFOEX
来控制继承。
@eryksun,在这种情况下,CreateProcessAsUser 可能不是一个选项,因为该服务没有管理员权限。另一方面,根据文档, CreateProcessWithTokenW 只需要模拟权限(默认情况下服务具有)并为您更改权限。所以当然没有必要自己做。
@HarryJohnston,辅助登录服务(即CreateProcessWithTokenW
)不会修改父级 WindowStation 的安全性。它从客户端的令牌中获取登录 ID 组(即SE_GROUP_LOGON_ID
),并将其添加到孩子的令牌中。当服务以 SYSTEM 身份运行时,它甚至没有这个组,因此创建失败。当作为 NETWORK SERVICE 运行时,它有它,但它用于另一个登录 ID,而不是硬编码的 0x3e4 ID。无论如何,它迫使我们使用“Service-0x0-3e4$”WindowStation,它只有 ACE 用于 NETWORK SERVICE 和 Administrators,因此连接失败。
【参考方案1】:
我终于找到了如何做到这一点。它相当复杂,我不得不合并几个示例中的代码(其中一些在 C 中)。以下示例在以 Network Service 或 System 用户身份执行时有效。在用户会话或会话 0 中执行无关紧要。
代码如下:
import os
import msvcrt
import win32security
import win32con
import win32pipe
import win32process
import win32api
import win32net
import win32file
import win32event
import win32profile
import win32service
GENERIC_ACCESS = win32con.GENERIC_READ | win32con.GENERIC_WRITE | win32con.GENERIC_EXECUTE | win32con.GENERIC_ALL
WINSTA_ALL = (win32con.WINSTA_ACCESSCLIPBOARD | win32con.WINSTA_ACCESSGLOBALATOMS | \
win32con.WINSTA_CREATEDESKTOP | win32con.WINSTA_ENUMDESKTOPS | \
win32con.WINSTA_ENUMERATE | win32con.WINSTA_EXITWINDOWS | \
win32con.WINSTA_READATTRIBUTES | win32con.WINSTA_READSCREEN | \
win32con.WINSTA_WRITEATTRIBUTES | win32con.DELETE | \
win32con.READ_CONTROL | win32con.WRITE_DAC | \
win32con.WRITE_OWNER)
DESKTOP_ALL = (win32con.DESKTOP_CREATEMENU | win32con.DESKTOP_CREATEWINDOW | \
win32con.DESKTOP_ENUMERATE | win32con.DESKTOP_HOOKCONTROL | \
win32con.DESKTOP_JOURNALPLAYBACK | win32con.DESKTOP_JOURNALRECORD | \
win32con.DESKTOP_READOBJECTS | win32con.DESKTOP_SWITCHDESKTOP | \
win32con.DESKTOP_WRITEOBJECTS | win32con.DELETE | \
win32con.READ_CONTROL | win32con.WRITE_DAC | \
win32con.WRITE_OWNER)
def runAsDomainUser(domainName, userName, password, cmdLine, maxWait):
# maxWait = Maximum execution time in ms
userGroupSid = win32security.LookupAccountName(domainName, userName)[0]
# Login as domain user and create new session
userToken = win32security.LogonUser(userName, domainName, password,
win32con.LOGON32_LOGON_INTERACTIVE,
win32con.LOGON32_PROVIDER_DEFAULT)
rc = win32api.GetLastError()
if userToken is None or (rc != 0):
return -1, "", "LogonUser failed with RC=%d!" % rc
profileDir = win32profile.GetUserProfileDirectory(userToken)
tokenUser = win32security.GetTokenInformation(userToken, win32security.TokenUser)
# Set access rights to window station
hWinSta = win32service.OpenWindowStation("winsta0", False, win32con.READ_CONTROL | win32con.WRITE_DAC )
# Get security descriptor by winsta0-handle
secDescWinSta = win32security.GetUserObjectSecurity(hWinSta, win32security.OWNER_SECURITY_INFORMATION
| win32security.DACL_SECURITY_INFORMATION
| win32con.GROUP_SECURITY_INFORMATION)
# Get DACL from security descriptor
daclWinSta = secDescWinSta.GetSecurityDescriptorDacl()
if daclWinSta is None:
# Create DACL if not exisiting
daclWinSta = win32security.ACL()
# Add ACEs to DACL for specific user group
daclWinSta.AddAccessAllowedAce(win32security.ACL_REVISION_DS, GENERIC_ACCESS, userGroupSid)
daclWinSta.AddAccessAllowedAce(win32security.ACL_REVISION_DS, WINSTA_ALL, userGroupSid)
# Set modified DACL for winsta0
win32security.SetSecurityInfo(hWinSta, win32security.SE_WINDOW_OBJECT, win32security.DACL_SECURITY_INFORMATION,
None, None, daclWinSta, None)
# Set access rights to desktop
hDesktop = win32service.OpenDesktop("default", 0, False, win32con.READ_CONTROL
| win32con.WRITE_DAC
| win32con.DESKTOP_WRITEOBJECTS
| win32con.DESKTOP_READOBJECTS)
# Get security descriptor by desktop-handle
secDescDesktop = win32security.GetUserObjectSecurity(hDesktop, win32security.OWNER_SECURITY_INFORMATION
| win32security.DACL_SECURITY_INFORMATION
| win32con.GROUP_SECURITY_INFORMATION )
# Get DACL from security descriptor
daclDesktop = secDescDesktop.GetSecurityDescriptorDacl()
if daclDesktop is None:
#create DACL if not exisiting
daclDesktop = win32security.ACL()
# Add ACEs to DACL for specific user group
daclDesktop.AddAccessAllowedAce(win32security.ACL_REVISION_DS, GENERIC_ACCESS, userGroupSid)
daclDesktop.AddAccessAllowedAce(win32security.ACL_REVISION_DS, DESKTOP_ALL, userGroupSid)
# Set modified DACL for desktop
win32security.SetSecurityInfo(hDesktop, win32security.SE_WINDOW_OBJECT, win32security.DACL_SECURITY_INFORMATION,
None, None, daclDesktop, None)
# Setup stdin, stdOut and stderr
secAttrs = win32security.SECURITY_ATTRIBUTES()
secAttrs.bInheritHandle = 1
stdOutRd, stdOutWr = win32pipe.CreatePipe(secAttrs, 0)
stdErrRd, stdErrWr = win32pipe.CreatePipe(secAttrs, 0)
ppid = win32api.GetCurrentProcess()
tmp = win32api.DuplicateHandle(ppid, stdOutRd, ppid, 0, 0, win32con.DUPLICATE_SAME_ACCESS)
win32file.CloseHandle(stdOutRd)
stdOutRd = tmp
environment = win32profile.CreateEnvironmentBlock(userToken, False)
startupInfo = win32process.STARTUPINFO()
startupInfo.dwFlags = win32con.STARTF_USESTDHANDLES
startupInfo.hStdOutput = stdOutWr
startupInfo.hStdError = stdErrWr
hPrc = win32process.CreateProcessAsUser(
userToken,
None, # appName
cmdLine, # commandLine
None, # processAttributes
None, # threadAttributes
1, # bInheritHandles
win32process.CREATE_NEW_CONSOLE, # dwCreationFlags
environment, # newEnvironment
profileDir, # currentDirectory
startupInfo)[0]
win32file.CloseHandle(stdErrWr)
win32file.CloseHandle(stdOutWr)
win32security.RevertToSelf()
# Wait for process to complete
stdOutBuf = os.fdopen(msvcrt.open_osfhandle(stdOutRd, 0), "rb")
stdErrBuf = os.fdopen(msvcrt.open_osfhandle(stdErrRd, 0), "rb")
win32event.WaitForSingleObject(hPrc, maxWait)
stdOut = stdOutBuf.read()
stdErr = stdErrBuf.read()
rc = win32process.GetExitCodeProcess(hPrc)
return rc, str(stdOut, "utf-8"), str(stdErr, "utf-8")
if __name__ == "__main__":
cmdLine = "C:/Windows/System32/cmd.exe"
domainName = input("Domain: ")
userName = input("User: ")
password = input("Password: ")
print(runAsDomainUser(domainName, userName, password, cmdLine, 60000))
【讨论】:
以上是关于如何以与 Windows 服务不同的用户身份运行进程的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Keycloak 中创建客户端以与 AWS Cognito 身份联合使用
在 Windows 上,是不是可以以不同的用户身份运行单个 goroutine?
通过 WCF 服务传递 NetworkCredential 以与 EWS API 一起使用