如何用C++ Windows API设置服务访问规则(替代sc sdset命令)
用C++ Windows API实现服务权限配置(替代
sc sdset) 我刚好做过类似的需求,其实不用把SecurityDescriptor直接转换成EXPLICIT_ACCESS结构,核心思路是:先获取服务现有安全描述符里的DACL(自由访问控制列表),然后创建目标用户的权限条目,将其与现有DACL合并生成新的ACL,最后把更新后的安全描述符写回服务。下面是具体实现步骤和代码示例:
核心步骤拆解
- 打开服务管理器和服务句柄:需要足够的权限(
SC_MANAGER_CONNECT连接服务管理器,SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG操作服务安全配置)。 - 获取服务现有安全描述符:通过
QueryServiceObjectSecurity获取,注意处理缓冲区不足的情况。 - 提取现有DACL:用
GetSecurityDescriptorDacl从安全描述符中取出当前的访问控制列表。 - 创建目标用户的EXPLICIT_ACCESS条目:指定用户账号、所需权限(启停服务对应的掩码)、访问模式(追加或覆盖)等。
- 合并生成新ACL:用
SetEntriesInAcl把新的权限条目和现有DACL合并,得到更新后的ACL。 - 更新安全描述符并写回服务:用
SetSecurityDescriptorDacl更新安全描述符的DACL,再通过SetServiceObjectSecurity将配置应用到服务。
完整代码示例
#include <windows.h> #include <aclapi.h> #include <stdio.h> #pragma comment(lib, "advapi32.lib") #pragma comment(lib, "aclui.lib") BOOL GrantServiceStartStopAccess(const wchar_t* serviceName, const wchar_t* userName) { // 打开服务管理器 SC_HANDLE scManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); if (!scManager) { printf("OpenSCManager 失败,错误码:%lu\n", GetLastError()); return FALSE; } // 打开目标服务,需要查询和修改配置的权限 SC_HANDLE service = OpenService(scManager, serviceName, SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG); if (!service) { printf("OpenService 失败,错误码:%lu\n", GetLastError()); CloseServiceHandle(scManager); return FALSE; } // 获取服务现有安全描述符(先获取所需缓冲区大小) PSECURITY_DESCRIPTOR pSecurityDesc = NULL; DWORD dwBufferSize = 0; if (!QueryServiceObjectSecurity(service, DACL_SECURITY_INFORMATION, NULL, 0, &dwBufferSize)) { if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { printf("QueryServiceObjectSecurity 预查询失败,错误码:%lu\n", GetLastError()); CloseServiceHandle(service); CloseServiceHandle(scManager); return FALSE; } // 分配足够内存 pSecurityDesc = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, dwBufferSize); if (!pSecurityDesc) { printf("LocalAlloc 内存分配失败,错误码:%lu\n", GetLastError()); CloseServiceHandle(service); CloseServiceHandle(scManager); return FALSE; } // 再次调用获取安全描述符 if (!QueryServiceObjectSecurity(service, DACL_SECURITY_INFORMATION, pSecurityDesc, dwBufferSize, &dwBufferSize)) { printf("QueryServiceObjectSecurity 失败,错误码:%lu\n", GetLastError()); LocalFree(pSecurityDesc); CloseServiceHandle(service); CloseServiceHandle(scManager); return FALSE; } } // 从安全描述符中提取现有DACL PACL pOldDacl = NULL; BOOL isDaclPresent = FALSE; BOOL isDaclDefaulted = FALSE; if (!GetSecurityDescriptorDacl(pSecurityDesc, &isDaclPresent, &pOldDacl, &isDaclDefaulted)) { printf("GetSecurityDescriptorDacl 失败,错误码:%lu\n", GetLastError()); LocalFree(pSecurityDesc); CloseServiceHandle(service); CloseServiceHandle(scManager); return FALSE; } // 构建目标用户的权限条目 EXPLICIT_ACCESS explicitAccess = {0}; // 设置所需权限:启停服务+查询状态,可根据需求添加其他权限(如SERVICE_PAUSE_CONTINUE) explicitAccess.grfAccessPermissions = SERVICE_START | SERVICE_STOP | SERVICE_QUERY_STATUS; // 访问模式:ADD_ACCESS是追加规则,SET_ACCESS会覆盖该用户现有规则 explicitAccess.grfAccessMode = ADD_ACCESS; // 服务无继承对象,设置为无继承 explicitAccess.grfInheritance = NO_INHERITANCE; // 指定用户类型和名称,格式为"域\用户名"或".\本地用户名" explicitAccess.Trustee.TrusteeForm = TRUSTEE_NAME; explicitAccess.Trustee.ptstrName = (LPWSTR)userName; explicitAccess.Trustee.TrusteeType = TRUSTEE_IS_USER; // 合并现有DACL和新权限条目,生成新的ACL PACL pNewDacl = NULL; DWORD dwError = SetEntriesInAcl(1, &explicitAccess, pOldDacl, &pNewDacl); if (dwError != ERROR_SUCCESS) { printf("SetEntriesInAcl 合并ACL失败,错误码:%lu\n", dwError); LocalFree(pSecurityDesc); CloseServiceHandle(service); CloseServiceHandle(scManager); return FALSE; } // 更新安全描述符的DACL if (!SetSecurityDescriptorDacl(pSecurityDesc, TRUE, pNewDacl, FALSE)) { printf("SetSecurityDescriptorDacl 更新DACL失败,错误码:%lu\n", GetLastError()); LocalFree(pNewDacl); LocalFree(pSecurityDesc); CloseServiceHandle(service); CloseServiceHandle(scManager); return FALSE; } // 将更新后的安全描述符应用到服务 if (!SetServiceObjectSecurity(service, DACL_SECURITY_INFORMATION, pSecurityDesc)) { printf("SetServiceObjectSecurity 设置服务权限失败,错误码:%lu\n", GetLastError()); LocalFree(pNewDacl); LocalFree(pSecurityDesc); CloseServiceHandle(service); CloseServiceHandle(scManager); return FALSE; } // 清理资源 LocalFree(pNewDacl); LocalFree(pSecurityDesc); CloseServiceHandle(service); CloseServiceHandle(scManager); printf("权限配置成功!\n"); return TRUE; } int main() { // 替换为你的服务名称和目标用户(格式注意:域用户用"DOMAIN\Username",本地用户用".\Username") const wchar_t* targetService = L"YourServiceName"; const wchar_t* targetUser = L".\\LocalTestUser"; GrantServiceStartStopAccess(targetService, targetUser); return 0; }
关键细节说明
- 权限掩码:
SERVICE_START、SERVICE_STOP是Windows定义的服务权限常量,若需要更多权限(如暂停/继续),可追加SERVICE_PAUSE_CONTINUE等。 - 访问模式选择:
ADD_ACCESS会保留该用户现有权限并追加新权限;SET_ACCESS会覆盖该用户的所有现有规则,只保留当前设置的权限,根据需求选择。 - 用户名称格式:必须是完整的用户标识符,本地用户用
.\\Username,域用户用DomainName\\Username,否则SetEntriesInAcl会无法识别用户。 - 错误处理:每个Windows API调用都要检查返回值,尤其注意内存分配和安全相关函数的返回码,避免内存泄漏或配置失败。
内容的提问来源于stack exchange,提问作者crunsher
相关产品推荐
相关产品推荐

