Can't query, run or stop a Windows Service - c++

I have created my first Windows service following this instruction. I can start or stop my service from the Service panel in Manage-> My computer without problem.
The service account is "LocalSystem" so, I think, I'm ok with privileges.
Now I want to communicate with my service from another app, but first of all I want to stop or start my service.
Following this example I Write this code:
SC_HANDLE schSCManager;
SC_HANDLE schService;
LPCWSTR szSvcName = _T("MyNewService");
// Get a handle to the SCM database.
schSCManager = OpenSCManager(
NULL, // local computer
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (NULL == schSCManager)
{
printf("OpenSCManager failed (%d)\n", GetLastError());
return;
}
// Get a handle to the service.
schService = OpenService(
schSCManager, // SCM database
szSvcName, // name of service
SERVICE_CHANGE_CONFIG); // need change config access
if (schService == NULL)
{
printf("OpenService failed (%d)\n", GetLastError());
CloseServiceHandle(schSCManager);
return;
}
//OK until now
// Check the status in case the service is not stopped.
SERVICE_STATUS_PROCESS ssStatus;
DWORD dwBytesNeeded;
if (!QueryServiceStatusEx(
schService, // handle to service
SC_STATUS_PROCESS_INFO, // information level
(LPBYTE) &ssStatus, // address of structure
sizeof(SERVICE_STATUS_PROCESS), // size of structure
&dwBytesNeeded ) ) // size needed if buffer is too small
{
//printf("QueryServiceStatusEx failed (%d)\n", GetLastError());
CString str;
str.Format(_T("QueryServiceStatusEx failed (%d)\n"), GetLastError()); //ERROR 5: ERROR_ACCESS_DENIED
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
return;
}
// Check if the service is already running. It would be possible
// to stop the service here, but for simplicity this example just returns.
if(ssStatus.dwCurrentState != SERVICE_STOPPED && ssStatus.dwCurrentState != SERVICE_STOP_PENDING)
{
printf("Cannot start the service because it is already running\n");
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
return;
}
In any case I get the error number 5, that is "ERROR_ACCESS_DENIED: The handle does not have the SERVICE_QUERY_STATUS access right", but I'm not sure where is the problem. The service has LocalSystem account option that means all privileges and I'm the admin of my computer.. what's wrong?
I also tried with ControlService trying to start or stop the service but I get the same error about rights
BOOL success = ::ControlService(schService, SERVICE_CONTROL_STOP, &ss);
I'm on Visual Studio 2013 Update 2.
Thank you

You seem to be a little bit confused about terms "privileges" and "access rights". You are quite right that the LocalSystem account is really a privileged one, at least for actions on the local computer. However, even a privileged user must know how to take advantage if her privileges (how things work).
When an application wants to work with an object (e.g. a service), it has to declare its intention to the Windows kernel (done via OpenSCManager and OpenService in your code). The application identifies the object (e.g. by service name) and also informs the kernel what sorts of things it plans to do with it. These "sorts of things" are called "access rights" and the kenrel decides whether the application is privileged enough to obtain the access rights it is requesting. In your code, you are requesting all access rights (SC_MANAGER_ALL_ACCESS) for the SC manager and an access right to change configuration of your service (SERVICE_CHANGE_CONFIG). Then, you attempt to query status of your service, but you did not declare this intention to the kernel when requesting access to the service (the OpenService call). That's why you get the "access denied" error.
Here is a list of access rights defined for services and SC manager(s): https://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx. The document also contains information about which access rights you need to perform which actions.
To open a service, you need only the SC_MANAGER_CONNECT access right to its SC manager. To query service status, the SERVICE-QUERY_STATUS access right is required. To stop the service, request the SERVICE_STOP right.
So, the short version is: specify SERVICE_STOP | SERVICE_QUERY_STATUS desired access mask when calling OpenService, and optionally SC_MANAGER_CONNECT in the OpenSCManager call.

Problem
Your example program does not have the correct privileges to stop/start your service. It does not matter what privileges your service has.
Solution
Try running your example program as administrator by right-clicking on the exe file and selecting "Run as administrator".
If you need to elevate your process programmatically then have a look at this article.

UINT GetServiceStatus(LPCTSTR ServiceName)
{
SC_HANDLE schService;
SC_HANDLE schSCManager;
DWORD ErrorCode;
SERVICE_STATUS ssStatus;
UINT return_value;
schSCManager = OpenSCManager(
NULL, // machine (NULL == local)
NULL, // database (NULL == default)
SC_MANAGER_ALL_ACCESS // access required
);
if (!schSCManager)
return -1;
schService = OpenService(schSCManager, ServiceName, SERVICE_ALL_ACCESS);
if (!schService)
{
ErrorCode = GetLastError();
CloseServiceHandle(schSCManager);
if (ErrorCode == ERROR_SERVICE_DOES_NOT_EXIST)
return -2;
else
return -1;
}
QueryServiceStatus(schService, &ssStatus);
if (ssStatus.dwCurrentState == SERVICE_RUNNING)
return_value = 1;
else
return_value = 0;
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
return return_value;
}

Related

Remote Desktop Connection with Custom Credential Provider

I have developed a custom credential provider using the SampleWrapExistingCredentialProvider from VistaCredentialProviderSamples. The credential provider has a filter implemented that filters all other credential providers and I see just my credential provider at the time of logon. The issue is that if we connect to it using remote desktop connection, the username/password are not passed from windows RDP client to the Credential provider and I have to enter it again when RDP session opens (unlike the behavior with default provider)
I am trying to explore which part of the code handles this scenario that the credential provider accepts the username/password from remote desktop client and does not ask again. Attached is the screenshot of my credential provider after providing successful credentials on the RDP client. After I click this icon of my credential provider, I am shown the credential provider tile that asking again for the username and password. Any help would be highly appreciated on how to receive credentials from RDP client.
I have returned S_OK for CREDUI. My SetUsageScenario is as follows:
HRESULT CSampleProvider::SetUsageScenario(
CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus,
DWORD dwFlags
)
{
HRESULT hr;
// Create the password credential provider and query its interface for an
// ICredentialProvider we can use. Once it's up and running, ask it about the
// usage scenario being provided.
IUnknown *pUnknown = NULL;
hr = ::CoCreateInstance(CLSID_PasswordCredentialProvider, NULL, CLSCTX_ALL, IID_PPV_ARGS(&pUnknown));
if (SUCCEEDED(hr))
{
hr = pUnknown->QueryInterface(IID_PPV_ARGS(&(_pWrappedProvider)));
if (SUCCEEDED(hr))
{
hr = _pWrappedProvider->SetUsageScenario(cpus, dwFlags);
switch (cpus)
{
case CPUS_LOGON:
case CPUS_UNLOCK_WORKSTATION:
case CPUS_CREDUI:
{
hr = S_OK;
break;
}
case CPUS_CHANGE_PASSWORD:
default:
hr = E_INVALIDARG;
break;
}
}
}
if (FAILED(hr))
{
if (_pWrappedProvider != NULL)
{
_pWrappedProvider->Release();
_pWrappedProvider = NULL;
}
}
return hr;
}
the username/password are not passed from windows RDP client to the
Credential provider and I have to enter it again when RDP session
opens (unlike the behavior with default provider)
windows can not by some magic know client username/password, which connecting by rdp.
at begin on client side some credential provider must create CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION and pass it to server. inside it clsidCredentialProvider say which concrete provider collect this serialization.
what server must do with this CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION ? obvious pass it to some credential provider SetSerialization method. but for which ? for all ? no. again obvivous only for provider which clsid exactly matches clsidCredentialProvider from CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION. this provider (if exist and not filtered) must remember this credentional and then when called GetCredentialCount - say that it have dafault credentional (not CREDENTIAL_PROVIDER_NO_DEFAULT) and it usually ready for auto logon attempt with this.
from client side (mstsc) the password provider create serialization. so will be __uuidof(PasswordCredentialProvider) or __uuidof(V1PasswordCredentialProvider) (if client run on win7) in clsidCredentialProvider.
but you disable this providers in self filter. as result you yourself break process.
filter must implement UpdateRemoteCredential method. and here copy and update passed pcpcsIn. most importand part of this - we must replace clsidCredentialProvider to self CLSID. as result our SetSerialization method will be called. here we need restore original CLSID before pass it to wrapped credentional.
also important place - inside GetCredentialCount - first pass it to wrapped credentional and then do *pbAutoLogonWithDefault = FALSE; - disable autologon - you can not do this (autologon) if you require additional (OTP ?) information from client.
inside UpdateRemoteCredential method we can not modify pcpcsIn - if declared as const. so we need write our update credentional to pcpcsOut. because system can not know which size is required for rgbSerialization - we need allocate it yourself. and system then free it. obvivous need use CoTaskMemAlloc for allocate rgbSerialization.
so - all this can be understanded and without any documentation. however if all this was documented - will be not bad too.
so code for UpdateRemoteCredential :
HRESULT STDMETHODCALLTYPE CSampleProvider::UpdateRemoteCredential(
/* [annotation][in] */
_In_ const CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION *pcpcsIn,
/* [annotation][out] */
_Out_ CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION *pcpcsOut)
{
if (pcpcsIn->clsidCredentialProvider != __uuidof(PasswordCredentialProvider) &&
pcpcsIn->clsidCredentialProvider != __uuidof(V1PasswordCredentialProvider))
{
// we dont know format of serialization
return E_UNEXPECTED;
}
ULONG cbSerialization = pcpcsIn->cbSerialization;
if (pcpcsOut->rgbSerialization = (PBYTE)CoTaskMemAlloc(cbSerialization + sizeof(GUID)))
{
memcpy(pcpcsOut->rgbSerialization, pcpcsIn->rgbSerialization, cbSerialization);
memcpy(pcpcsOut->rgbSerialization + cbSerialization, &pcpcsIn->clsidCredentialProvider, sizeof(GUID));
pcpcsOut->cbSerialization = cbSerialization + sizeof(GUID);
pcpcsOut->ulAuthenticationPackage = pcpcsIn->ulAuthenticationPackage;
pcpcsOut->clsidCredentialProvider = __uuidof(CSampleProvider);
return S_OK;
}
return E_OUTOFMEMORY;
}
if we dont know the clsidCredentialProvider - simply return E_UNEXPECTED
oterwise allocate more (on sizeof(CLSID)) memory and save original clsidCredentialProvider in the end
now SetSerialization:
HRESULT STDMETHODCALLTYPE CSampleProvider::SetSerialization(
__in const CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION* pcpcs
)
{
if (pcpcs->clsidCredentialProvider == __uuidof(CSampleProvider))
{
// can not query WTSIsRemoteSession, small optimization
_IsRemoteSession = true;
// we got this via ICredentialProviderFilter::UpdateRemoteCredential
ULONG cbSerialization = pcpcs->cbSerialization;
if (cbSerialization >= sizeof(GUID))
{
// restore original clsidCredentialProvider
cbSerialization -= sizeof(GUID);
memcpy(const_cast<GUID*>(&pcpcs->clsidCredentialProvider), pcpcs->rgbSerialization + cbSerialization, sizeof(GUID));
const_cast<CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION*>(pcpcs)->cbSerialization = cbSerialization;
}
}
return _pWrappedProvider->SetSerialization(pcpcs);
}
restore original clsidCredentialProvider and fix cbSerialization. also because pcpcs->clsidCredentialProvider == __uuidof(CSampleProvider) can be set only inside UpdateRemoteCredential in my case (i not do CPUS_CREDUI on client side for RDP, only for "run as admin") - i just know that this is remote connection and save this information (_IsRemoteSession = true;) for not call WTSIsRemoteSession
finally GetCredentialCount:
HRESULT STDMETHODCALLTYPE CSampleProvider::GetCredentialCount(
__out DWORD* pdwCount,
__out_range(<,*pdwCount) DWORD* pdwDefault,
__out BOOL* pbAutoLogonWithDefault
)
{
HRESULT hr = _pWrappedProvider->GetCredentialCount(pdwCount, pdwDefault, pbAutoLogonWithDefault);
*pbAutoLogonWithDefault = FALSE;//!!!
return hr;
}
note very important *pbAutoLogonWithDefault = FALSE;//!!! line
According to official documentationhttps: RDC and Custom Credential Providers
If the user connected with a non-Microsoft credential provider, then
you will be prompted on the terminal server to enter credentials again
(twice). If NLA is not enabled, then despite entering using an
unsupported credential provider on the client prior to the connection,
the user will still be connected. You will be left at the logon
screen, where you can use any credential provider that is supported
for local authentication. There’s no way to avoid the two
authentications when using unsupported credential providers.
Having said that, if you have your own credential providers and you
try to do a remote desktop connection to a Vista box (having this
Credential provider) then you would need to log-in twice. This is an
expected behavior and it is by design and there is no legitimate way
to avoid it.

How to drop elevated privileges when i no longer need them

I have an application that needs to register controls when it is Run As Administrator and I would like the application to drop the elevated privileges when they are no longer needed. I have read that this can be done with AdjustTokenPrivileges, (Dropping privileges in C++ on Windows) but I have not found any sample code that will allow me to go from SECURITY_MANDATORY_HIGH_RID to SECURITY_MANDATORY_MEDIUM_RID. My code is written C++ using Visual Studio.
if you want
sample code that will allow me to go from SECURITY_MANDATORY_HIGH_RID
to SECURITY_MANDATORY_MEDIUM_RID.
you need open own process token with TOKEN_ADJUST_DEFAULT (for change integrity level - this is mandatory) and WRITE_OWNER (for change mandatory label in your token security descriptor - otherwise you not be able more open own token with write access more - but this is optional)
call SetTokenInformation with TokenIntegrityLevel let downgrade current integrity level. after this it already can not be raised.
also internally system disable some privileges in token, when we set integrity level below SECURITY_MANDATORY_HIGH_RID. i doubt that this is documented, but from my test, next privileges is disabled and can not be more enabled:
SeTakeOwnershipPrivilege
SeLoadDriverPrivilege
SeBackupPrivilege
SeRestorePrivilege
SeDebugPrivilege
SeImpersonatePrivilege
SeDelegateSessionUserImpersonatePrivilege
but you still will be member of admin group (S-1-5-32-544 Administrators) and this group can not be disabled by AdjustTokenGroups because function cannot disable groups with the SE_GROUP_MANDATORY attribute - but S-1-5-32-544 have this attribute. and change primary process token also not possible already (it possible only after process created (in suspended state) and before it begin executed (resumed) if we have SeAssignPrimaryTokenPrivilege )
so really your application will be in intermediate state after downgrade integrity level to medium - from one side you lost most significant privileges, but access to objects (files, registry keys) primary based not on privileges but group membership and mandatory label - vs integrity level. because Administrators group still will be enabled in your token and most objects not have explicit mandatory label (so medium by default) - your app still be able open/create files/keys which limited applications (admin under uac) can not. however if you downgrade you integrity level to SECURITY_MANDATORY_LOW_RID - you really got limited application, but most legacy code incorrect worked under LowLevel integrity
minimal code for downgrade integrity level:
ULONG SetMediumLevel()
{
HANDLE hToken;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_DEFAULT, &hToken))
{
ULONG cbSid = GetSidLengthRequired(1);
TOKEN_MANDATORY_LABEL tml = { { alloca(cbSid)} };
ULONG dwError = NOERROR;
if (!CreateWellKnownSid(WinMediumLabelSid, 0, tml.Label.Sid, &cbSid) ||
!SetTokenInformation(hToken, TokenIntegrityLevel, &tml, sizeof(tml)))
{
dwError = GetLastError();
}
CloseHandle(hToken);
return dwError;
}
return GetLastError();
}
but here exist thin point - token itself have security descriptor with explicit label. and high integrity process have High Mandatory Label with SYSTEM_MANDATORY_LABEL_NO_WRITE_UP access policy. this mean that we no more can open our process token with write access (TOKEN_ADJUST_* ) (with read access can). this can create problems if app in some place try open self process token with this access (some bad design code can when need query own process token properties instead TOKEN_QUERY access ask TOKEN_ALL_ACCESS and fail in this point). for prevent this potential issue we can change mandatory label of our token security descriptor before downgrade it integrity:
ULONG SetMediumLevel()
{
HANDLE hToken;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_DEFAULT|WRITE_OWNER, &hToken))
{
ULONG cbSid = GetSidLengthRequired(1);
TOKEN_MANDATORY_LABEL tml = { { alloca(cbSid)} };
ULONG dwError = NOERROR;
if (CreateWellKnownSid(WinMediumLabelSid, 0, tml.Label.Sid, &cbSid))
{
SECURITY_DESCRIPTOR sd;
ULONG cbAcl = sizeof(ACL) + FIELD_OFFSET(SYSTEM_MANDATORY_LABEL_ACE, SidStart) + cbSid;
PACL Sacl = (PACL)alloca(cbAcl);
if (!InitializeAcl(Sacl, cbAcl, ACL_REVISION) ||
!AddMandatoryAce(Sacl, ACL_REVISION, 0, SYSTEM_MANDATORY_LABEL_NO_WRITE_UP, tml.Label.Sid) ||
!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION) ||
!SetSecurityDescriptorSacl(&sd, TRUE, Sacl, FALSE) ||
!SetKernelObjectSecurity(hToken, LABEL_SECURITY_INFORMATION, &sd) ||
!SetTokenInformation(hToken, TokenIntegrityLevel, &tml, sizeof(tml)))
{
dwError = GetLastError();
}
}
CloseHandle(hToken);
return dwError;
}
return GetLastError();
}

