关于Windows:使用CreateProcessWithLogonW从服务启动的进程立即终止

process started from service with CreateProcessWithLogonW terminates right away

在测试框架中,进程A必须使用CreateProcessWithLogonW API在不同的用户凭据(例如_limited_user)下启动进程B。 lpStartupInfo->lpDesktop为NULL,因此进程B应该与进程A在同一桌面和窗口站中运行。

手动启动进程A(如_glagolig),一切正常。但是,当进程A由测试框架服务(在指定的测试框架的用户帐户_test_framework下运行)启动时,将无法正常工作。 CreateProcessWithLogonW返回成功,但进程B无法执行任何工作。它显然立即终止,因为其conhost.exe无法初始化user32.dll并返回0xC0000142(我从SysInternalsa?procmon.exe日志中获得了该信息)。因此看来问题出在桌面/窗口站访问上。

我想了解根本原因。目前尚不清楚是什么使测试框架服务的桌面/窗口站对象与手动登录的用户的对象不同。

我还想找到一种解决方法,同时保持整体方案不变(帐户_test_framework下的测试框架的服务必须在_limited_user下启动进程B)。


附录:根据文档,如果您不希望新流程与用户进行交互,则应该无需执行这些步骤即可使用CreateProcessAsUser。我尚未对此进行测试,但是假设它是正确的,那么对于许多情况来说,这将是一个简单得多的解决方案。

事实证明,Microsoft已经以在C中启动交互式客户端进程的标题提供了示例代码来操纵window station和桌面访问权限。从Windows Vista开始,在默认窗口站中启动子进程已不足以允许该子进程与用户进行交互,但是它确实允许该子进程使用备用用户凭据运行。

我应该注意,Microsoft的代码使用LogonUserCreateProcessAsUser而不是CreateProcessWithLogonW。这确实意味着该服务将需要SE_INCREASE_QUOTA_NAME特权,并且可能需要SE_ASSIGNPRIMARYTOKEN_NAME。最好替换只需要SE_IMPERSONATE_NAMECreateProcessWithTokenW。我不建议在这种情况下使用CreateProcessWithLogonW,因为它不允许您在启动子进程之前访问登录SID。

我写了一个最少的服务来演示如何使用Microsoft的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
/*******************************************************************/

#define _WIN32_WINNT 0x0501

#include <windows.h>

/*******************************************************************/

// See http://msdn.microsoft.com/en-us/library/windows/desktop/aa379608%28v=vs.85%29.aspx
//"Starting an Interactive Client Process in C++"

BOOL AddAceToWindowStation(HWINSTA hwinsta, PSID psid);
BOOL AddAceToDesktop(HDESK hdesk, PSID psid);
BOOL GetLogonSID (HANDLE hToken, PSID *ppsid);
VOID FreeLogonSID (PSID *ppsid);
BOOL StartInteractiveClientProcess (
    LPTSTR lpszUsername,    // client to log on
    LPTSTR lpszDomain,      // domain of client's account
    LPTSTR lpszPassword,    // client's password
    LPTSTR lpCommandLine    // command line to execute
);

/*******************************************************************/

const wchar_t displayname[] = L"Demo service for CreateProcessWithLogonW";
const wchar_t servicename[] = L"demosvc-createprocesswithlogonw";

DWORD dwWin32ExitCode = 0, dwServiceSpecificExitCode = 0;

/*******************************************************************/

#define EXCEPTION_USER 0xE0000000
#define FACILITY_USER_DEMOSVC 0x0001
#define EXCEPTION_USER_LINENUMBER (EXCEPTION_USER | (FACILITY_USER_DEMOSVC << 16))

HANDLE eventloghandle;

/*******************************************************************/

wchar_t subprocess_username[] = L"harry-test1";
wchar_t subprocess_domain[] = L"scms";
wchar_t subprocess_password[] = L"xyzzy916";
wchar_t subprocess_command[] = L"cmd.exe /c dir";

void demo(void)
{
    if (!StartInteractiveClientProcess(subprocess_username, subprocess_domain, subprocess_password, subprocess_command))
    {
        const wchar_t * strings[] = {L"Creating subprocess failed."};
        DWORD err = GetLastError();
        ReportEventW(eventloghandle,
                    EVENTLOG_ERROR_TYPE,
                    0,
                    2,
                    NULL,
                    _countof(strings),
                    sizeof(err),
                    strings,
                    &err);
        return;
    }

    {
        const wchar_t * strings[] = {L"Creating subprocess succeeded!"};
        ReportEventW(eventloghandle,
                    EVENTLOG_INFORMATION_TYPE,
                    0,
                    1,
                    NULL,
                    _countof(strings),
                    0,
                    strings,
                    NULL);
    }

    return;
}

