如何以与 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、CreateProcessWithLogonWCreateProcessWithTokenW 将进程参数中的桌面信息默认为调用者的,例如“服务-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 ServiceSystem 用户身份执行时有效。在用户会话或会话 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 服务不同的用户身份运行进程的主要内容,如果未能解决你的问题,请参考以下文章

以与 SSISDB 不同的用户身份执行 SSIS 包

如何在 Keycloak 中创建客户端以与 AWS Cognito 身份联合使用

在 Windows 上,是不是可以以不同的用户身份运行单个 goroutine?

通过 WCF 服务传递 NetworkCredential 以与 EWS API 一起使用

如何在 Python 中创建 Socket.io 客户端以与 Sails 服务器通信

WiX ServiceInstall - 将服务设置为以当前 Windows 用户身份运行