Windows services installation / uninstallation in Windows 10

I have an application, in C++, which attempts, upon running (at which time, no other relevant process is thought to be running), to install a couple of services.
The workflow is that if the services exist, then they are uninstalled, else they and uninstalled and then re-installed.
I had followed a few tutorials like :
Installing a service
Unfortunately, something seems to go wrong with the installation or the uninstallation.
Upon assuming that the service is not installed, when I proceed to install the services, they get installed, and I then attempt to change the configuration, to delayed-Auto start.
At this stage, although the services are installed, I cannot start them, as I get the error saying services cannot be found.
When I try uninstalling the services, the uninstallation fails, giving an error 1060, saying the services are marked for deletion.
SC_HANDLE schSCManager;
SC_HANDLE schService;
schSCManager = OpenSCManager(
NULL, // local computer
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (schSCManager == nullptr)
{
continue;
}
if (bInstall)
{
schService = CreateService(
schSCManager, // SCM database
szServiceName, // name of service
szDisplayName, // service name to display
SERVICE_ALL_ACCESS, // desired access
SERVICE_WIN32_OWN_PROCESS, // service type
SERVICE_DEMAND_START, // start type
SERVICE_ERROR_NORMAL, // error control type
szDirectory, // path to service's binary
NULL, // no load ordering group
NULL, // no tag identifier
NULL, // no dependencies
NULL, // LocalSystem account
NULL); // no password
if (schService == NULL)
{
TraceAdvice(L"CreateService failed (%d)\n", GetLastError());
CloseServiceHandle(schSCManager);
continue;
}
else
{
if (!ChangeServiceConfig(
schService, // handle of service
SERVICE_NO_CHANGE, // service type: no change
SERVICE_CONFIG_DELAYED_AUTO_START_INFO, // service start type
SERVICE_NO_CHANGE, // error control: no change
NULL, // binary path: no change
NULL, // load order group: no change
NULL, // tag ID: no change
NULL, // dependencies: no change
NULL, // account name: no change
NULL, // password: no change
NULL)) // display name: no change
{
TraceAdvice(L"ChangeServiceConfig failed (%d)\n", GetLastError());
}
TraceAdvice(L"Service installed successfully\n");
}
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
/*TraceFatal(_T("Now installing : %s"), szServiceName);
_stprintf_s(szTmp, _T("SC create %s binpath= \"%s%s.exe\" displayName= \"%s\" start= delayed-auto"), szServiceName, szDirectory, szServiceName, szDisplayName);
TraceFatal(_T("Command is : %s"), szTmp);*/
}
else
{
_wsystem(_T("taskkill /F /IM mmc.exe")); // Need to kill any instance of MMC running
_wsystem(_T("taskkill /F /IM procexp.exe"));
schService = OpenService(
schSCManager, // SCM database
szServiceName, // name of service
DELETE); // need delete access
if (schService == NULL)
{
TraceAdvice(L"OpenService failed (%d)\n", GetLastError());
CloseServiceHandle(schSCManager);
continue;
}
DWORD dwBytesNeeded;
SERVICE_STATUS_PROCESS ssp;
if (!QueryServiceStatusEx(
schService,
SC_STATUS_PROCESS_INFO,
(LPBYTE)&ssp,
sizeof(SERVICE_STATUS_PROCESS),
&dwBytesNeeded))
{
printf("QueryServiceStatusEx failed (%d)\n", GetLastError());
}
if (ssp.dwCurrentState == SERVICE_RUNNING)
{
_stprintf_s(szTmp, _T("taskkill /F /IM %s.exe"), szServiceName);
_wsystem(szTmp);
}
// Delete the service.
if (!DeleteService(schService))
{
TraceAdvice(L"DeleteService failed (%d)\n", GetLastError());
}
else TraceAdvice(L"Service deleted successfully\n");
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
}
}
I made sure of all the points, viz.
Task manager was closed
Service console was closed.
All instances of MMC were closed.
Service was stopped
Service was not open in debugger in Visual Studio...
But it does not avail. The service does not get uninstalled till I reboot the system (deleting the registry linked to the service does not work either).
I have checked the parameters I pass on to these functions, and they seem correct.
What else should I be checking for, to ensure a correct installation, or a successful uninstallation?
You need to stop the service first, otherwise it gets stuck in this pending delete state. Forcibly killing the service is not the same thing as stopping it. From the same area of documentation, you need to ControlService(SERVICE_CONTROL_STOP).

