0

I’m trying to call a windows api (that only seems to work in logged on user’s context) from a windows service (running as SYSTEM). I’m able to get the token for logged on user. I don’t get any errors when I call ImpersonateLoggedOnUser(), it returns true. But the DoSomethingInUserContext() still executes in the SYSTEM context. What am I doing wrong?

DWORD sessionIdDw = WTSGetActiveConsoleSessionId();
 
HANDLE hToken;
if (!WTSQueryUserToken(sessionIdDw, &hToken))
  LOG() << "WTSQueryUserToken failed: " << GetLastError();
 
HANDLE hDuplicated = NULL;
if (!DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenImpersonation, &hDuplicated)) {
  LOG() << "DuplicateTokenEx failed: " <<GetLastError();
}
 
if (!ImpersonateLoggedOnUser(hDuplicated)) {
  LOG() << "ImpersonateLoggedOnUser failed " << GetLastError();
}
else {
      DoSomethingInUserContext();

      if (!RevertToSelf()) {
           LOG() << "RevertToSelf failed" << GetLastError();
      }
}
               
CloseHandle(hDuplicated);
CloseHandle(hToken);
NGambit
  • 1,141
  • 13
  • 27
  • Does this answer your question? [How to impersonate a user from a service correctly?](https://stackoverflow.com/questions/19796409/how-to-impersonate-a-user-from-a-service-correctly) – Richard Critten Nov 07 '19 at 16:33
  • It does not because the winapi I’m using doesn’t accept user token – NGambit Nov 07 '19 at 16:36
  • The api I need impersonation for is EnumWindows() https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumwindows – NGambit Nov 07 '19 at 16:37
  • The API you want to use just needs the Service to have: "Allow service to interact with desktop" set on it's service definition. I would have been useful to have explained your requirements in the quesion instead of just having the opaque function `DoSomethingInUserContext`. The reason for impersonation is to get a user token onto the current thread. – Richard Critten Nov 07 '19 at 16:43
  • I’ve “Allow service to interact with desktop” check-box checked in the service properties – NGambit Nov 07 '19 at 16:53
  • 3
    @NGambit impersonation is not the correct solution. `EnumWindows()` enumerates the windows of the desktop associated with the calling thread. Impersonation doesn't change the calling thread's desktop. So you are enumerating windows within your service's session, not the user's session. See [Why EnumWindows Not Working in service?](https://stackoverflow.com/questions/32177233/) Also, "Allow service to interact with desktop" has no effect anymore in Vista+ due to Session 0 Isolation. – Remy Lebeau Nov 07 '19 at 17:14
  • as side note - you not need call `DuplicateTokenEx` before `ImpersonateLoggedOnUser` - api do this by self. if you already duplicate token - call `SetThreadToken` – RbMm Nov 07 '19 at 17:54
  • To reiterate, if a system service needs to interact with a user's session, it has to spawn a proxy process in that session via `CreateProcessAsUserW` and use some form of IPC to interact with the proxy (e.g. named pipe, socket, shared memory). Alternatively, Windows 10 has user services that execute in each interactive session, but they're still undocumented. – Eryk Sun Nov 07 '19 at 23:27
  • Any links or details on these “user services” for windows 10? – NGambit Nov 07 '19 at 23:30
  • 1
    @NGambit - https://learn.microsoft.com/en-us/windows/application-management/per-user-services-in-windows – RbMm Nov 07 '19 at 23:54
  • 1
    The service template entry in "HKLM\System\CurrentControlSet\Services" will have a type that includes the `SERVICE_USER_SERVICE` (0x40) flag. The service control manager generates an LUID for each user session, and an instance of the user service that includes this LUID in its name is started in the session. Some examples: "CDPUserSvc_LUID", "OneSyncSvc_LUID", "WpnUserService_LUID", "LxssManagerUser_LUID". – Eryk Sun Nov 08 '19 at 00:00

1 Answers1

1

The comments are detailed enough to point out the cause, EnumWindows enumerates by session.

Create a user service is a feasible method. And the following method is also effective:

TCHAR Command[MAX_PATH] = L"C:\\EnumWindows.exe";

DWORD sessionIdDw = WTSGetActiveConsoleSessionId();
logfile(sessionIdDw);
HANDLE hToken;
if (!WTSQueryUserToken(sessionIdDw, &hToken))
    LOG() << "WTSQueryUserToken failed: " << GetLastError();
PROCESS_INFORMATION pi;
STARTUPINFO si;
ZeroMemory(&pi, sizeof(pi));
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);

if(!CreateProcessAsUser(hToken,NULL,Command,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi))
    LOG() << "CreateProcessAsUser failed: " << GetLastError();
else
{
    WaitForSingleObject(pi.hProcess, INFINITE);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
}

The new process is running in user session.

EDIT:

Thanks @Eryk for pointing out, Window Stations:

Each session is associated with its own interactive window station

SetThreadDesktop:

The desktop must be associated with the current window station for the process.

SetProcessWindowStation:

The window station must be associated with the current session.

So SetThreadDesktop doesn't work here.

Drake Wu
  • 6,927
  • 1
  • 7
  • 30
  • 1
    `EnumWindows` enumerates windows on a Desktop, which is contained in a WindowStation. A process can only connect to a WindowStation that's in its session. Your example switches to the session 0 "WinSta0", which is unrelated to "WinSta0" of the user's session. Every session has a "WinSta0". The fully-qualified name is "\Sessions\\Windows\WindowStations\WinSta0". The API is limited to using the unqualified name "WinSta0", but since we're limited to the current session anyway, no functionality is lost. – Eryk Sun Nov 08 '19 at 05:45