I wrote a windows service (and it runs fine). Now i have a separate app where I want to start this service from, but it seems this is not possible without administrator rights.
How would a proper solution look like that a user can start/stop the service (e.g. from a tray or application)
IMHO its bad that the application must always be started with administrator rights.
You just need to change the permissions on the service object, preferably at the same time you install it.
wchar_t sddl[] = L"D:"
L"(A;;CCLCSWRPWPDTLOCRRC;;;SY)" // default permissions for local system
L"(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)" // default permissions for administrators
L"(A;;CCLCSWLOCRRC;;;AU)" // default permissions for authenticated users
L"(A;;CCLCSWRPWPDTLOCRRC;;;PU)" // default permissions for power users
L"(A;;RP;;;IU)" // added permission: start service for interactive users
;
PSECURITY_DESCRIPTOR sd;
if (!ConvertStringSecurityDescriptorToSecurityDescriptor(sddl, SDDL_REVISION_1, &sd, NULL))
{
fail();
}
if (!SetServiceObjectSecurity(service, DACL_SECURITY_INFORMATION, sd))
{
fail();
}
I'm assuming here you've already opened the service handle. You need WRITE_DAC permission.
If you also want non-admin users to be able to stop the service, add the WP right, i.e.,
L"(A;;RPWP;;;IU)"
// added permissions: start service, stop service for interactive users
SDDL codes for service rights can be found in Wayne Martin's blog entry, Service Control Manager Security for non-admins.
#Harry Johnston, in addition to response.
Here is c++ builder example.
void __fastcall TService1::ServiceAfterInstall(TService *Sender)
{
wchar_t lpBuffer[256];
long errorCode;
SC_HANDLE hSCManager,hService;
hSCManager = OpenSCManager(0, 0, SC_MANAGER_CONNECT);
if (hSCManager == NULL)
{
errorCode = GetLastError();
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), lpBuffer, 256, NULL);
LogMessage("OpenSCManager Error "+AnsiString(lpBuffer), EVENTLOG_ERROR_TYPE);
return;
}
hService = OpenService(hSCManager, this->Name.c_str(), READ_CONTROL | WRITE_DAC);
if (hService == NULL)
{
errorCode = GetLastError();
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), lpBuffer, 256, NULL);
LogMessage("OpenService Error "+AnsiString(lpBuffer), EVENTLOG_ERROR_TYPE);
CloseServiceHandle(hSCManager);
}
wchar_t sddl[] = L"D:"
L"(A;;CCLCSWRPWPDTLOCRRC;;;SY)" // default permissions for local system
L"(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)" // default permissions for administrators
L"(A;;CCLCSWLOCRRC;;;AU)" // default permissions for authenticated users
L"(A;;CCLCSWRPWPDTLOCRRC;;;PU)" // default permissions for power users
L"(A;;RP;;;IU)" // added permission: start service for interactive users
;
PSECURITY_DESCRIPTOR sd;
if (!ConvertStringSecurityDescriptorToSecurityDescriptor(AnsiString(sddl).c_str(), SDDL_REVISION_1, &sd, NULL))
{
errorCode = GetLastError();
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), lpBuffer, 256, NULL);
LogMessage("ConvertStringSecurityDescriptorToSecurityDescriptor Error "+AnsiString(lpBuffer), EVENTLOG_ERROR_TYPE);
}
if (!SetServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, sd))
{
errorCode = GetLastError();
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), lpBuffer, 256, NULL);
LogMessage("SetServiceObjectSecurity Error "+AnsiString(lpBuffer), EVENTLOG_ERROR_TYPE);
}
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
}
Starting a service programmatically is done with the StartService function. There is a comprehensive usage example also given under the title starting a service, which also shows how to:
detect that the service is for some reason shutting down
wait until the service is in a stable state (started/stopped)
start the service programmatically
As for administrator rights, this is necessary because if just about any application could shut services down (or, more importantly, install and start new services) there would be very real and very serious security issues.
#Harry Johnston 's worked fine for me, in case someone wants to do this in C#:
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool SetServiceObjectSecurity(SafeHandle serviceHandle,
UInt32 secInfos,
IntPtr lpSecDesrBuf);
[DllImport("advapi32.dll", EntryPoint = "ConvertStringSecurityDescriptorToSecurityDescriptorW", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern Boolean ConvertStringSecurityDescriptorToSecurityDescriptor(
[MarshalAs(UnmanagedType.LPWStr)] String strSecurityDescriptor,
UInt32 sDRevision,
ref IntPtr securityDescriptor,
ref UInt32 securityDescriptorSize);
public static void SetServicePermissions(string service)
{
System.ServiceProcess.ServiceController sc = new System.ServiceProcess.ServiceController(service);
bool ok;
IntPtr pSD = IntPtr.Zero;
uint securityDescriptorSize = 0;
string secDesc = "D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)(A;;RPWP;;;IU)";
ok = ConvertStringSecurityDescriptorToSecurityDescriptor(secDesc, 1, ref pSD, ref securityDescriptorSize);
if (!ok)
{
throw new ApplicationException("error calling ConvertStringSecurityDescriptorToSecurityDescriptor(): error code=" + Marshal.GetLastWin32Error());
}
ok = SetServiceObjectSecurity(sc.ServiceHandle, 4 , pSD);
if (!ok)
{
throw new ApplicationException("error calling SetServiceObjectSecurity(); error code=" + Marshal.GetLastWin32Error());
}
}
Related
I am trying to create a Windows Service that is able to log on my user programatically into my local windows 10 pc the same as if i was typing directly in front of my keyboard. (I want to be able to initialize my session remotely)
I am using LogonUser and it returns properly but i will never see the user to appear as loged in in the Task Manager for example.
This is what i have tried now:
BSINFO(L"Login User...");
HANDLE sessToken = nullptr;
BOOL bRes = LogonUserW(
L"TestUser",
L".",
L"1234",
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
&sessToken);
if (bRes)
{
PROFILEINFO profInfo;
ZeroMemory(&profInfo,sizeof(PROFILEINFO));
profInfo.dwSize = sizeof(PROFILEINFO);
profInfo.lpUserName = L"TestUser";
profInfo.lpServerName = L"";
bRes = LoadUserProfileW(sessToken,&profInfo);
if (!bRes)
{
DWORD dwError = GetLastError();
BSERROR(L"Error LoadUserProfileW. %d '%ls'", dwError, GetLastErrorW(dwError).c_str());
}
}
else
{
DWORD dwError = GetLastError();
BSERROR(L"Error login user. %d '%ls'", dwError, GetLastErrorW(dwError).c_str());
}
Is it something possible to do?
Are there any alternatives to LsaLogonUser for impersonating given account in order to access network resources? I'm looking for the method of impersonation which would let me call a remote machine via UNC in the same domain.
For initial data I have: domain\username.
I have no password, because this I am using LsaRegisterLogonProcess and LsaLogonUser with Kerberos package. Locally everything is working and I can impersonate and call applications using a different username, however, when trying to access a remote machine using.. \\remotemachine\shared\somefile.bat I have Access Denied.
Basically I call cmd.exe as command.. and then a Console opens with a new user impersonated, but if a Try to call the UNC path, Access Denied.
If I open a basic console with my own user, I execute this UNC path successfully. Is not Folder permissions because is a public Share.
This is a part of my working code:
...
NTSTATUS Status = STATUS_SUCCESS;
LSA_OPERATIONAL_MODE unused;
LSA_STRING lsaString;
lsaString.Buffer = "User Impersonated LogonProcess";
lsaString.Length = strlen(lsaString.Buffer);
lsaString.MaximumLength = lsaString.Length + 1;
Status = LsaRegisterLogonProcess(&lsaString, &lsa, &unused);
if (Status != STATUS_SUCCESS) {
printf("LsaRegisterLogonProcess failed: %x\n", Status);
}
...
NTSTATUS status = LsaLogonUser(
lsa,
&origin,
Network,
packageId,
authInfo,
authInfoSize,
0,
&source,
&profileBuffer,
&profileBufLen,
&luid,
&token,
&qlimits,
&subStatus
);
if (status != ERROR_SUCCESS)
{
ULONG err = LsaNtStatusToWinError(status);
printf("LsaLogonUser failed: %x\n", status);
return 1;
}
...
DuplicateTokenEx(token, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &tokenDuplicate);
if (CreateProcessWithTokenW(
tokenDuplicate,
LOGON_WITH_PROFILE,
command,
command_arguments,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&si,
&pi))
{
pi.hThread = NULL;
pi.hProcess = NULL;
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
My goal is to copy privileges from one primary user token to another before I start a user mode process with the destination token. I created a sample pseudo code to illustrate what I need to accomplish.
The following will be run from a local system service:
//dwSessionId = user session ID to run process in
HANDLE hToken1 = NULL; //Source user token
WTSQueryUserToken(dwSessionId, &hToken1);
HANDLE hSelfToken = NULL; //User token for system service
HANDLE hToken2 = NULL; //Adjusted self-token
OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, &hSelfToken);
DuplicateTokenEx(hSelfToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS,
NULL, SecurityIdentification, TokenPrimary, &hToken2);
//Specify user session to run in
SetTokenInformation(hToken2, TokenSessionId, &dwSessionId, sizeof(dwSessionId));
//Now I need to set privileges in 'hToken2' as they are in 'hToken1'
...
//Then use 'hToken2' in CreateProcessAsUser() to start a process
Any idea how to copy privileges from hToken1 to hToken2?
Use the following code to obtain hToken2 from hToken, and then use it in CreateProcessAsUser. There is no need to use hSelfToken, actually.
HANDLE GetAdjustedToken(HANDLE hSrcToken)
{
TOKEN_LINKED_TOKEN admin = {};
HANDLE hTarToken = 0;
DWORD dw = 0;
if (GetTokenInformation(hSrcToken, (TOKEN_INFORMATION_CLASS)TokenLinkedToken, &admin, sizeof(TOKEN_LINKED_TOKEN), &dw))
{
hTarToken = admin.LinkedToken;
}
else
{
DuplicateTokenEx(hSrcToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hTarToken);
}
return hTarToken;
}
And if you want to create the new process at a low mandatory integrity level, see this MSDN article.
First you have to call GetTokenInformation using TokenPrivileges class for your original token to obtain TOKEN_PRIVILEGES structure. Then call AdjustTokenPrivileges with it to update your cloned token. You will have to call GetTokenInformation twice - first one to obtain buffer length and second - to get actual data.
HANDLE hSelfToken = NULL; //User token for system service
HANDLE hToken2 = NULL; //Adjusted self-token
OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, &hSelfToken);
DuplicateTokenEx(hSelfToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS,
NULL, SecurityIdentification, TokenPrimary, &hToken2);
PTOKEN_PRIVILEGES pPriv = NULL;
DWORD dwLen = 0;
GetTokenInformation(hSelfToken, TokenPrivileges, (LPVOID)pPriv, 0, &dwLen);
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
return(0);
pPriv = (PTOKEN_PRIVILEGES)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLen);
if (!GetTokenInformation(hSelfToken, TokenPrivileges, (LPVOID)pPriv, dwLen, &dwLen))
return(0);
AdjustTokenPrivileges(hToken2, FALSE, pPriv, dwLen, NULL, NULL);
OK. I might have gotten something close to what I needed. It's not really copying privileges, but for the purpose of "stripping out" privileges I can create a restricted token that will have almost all privileges removed (except SeChangeNotifyPrivilege and SeSystemtimePrivilege.)
//Call the following instead of DuplicateTokenEx() in my example above
CreateRestrictedToken(hSelfToken, DISABLE_MAX_PRIVILEGE,
0, NULL, 0, NULL, 0, NULL,
&hToken2);
FYI: Just a curious fact. For some reason I was not able to remove the SeSystemtimePrivilege from the token. Not with this method, nor using AdjustTokenPrivileges with its attribute set to SE_PRIVILEGE_REMOVED. All other privileges could be removed just fine, except this one. So if anyone have any idea why, I'd be glad to learn it?
I'm a Domain Admin and I want to take ownership of some shared folders on some server of my domain programmatically in API(for example C++). I did some reading work and found that a Domain Admin is in the member machine's Local Admins group by default, and the Local Admins users can take ownership anyway. I just wrtie some code in this way but still encountered ERROR_ACCESS_DENIED when getting the owner sid using GetNamedSecurityInfo? Where's the problem?
Something interesting is: When I changed the GetNamedSecurityInfo's secound argument from SE_FILE_OBJECT to SE_LMSHARE, it would succeed(also set one). But I didn't see the owner changed in the "Security" tab of folder's properties. I know a "share" permission is different with "security" one. a "share" permission even don't have a owner. So what owner did I get when calling GetNamedSecurityInfo by the SE_LMSHARE argument?
Here's the function i use for Taking ownership for the folder "strFileName", on server "strServerName", the owner changed to is just the Domain Admin account known as "strDomainName" "strUserName" "strPassword", orginal owner is reserved in "pOriginSID".
I got error code 5 in the GetNamedSecurityInfo call (also the Set one). I also write a impersonation method "logOnByUserPassword" which seems not to work, i paste it below.
HANDLE ADPermissionSearch::getAccessTokenByCredential(CString strDomainName, CString strUserName, CString strPassword)
{
CString strUPNUserName = strUserName + _T("#") + strDomainName;
HANDLE hToken;
BOOL bResult;
//bResult = LogonUser(strUserName, strDomainName, strPassword, LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_DEFAULT,
// &hToken);
if (strDomainName != _T(""))
{
bResult = LogonUser(strUPNUserName, _T(""), strPassword, LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_DEFAULT,
&hToken);
}
else
{
bResult = LogonUser(strUserName, _T("."), strPassword, LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_DEFAULT,
&hToken);
}
if (bResult == FALSE)
{
MyMessageBox_Error(_T("getAccessTokenByCredential Error."), _T("Error"));
return FALSE;
}
else
{
return hToken;
}
}
int ADPermissionSearch::takeOwnership(CString strServerName, CString strFileName, CString strDomainName, CString strUserName, CString strPassword, __out PSID &pOriginSID)
{
CString strUNCFileName = _T("\\\\") + strServerName + _T("\\") + strFileName;
_bstr_t bstrUNCFileName = _bstr_t(strUNCFileName);
PSID pSIDAdmin = NULL;
SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY;
HANDLE hToken = NULL;
DWORD dwRes;
// Create a SID for the BUILTIN\Administrators group.
if (!AllocateAndInitializeSid(&SIDAuthNT, 2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
&pSIDAdmin))
{
if (pSIDAdmin)
FreeSid(pSIDAdmin);
if (hToken)
CloseHandle(hToken);
MyMessageBox_Error(_T("takeOwnership"));
return 0;
}
// If the preceding call failed because access was denied,
// enable the SE_TAKE_OWNERSHIP_NAME privilege, create a SID for
// the Administrators group, take ownership of the object, and
// disable the privilege. Then try again to set the object's DACL.
// Open a handle to the access token for the calling process.
/*
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES,
&hToken))
{
if (pSIDAdmin)
FreeSid(pSIDAdmin);
if (hToken)
CloseHandle(hToken);
MyMessageBox_Error(_T("takeOwnership"));
return 0;
}
*/
if ((hToken = getAccessTokenByCredential(strDomainName, strUserName, strPassword)) == NULL)
{
if (pSIDAdmin)
FreeSid(pSIDAdmin);
if (hToken)
CloseHandle(hToken);
MyMessageBox_Error(_T("takeOwnership"));
return 0;
}
// Enable the SE_TAKE_OWNERSHIP_NAME privilege.
if (!setPrivilege(hToken, SE_TAKE_OWNERSHIP_NAME, TRUE))
{
if (pSIDAdmin)
FreeSid(pSIDAdmin);
if (hToken)
CloseHandle(hToken);
MyMessageBox_Error(_T("takeOwnership"));
return 0;
}
// Get the original owner in the object's security descriptor.
dwRes = GetNamedSecurityInfo(
bstrUNCFileName, // name of the object
SE_FILE_OBJECT, // type of object
OWNER_SECURITY_INFORMATION, // change only the object's owner
&pOriginSID, // SID of Administrator group
NULL,
NULL,
NULL,
NULL);
if (dwRes != ERROR_SUCCESS)
{
if (pSIDAdmin)
FreeSid(pSIDAdmin);
if (hToken)
CloseHandle(hToken);
MyMessageBox_Error(_T("takeOwnership"));
return 0;
}
// Set the owner in the object's security descriptor.
dwRes = SetNamedSecurityInfo(
bstrUNCFileName, // name of the object
SE_FILE_OBJECT, // type of object
OWNER_SECURITY_INFORMATION, // change only the object's owner
pSIDAdmin, // SID of Administrator group
NULL,
NULL,
NULL);
if (dwRes != ERROR_SUCCESS)
{
if (pSIDAdmin)
FreeSid(pSIDAdmin);
if (hToken)
CloseHandle(hToken);
MyMessageBox_Error(_T("takeOwnership"));
return 0;
}
// Disable the SE_TAKE_OWNERSHIP_NAME privilege.
if (!setPrivilege(hToken, SE_TAKE_OWNERSHIP_NAME, FALSE))
{
if (pSIDAdmin)
FreeSid(pSIDAdmin);
if (hToken)
CloseHandle(hToken);
MyMessageBox_Error(_T("takeOwnership"));
return 0;
}
return 1;
}
BOOL ADDirectorySearch::logOnByUserPassword(CString strDomainName, CString strUserName, CString strPassword)
{
CString strUPNUserName = strUserName + _T("#") + strDomainName;
HANDLE hToken;
BOOL bResult;
//bResult = LogonUser(strUserName, strDomainName, strPassword, LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_DEFAULT,
// &hToken);
if (strDomainName != _T(""))
{
bResult = LogonUser(strUPNUserName, _T(""), strPassword, LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_DEFAULT,
&hToken);
}
else
{
bResult = LogonUser(strUserName, _T("."), strPassword, LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_DEFAULT,
&hToken);
}
if (bResult == FALSE)
{
MyMessageBox_Error(_T("logOnByUserPassword Error."), _T("Error"));
return FALSE;
}
else
{
bResult = ImpersonateLoggedOnUser(hToken);
if (bResult == FALSE)
{
MyMessageBox_Error(_T("logOnByUserPassword Error."), _T("Error"));
return FALSE;
}
else
{
return TRUE;
}
}
}
Local admins are subject to the usual Windows security checks with one exception: they can always take ownership of a secured object regardless of the permissions. This ensures that admins are always able to regain control.
However, you are not trying to take ownership, you are trying to read the current owner and you don't necessarily have permission to do that.
It's not clear from your code why you are trying to read the owner. You don't seem to do anything with it. Maybe remove the call to GetNamedSecurityInfo altogether.
Update
The intention is to write a program that checks the DACLs on every share. So it needs to save the current owner, take ownership, read the DACLs and restore the owner. But the current owner cannot be read until ownership has been taken.
I think this behaviour is by design. The original intention was that admins were able to take ownership, but not hide the fact that they had from the owner of an object, though there are ways around this. For example, for files you can read the complete security descriptor (including the owner) by enabling the backup privilege, calling BackupRead and parsing the output (a sequence of WIN32_STREAM_ID structures each followed by data). I don't know if there's a simpler way.
Information about shares is stored in the registry under:
SYSTEM\CurrentControlSet\Services\LanmanServer\Shares
The security info seems to be stored in the Security subkey, in a value named after the share. This binary value seems to be a security descriptor so you can read the owner with GetSecurityDescriptorOwner. You can also read all the other security info from this security descriptor, so you shouldn't need to change the owner at all.
I wrote a windows service (and it runs fine). Now i have a separate app where I want to start this service from, but it seems this is not possible without administrator rights.
How would a proper solution look like that a user can start/stop the service (e.g. from a tray or application)
IMHO its bad that the application must always be started with administrator rights.
You just need to change the permissions on the service object, preferably at the same time you install it.
wchar_t sddl[] = L"D:"
L"(A;;CCLCSWRPWPDTLOCRRC;;;SY)" // default permissions for local system
L"(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)" // default permissions for administrators
L"(A;;CCLCSWLOCRRC;;;AU)" // default permissions for authenticated users
L"(A;;CCLCSWRPWPDTLOCRRC;;;PU)" // default permissions for power users
L"(A;;RP;;;IU)" // added permission: start service for interactive users
;
PSECURITY_DESCRIPTOR sd;
if (!ConvertStringSecurityDescriptorToSecurityDescriptor(sddl, SDDL_REVISION_1, &sd, NULL))
{
fail();
}
if (!SetServiceObjectSecurity(service, DACL_SECURITY_INFORMATION, sd))
{
fail();
}
I'm assuming here you've already opened the service handle. You need WRITE_DAC permission.
If you also want non-admin users to be able to stop the service, add the WP right, i.e.,
L"(A;;RPWP;;;IU)"
// added permissions: start service, stop service for interactive users
SDDL codes for service rights can be found in Wayne Martin's blog entry, Service Control Manager Security for non-admins.
#Harry Johnston, in addition to response.
Here is c++ builder example.
void __fastcall TService1::ServiceAfterInstall(TService *Sender)
{
wchar_t lpBuffer[256];
long errorCode;
SC_HANDLE hSCManager,hService;
hSCManager = OpenSCManager(0, 0, SC_MANAGER_CONNECT);
if (hSCManager == NULL)
{
errorCode = GetLastError();
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), lpBuffer, 256, NULL);
LogMessage("OpenSCManager Error "+AnsiString(lpBuffer), EVENTLOG_ERROR_TYPE);
return;
}
hService = OpenService(hSCManager, this->Name.c_str(), READ_CONTROL | WRITE_DAC);
if (hService == NULL)
{
errorCode = GetLastError();
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), lpBuffer, 256, NULL);
LogMessage("OpenService Error "+AnsiString(lpBuffer), EVENTLOG_ERROR_TYPE);
CloseServiceHandle(hSCManager);
}
wchar_t sddl[] = L"D:"
L"(A;;CCLCSWRPWPDTLOCRRC;;;SY)" // default permissions for local system
L"(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)" // default permissions for administrators
L"(A;;CCLCSWLOCRRC;;;AU)" // default permissions for authenticated users
L"(A;;CCLCSWRPWPDTLOCRRC;;;PU)" // default permissions for power users
L"(A;;RP;;;IU)" // added permission: start service for interactive users
;
PSECURITY_DESCRIPTOR sd;
if (!ConvertStringSecurityDescriptorToSecurityDescriptor(AnsiString(sddl).c_str(), SDDL_REVISION_1, &sd, NULL))
{
errorCode = GetLastError();
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), lpBuffer, 256, NULL);
LogMessage("ConvertStringSecurityDescriptorToSecurityDescriptor Error "+AnsiString(lpBuffer), EVENTLOG_ERROR_TYPE);
}
if (!SetServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, sd))
{
errorCode = GetLastError();
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), lpBuffer, 256, NULL);
LogMessage("SetServiceObjectSecurity Error "+AnsiString(lpBuffer), EVENTLOG_ERROR_TYPE);
}
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
}
Starting a service programmatically is done with the StartService function. There is a comprehensive usage example also given under the title starting a service, which also shows how to:
detect that the service is for some reason shutting down
wait until the service is in a stable state (started/stopped)
start the service programmatically
As for administrator rights, this is necessary because if just about any application could shut services down (or, more importantly, install and start new services) there would be very real and very serious security issues.
#Harry Johnston 's worked fine for me, in case someone wants to do this in C#:
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool SetServiceObjectSecurity(SafeHandle serviceHandle,
UInt32 secInfos,
IntPtr lpSecDesrBuf);
[DllImport("advapi32.dll", EntryPoint = "ConvertStringSecurityDescriptorToSecurityDescriptorW", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern Boolean ConvertStringSecurityDescriptorToSecurityDescriptor(
[MarshalAs(UnmanagedType.LPWStr)] String strSecurityDescriptor,
UInt32 sDRevision,
ref IntPtr securityDescriptor,
ref UInt32 securityDescriptorSize);
public static void SetServicePermissions(string service)
{
System.ServiceProcess.ServiceController sc = new System.ServiceProcess.ServiceController(service);
bool ok;
IntPtr pSD = IntPtr.Zero;
uint securityDescriptorSize = 0;
string secDesc = "D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)(A;;RPWP;;;IU)";
ok = ConvertStringSecurityDescriptorToSecurityDescriptor(secDesc, 1, ref pSD, ref securityDescriptorSize);
if (!ok)
{
throw new ApplicationException("error calling ConvertStringSecurityDescriptorToSecurityDescriptor(): error code=" + Marshal.GetLastWin32Error());
}
ok = SetServiceObjectSecurity(sc.ServiceHandle, 4 , pSD);
if (!ok)
{
throw new ApplicationException("error calling SetServiceObjectSecurity(); error code=" + Marshal.GetLastWin32Error());
}
}