Do I jeopardize security of the system if I start a logon user process with the SYSTEM user token?

I'm curious from a security standpoint, how bad is it to do what I describe below?
I need to launch an elevated process in an interactive logon user session from my local service. This process merely exists as a message-only GUI window, which is never visible to the user, and it's class name is randomized every time the process starts.
It speeds things up if I run this process with the user token of the local service as such:
//Pseudo-code, error checks are omitted for brevity
//This code is run from a local-service with SYSTEM credentials
PSID gpSidMIL_High;
ConvertStringSidToSid(L"S-1-16-12288", &gpSidMIL_High);
HANDLE hToken, hToken2;
OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken);
DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hToken2);
SetTokenInformation(hToken2, TokenSessionId, &userSessionID, sizeof(userSessionID));
DWORD dwUIAccess = 1;
SetTokenInformation(hToken2, TokenUIAccess, &dwUIAccess, sizeof(dwUIAccess));
//Set "high" mandatory integrity level
TOKEN_MANDATORY_LABEL tml = {0};
tml.Label.Attributes = SE_GROUP_INTEGRITY;
tml.Label.Sid = gpSidMIL_High;
SetTokenInformation(hToken2, TokenIntegrityLevel, &tml, sizeof(TOKEN_MANDATORY_LABEL) + ::GetSidLengthRequired(1));
CreateEnvironmentBlock(&pEnvBlock, hToken2, FALSE);
ImpersonateLoggedOnUser(hToken2);
CreateProcessAsUser(hToken2,,,,,,,pEnvBlock,,);
RevertToSelf();
//Clean-up
DestroyEnvironmentBlock(pEnvBlock);
CloseHandle(hToken2);
CloseHandle(hToken);
LocalFree(gpSidMIL_High);
Looks pretty bad. The process has way too much rights. The process is at the risk of being hijacked by the user in whose session you run, which would give him SYSTEM rights that he generally does not own.
The proper design is to have the hook process be capable of doing nothing. Communicate the keyboard events back to the service. Your hook doesn't need to be SYSTEM for this. It's probably wise to call AdjustTokenPrivileges to irrevocably drop all privileges (which you don't need). Even if your hook process was hijacked, it can't regain those privileges.

How do I detect that my application is running as service or in an interactive session?

I'm writing an application that is able to run as a service or standalone but I want to detect if the application was executed as a service or in a normal user session.
If this is a C++ application, somewhere in your startup code you have to call StartServiceCtrlDispatcher. If it fails and GetLastError() returns ERROR_FAILED_SERVICE_CONTROLLER_CONNECT, the app has not been started as a service.
Another option would be to use System.Environment.UserInteractive
http://msdn.microsoft.com/en-us/library/system.environment.userinteractive.aspx
Update: To make up for posting a .NET answer to a C++ topic, I provide a C implementation based on the .NET implementation.
BOOL IsUserInteractive()
{
BOOL bIsUserInteractive = TRUE;
HWINSTA hWinStation = GetProcessWindowStation();
if (hWinStation != NULL)
{
USEROBJECTFLAGS uof = {0};
if (GetUserObjectInformation(hWinStation, UOI_FLAGS, &uof, sizeof(USEROBJECTFLAGS), NULL) && ((uof.dwFlags & WSF_VISIBLE) == 0))
{
bIsUserInteractive = FALSE;
}
}
return bIsUserInteractive;
}
I think you can query the process token for membership in the Interactive group.
From http://support.microsoft.com/kb/243330:
SID: S-1-5-4
Name: Interactive
Description: A group that includes all users that have logged on interactively. Membership is controlled by the operating system.
Call GetTokenInformation with TokenGroups to get the groups associated with the account under which the process is running, then iterate over the sids looking for the Interactive sid.
I found a nice chunk of code at http://marc.info/?l=openssl-dev&m=104401851331452&w=2
I think you can base your detection on the fact that services are running with SessionID 0 and user accounts do have other values (like 1).
bServiceMode = false;
SessionID=-1;
Size=0;
hToken = NULL;
(!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
GetLastError();
if (!GetTokenInformation(hToken, TokenSessionId, &SessionID, sizeof(SessionID), &Size) || !Size)
return FALSE;
if(SessionID==0)
bServiceMode = true;
All of the above methods are unreliable. Session Id is not necessarily 0 (at least not in previous Windows versions), Window Station is only WinSta0 if "If the service is running in the LocalSystem account and is interacting with the desktop".
See KB171890 for more details.
One method for detecting if a process is running as service is following:
Please note: Only services installed in services database will be detected with this method, but not child processes started by a service process that are not registered in the database. In this case, it would not also be a system service. *1.
bool IsRunningAsService(unsigned int Pid) {
bool Result = false;
SC_HANDLE hScm = OpenSCManager(
0,
SERVICES_ACTIVE_DATABASE,
SC_MANAGER_ENUMERATE_SERVICE
);
if (hScm == 0) {
return Result;
}
DWORD ServicesBufferRequired = 0;
DWORD ResumeHandle = 0;
DWORD ServicesBufferSize = 0;
DWORD ServicesCount = 0;
ENUM_SERVICE_STATUS_PROCESS* ServicesBuffer = 0;
EnumServicesStatusEx(hScm, SC_ENUM_PROCESS_INFO, SERVICE_WIN32,
SERVICE_ACTIVE, 0, 0, &ServicesBufferRequired, &ServicesCount, &ResumeHandle, 0);
// Todo: Error handling (GetLastError() results are currently bogus?)
ServicesBuffer = (ENUM_SERVICE_STATUS_PROCESS*) new
char[ServicesBufferRequired];
ServicesBufferSize = ServicesBufferRequired;
EnumServicesStatusEx(hScm, SC_ENUM_PROCESS_INFO, SERVICE_WIN32,
SERVICE_ACTIVE, (LPBYTE) ServicesBuffer, ServicesBufferSize,
&ServicesBufferRequired, &ServicesCount, &ResumeHandle, 0);
ENUM_SERVICE_STATUS_PROCESS* ServicesBufferPtr = ServicesBuffer;
while (ServicesCount--) {
if (ServicesBufferPtr->ServiceStatusProcess.dwProcessId == Pid) {
Result = true;
break;
}
ServicesBufferPtr++;
}
delete [] ServicesBuffer;
CloseServiceHandle(hScm);
return Result;
}
Please note, the code above should contain additional error handling, especially it should be called in a loop until EnumServicesStatusEx returns nonzero. But unfortunetaly as I found out, GetLastError() always returns 1 (ERROR_INVALID_FUNCTION) even if the buffer is correctly filled with data.
*1: Testing if a process was started by a service: In this case you could use a combination of other solutions. One could test, if the process has a parent (grandparent...) process that is a registered as a service. You could use CreateToolhelp32Snapshot API for this purpose. However if the parent process is already killed, things getting difficult. I'm sure there are any undocumented settings which can determine whether a process is running as a service apart from the usual suspects like SessionId = 0, WindowStation = 0, WSF_VISIBLE, No Interactive Group membership...
There is a simple way to detect whether the application is started as a service. When you create a service with CreateService, pass in lpBinaryPathName parameter some additional argument, say -s which would indicate that your application is started as a service. Then in the application you can check for this argument. It can also possibly help when debugging, because you can test your service functionality without actually running as a service. If StartServiceCtrlDispatcher fails with ERROR_FAILED_SERVICE_CONTROLLER_CONNECT, you can set a flag indicating the program is running as a console application simulating a service mode, so you can skip service related API calls using this flag.
Process in normal user session always has a window station called WinSta0.
wchar_t buffer[256] = {0};
DWORD length = 0;
GetUserObjectInformation(GetProcessWindowStation(), UOI_NAME, buffer, 256, &length);
if (!lstricmp(buffer, "WinSta0")) {
// normal user session
} else {
// service session
}