/*******************************************************************/

CRITICAL_SECTION service_section;

SERVICE_STATUS service_status;                     // Protected by service_section

SERVICE_STATUS_HANDLE service_handle = 0;          // Constant once set, so can be used from any thread

static DWORD WINAPI ServiceHandlerEx(DWORD control, DWORD eventtype, LPVOID lpEventData, LPVOID lpContext)
{
    if (control == SERVICE_CONTROL_INTERROGATE)
    {
        EnterCriticalSection(&service_section);
        if (service_status.dwCurrentState != SERVICE_STOPPED)
        {
        SetServiceStatus(service_handle, &service_status);
        }
        LeaveCriticalSection(&service_section);
        return NO_ERROR;
    }

    return ERROR_CALL_NOT_IMPLEMENTED;
}

static VOID WINAPI ServiceMain(DWORD argc, LPTSTR * argv)
{
    SERVICE_STATUS status;

    EnterCriticalSection(&service_section);

    service_handle = RegisterServiceCtrlHandlerEx(argv[0], ServiceHandlerEx, NULL);
    if (!service_handle) RaiseException(EXCEPTION_USER_LINENUMBER | __LINE__, EXCEPTION_NONCONTINUABLE, 0, NULL);

    service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    service_status.dwCurrentState = SERVICE_RUNNING;
    service_status.dwControlsAccepted = 0;
    service_status.dwWin32ExitCode = STILL_ACTIVE;
    service_status.dwServiceSpecificExitCode = 0;
    service_status.dwCheckPoint = 0;
    service_status.dwWaitHint = 500;

    SetServiceStatus(service_handle, &service_status);

    LeaveCriticalSection(&service_section);

    /************** service main function **************/

    {
        const wchar_t * strings[] = {L"Service started!"};
        ReportEventW(eventloghandle,
                    EVENTLOG_INFORMATION_TYPE,
                    0,
                    2,
                    NULL,
                    _countof(strings),
                    0,
                    strings,
                    NULL);
    }

    demo();

    /************** service shutdown **************/

    EnterCriticalSection(&service_section);    

    status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    status.dwCurrentState = service_status.dwCurrentState = SERVICE_STOPPED;
    status.dwControlsAccepted = 0;
    status.dwCheckPoint = 0;
    status.dwWaitHint = 500;
    status.dwWin32ExitCode = dwWin32ExitCode;
    status.dwServiceSpecificExitCode = dwServiceSpecificExitCode;

    LeaveCriticalSection(&service_section);

    SetServiceStatus(service_handle, &status);       /* NB: SetServiceStatus does not return here if successful,
                                                    so any code after this point will not normally run. */
    return;
}

int wmain(int argc, wchar_t * argv[])
{
    const static SERVICE_TABLE_ENTRY servicetable[2] = {
        {(wchar_t *)servicename, ServiceMain},
        {NULL, NULL}
    };

    InitializeCriticalSection(&service_section);

    eventloghandle = RegisterEventSource(NULL, displayname);
    if (!eventloghandle) return GetLastError();

    {
        const wchar_t * strings[] = {L"Executable started!"};
        ReportEventW(eventloghandle,
                    EVENTLOG_INFORMATION_TYPE,
                    0,
                    2,
                    NULL,
                    _countof(strings),
                    0,
                    strings,
                    NULL);
    }

    if (StartServiceCtrlDispatcher(servicetable)) return 0;
    return GetLastError();
}

这必须与Microsoft的示例代码链接。然后可以使用sc命令安装服务:

1
sc create demosvc-createprocesswithlogonw binPath= c:\\path\\demosvc.exe DisplayName="Demo service for CreateProcessWithLogonW"


我得到了以下解决方法。我配置了另一种以_limited_user身份运行的服务,并按需启动。然后,测试框架可以启动和停止受限的用户服务。而且受限的用户服务可以运行我的测试所需的过程。

解决方法起作用。因此,我的进程不需要交互式桌面(即使它们加载了user32.dll)。显然,可以在非交互式上下文中加载user32.dll。但是存在一些未知的微妙之处,当使用CreateProcessWithLogonW从测试框架服务直接启动时,该过程无法运行。