You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:16:50