ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Windows下让普通用户操作内核驱动:SDDL权限修改全解析

Windows下让普通用户操作内核驱动:SDDL权限修改全解析 老板这个问题我太有发言权了。在自动化测试、内网运维或者搞驱动调试的时候Windows 默认的权限模型经常能把人逼疯——一个普通域用户想要启动某个内核驱动服务或者让程序打开驱动句柄系统直接就甩一句“拒绝访问”。你总不能每次都把管理员密码发给所有人更不能为了这点事给全员开 UAC 后门。这篇文章我会以“让普通用户也能操作内核驱动服务”为主线从权限模型的底层原理一路拆到 SDDL 权限字符串怎么改再附上完整的实操过程和排坑记录。无论你是做驱动开发的工程师还是在企业环境里搞终端管控的运维这套方法都能直接抄作业。1. 问题拆解为什么内核驱动默认只向管理员开放1.1 Windows 服务与驱动的从属关系很多刚接触内核开发的朋友会把“驱动”想象成一个孤立的.sys文件其实在 Windows 的进程模型里驱动是依赖**服务Service**体系来加载和管理的。设备驱动程序本质上是SCManager服务控制管理器里注册的一个内核服务它的启动类型可以是 boot、system、auto、demand 或者 disabled。当我们说“打开内核驱动”时实际发生的动作是通过CreateService在 SCM 数据库里注册一个驱动服务通过StartService把这个内核服务启动起来让ntoskrnl.exe加载对应的.sys镜像用户态程序通过CreateFile打开驱动的设备对象Device Object下发DeviceIoControl请求。问题就出在第二步和第三步。SCM 对StartService和ControlService这类操作的默认安全描述符只向以下主体授予了SERVICE_START和SERVICE_STOP权限本机 Administrators 组的成员SYSTEM 账户服务自身的 LocalSystem 账户。也就是说普通用户即使加了 Users 组默认只能查看服务状态碰不了启动和停止按钮。这就是标题里“打不开”的根源。1.2 权限模型的底层逻辑Windows 的权限管控并不像某些人想的“看一眼你是不是管理员”而是基于**访问令牌Access Token和安全描述符Security Descriptor**的匹配。服务对象在注册时会被分配一个安全描述符里面包含一个 DACLDiscretionary Access Control List自主访问控制列表DACL 中的每一项 ACEAccess Control Entry指定了某个 SID 可以执行哪些操作。内核驱动服务之所以默认拒普通用户于门外是因为它继承自服务进程的默认安全模板。微软的默认策略是内核对象属于高危资源任何非管理员对它的访问都必须显式授权。这个设计的初衷当然是为了防恶意软件和误操作但在企业内网或实验室环境下它确实带来了一些运维上的麻烦。注意我这里说的“打开驱动”是指合法场景下由开发或运维人员主动授权普通用户加载特制的、经过签名的内核驱动。如果你是想用驱动做 Rootkit、过反作弊、非法外挂请立刻关掉这篇文章方向不对。2. 几个常见但不可取的方案2.1 拿 UAC 或提权工具硬顶有一种流传很广的野路子把普通用户的账户加入Administrators组然后配合 UAC 的“自动审批”策略让程序在启动时静默提权。或者更粗暴一点直接禁用 UAC。听起来好像问题就没了实际上后患无穷。举一个我踩过的真实案例某测试环境为了省事给所有测试机统一加装了提权工具把 UAC 调成“不提示直接提升”。结果一个测试脚本误触发了Stop-Service把核心数据采集驱动给停了紧接着依赖这个驱动的应用大面积报错。更麻烦的是由于 UAC 被禁用普通应用和提权应用的令牌完全一致原本能拦截恶意行为的一些机制全部失效攻击面大幅度扩大。把管理员权限发给普通用户等于把解决问题的钥匙配了无数把丢在公共区域。这绝对不是一个“可维护”的方案。就算不谈安全单说排障一旦某个服务挂了你根本分不清是驱动本身的 bug 还是用户操作导致的审计日志也是乱的。2.2 用计划任务创建高权限任务再讲一个看起来很聪明、实际很脆的方案通过schtasks创建一个以 SYSTEM 身份运行的计划任务普通用户在需要时触发这个任务由它代劳启动驱动服务。这个方案的优点是完全不需要改服务权限普通用户也不需要管理员令牌。但缺点是驱动的启停需要实时反馈而计划任务的返回机制很笨重用户很难拿到准确的启动结果如果驱动在启动时报错计划任务里的日志信息非常有限排错难度高多台机器、多个驱动服务分别建立计划任务管理起来就是一场灾难配置漂移问题会让你痛不欲生。我并不是说计划任务方案完全不能用它适合“定期执行、不需要即时交互”的场景。但对于“普通用户随时打开驱动”这个需求它并不对症。3. 正解路线通过 SDDL 修改服务权限3.1 SDDL 基础语法微软最正式的解决方案是直接修改目标服务的安全描述符通过sc.exe sdset命令把自定义 DACL 应用到服务对象上。这套描述符使用SDDLSecurity Descriptor Definition Language安全描述符定义语言来表达你可以把它理解成一段浓缩的权限配置文本。一个典型的 SDDL 字符串长这样D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)如果不熟悉看起来确实很像乱码。我们来拆一下结构D:表示后面是 DACL 部分每个( ... )是一组 ACE每组 ACE 内部分为四段用分号隔开第一段是 ACE 类型A表示允许D表示拒绝第二段是权限掩码如CC表示 SERVICE_QUERY_CONFIGLC表示 SERVICE_QUERY_STATUSSW表示 SERVICE_STARTRP表示 SERVICE_STOP第三段是对象类型留空表示通用第四段是 SID如SY是 SystemBA是内置管理员AU是 Authenticated Users。拿(A;;CCLCSWRPWPDTLOCRRC;;;AU)来说它的意思是允许 Authenticated Users 查询配置、查询状态、枚举依赖关系、读取权限等操作但没有授予SW启动和RP停止权限。这正好对应普通用户访问驱动服务的默认状态——看得见摸不着。3.2 使用 sc.exe sdset 实操要赋予普通用户启动驱动服务的权限我们需要在服务原有的 DACL 基础上追加一条允许某个用户或用户组执行SERVICE_STARTSW和SERVICE_STOPRP的 ACE。手工拼 SDDL 很容易出错所以我更推荐先读取现有 SDDL再基于它做修改。首先以管理员身份打开 CMD执行sc.exe sdshow 你的驱动服务名这里假设我的驱动服务叫myfilter命令输出大概是D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)接下来构造新的 SDDL。我的做法是先在 PowerShell 里组织好权限追加逻辑生成新的 ACE 字符串再调用sc.exe sdset应用。对普通用户组UsersSID 为BU授予服务启动SW和服务停止RP权限同时保留原有权限最终的新字符串是D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;SWRP;;;BU)你可以看到我只是在末尾追加了(A;;SWRP;;;BU)含义是允许内置 Users 组启动和停止该服务。最稳的应用方式是把原来的字符串和新增字符串拼在一起复制后用sc.exe sdset myfilter 一段新SDDL执行。sc.exe sdset myfilter D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;SWRP;;;BU)命令执行完会返回[SC] SetServiceObjectSecurity 成功。提示sw和rp是权限掩码的简写形式。如果需求里还要允许普通用户打开设备句柄并与驱动通信那就得额外留意 CreateFile 的 ACL这跟服务 DACL 是两个层面千万别搞混。4. 完整实施流程4.1 准备驱动服务和测试账户为了把整个过程说透我们用一个完整例子来走一遍流程。假设我手里有一个合法的内核驱动demo.sys需要让一个普通域账户devuser可以在不输入管理员密码的情况下启动它并和它通信。事前需要准备的工作清单如下驱动已经通过测试签名或正式签名可以正常在目标系统加载驱动服务已存在或者我们准备第一步就创建它一个普通用户账户已加入 Users 组或 Domain Users 组管理员权限的命令行环境用于修改服务配置。如果驱动服务还不存在需要先以管理员身份创建。这里的创建动作必须提权执行因为普通用户没有创建内核服务对象的权限。创建命令大概是这样sc.exe create demo type kernel start demand binPath C:\drivers\demo.sys DisplayName Demo Kernel Driver创建好之后先用sc.exe qc demo和sc.exe start demo验证管理员可以正常启动。这一步是整个流程的“基线测试”如果管理员都启动不了后面改权限没有意义。4.2 修改服务 DACL 并验证效果接下来按上一节的方式先导出原始 SDDLsc.exe sdshow demo以普通用户身份注意不是管理员执行以下命令确认当前状态sc.exe start demo预期结果[SC] StartService 失败 5: 拒绝访问。或者在 PowerShell 里用Start-Service demo报错Cannot start service demo because it is disabled or it has no enabled devices associated with it这个报错比较少见更多直接是 access denied。现在切回管理员应用新 DACLsc.exe sdset demo D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;SWRP;;;BU)然后切换到普通用户会话再执行sc.exe start demo正常情况下你会看到SERVICE_START成功服务状态变为RUNNING。接着再执行sc.exe stop demo同样应该成功。至此普通用户对驱动服务的启停权限已经生效了。4.3 打通普通用户到设备对象的访问权限服务能启动只是第一步。如果用户态程序还需要CreateFile打开驱动的设备对象比如\\.\DemoDevice并下发控制码那还要处理设备对象本身的 DACL。这一步常常被忽略因为服务启动成功了但程序打开句柄仍然报“Access Denied”。设备对象的 DACL 通常由驱动在IoCreateDevice时指定。如果驱动没有自定义安全描述符默认只允许系统和管理员访问。你可以通过两种方式处理在驱动代码里创建设备时显式设置SDDL_DEVOBJ_SYS_ALL_ADM_ALL或者写一段自定义 SDDL把BUUsers 组加进去。这需要重新编译驱动适合驱动源码掌握在自己手里的情况。如果不方便改驱动代码可以用DeviceIoControl配合IOCTL_DISK_GET_DRIVE_LAYOUT_EX这类已有接口不行这只是临时擦边。正确做法应该是在驱动里通过IoCreateDeviceSecure配置安全描述符。最干净的方案是在驱动开发阶段就从一个安全的 SDDL 模板出发比如D:P(A;;GA;;;SY)(A;;GA;;;BA)(A;;GRGW;;;BU)这段允许 Users 组读取和写入设备对象但拒绝其他权限。注意最前面的D:P表示“受保护”的 DACL会自动拒绝来自父容器的继承。如果驱动已经发布到生产环境无法改动代码备选方案是写一个小的用户态权限调整工具在服务启动后调用SetKernelObjectSecurity但这通常需要配合管理权限执行一次严格来讲不适合作为长期方案。我的经验是驱动设备 ACL 在开发期就规划好不要等到部署了再补救。5. 常见问题与排查技巧实录5.1 常见报错与含义速查表实际操作中你会遇到各种奇奇怪怪的报错。我把最常见的几种和对应的原因整理成了表格方便你对照排错。现象可能原因排查方向sc start报“拒绝访问”服务 DACL 未包含普通用户的 SW 权限用sdshow确认 DACL 里是否有(A;;SWRP;;;BU)服务启动成功但 CreateFile 返回 5设备对象 DACL 没有授予用户权限检查驱动的设备对象 ACL 定义sc sdset报“参数错误”SDDL 字符串结构不对ACE 里权限掩码拼错逐段对照 ABLE 权限掩码表修正普通用户sc query能看到服务但 Status 显示 STOPPED服务未启动或者用户无权启动先确认有没有执行start再查 DACL改完 SDDL 后管理员也启动不了了SDDL 覆盖时漏掉了 BA 权限应用新 SDDL 前一定要基于旧 SDDL 追加不能另起炉灶驱动签名校验失败服务启动报 577普通用户加载未签名/测试签名驱动被系统拦截启用 TESTSIGNING 或使用正式签名驱动最隐蔽的一个坑是sc sdset修改的只是一份独立的安全描述符它不会影响服务对象打开时的“继承”和“默认标签”。如果你的驱动服务在应用 SDDL 之前已经被其他程序打开了句柄那这次修改不会对已存在的句柄生效。改了权限之后务必把相关进程和服务先停掉再重新启动,否则你反复测试都感觉“没生效”其实只是句柄缓存的问题。5.2 容易被忽略的细节和最佳实践这里有一些我从实战中总结出来的经验常规文档里不太会写。第一条域环境下慎用单用户授权。如果只是给一个开发机上的本地用户授权直接在最前面的用户组上加 ACE 是高效做法但如果是在域环境或者你管理的是一整批机器我更推荐创建一个专用的本地组比如DrvUsers把需要授权的用户加进去再给这个组加 ACE。这样做的好处是以后发权限只需要维护组成员不必在几十台机器上去改 SDDL 字符串。如果要多台批量授权我强烈建议把 SDDL 写入一个标准答案文件配好一条命令执行sc.exe sdset demo D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;SWRP;;;BU)第二条总是先备份旧 SDDL。在sc.exe sdset执行之前先sdshow输出原始内容并保存成文件。不要以为这步是多余的——我见过有人想当然地从网上复制了一段通用 SDDL 来覆盖服务权限结果把 Service Control Manager 对服务的默认访问控制全丢了最后连查询服务都报错只能靠恢复注册表才救回来。保存旧权限不会花你一分钟但恢复的时候能救你半天。第三条对sw、rp、cc这些权限掩码的认知要准确。它们分别代表SERVICE_START、SERVICE_STOP、SERVICE_QUERY_CONFIG。如果需求是普通用户能启动驱动但不能停止出于安全考虑那 ACE 只需要写成(A;;SW;;;BU)而不是(A;;SWRP;;;BU)。对权限的精细控制往往比一刀切全放开更能体现一个从业者的功力。第四条安全审计别省略。给普通用户开放内核驱动权限本质上是在扩大信任边界所以事前的安全检查一定要做确认这个驱动是否有签名、是否有已知漏洞比如存在本地提权 CVE、发布渠道是否受控。内核驱动一旦被恶意利用后果远不是普通进程权限能比的。我在企业环境里做事的原则是能多次签名验证的就做验证能限制设备对象的权限就限制能只给一个用户而不是一组用户授权就只给一个。第五条Windows 安全日志会记录服务启动的完整时间线包括 SID 和进程路径。在问题排查或安全审计时通过wevtutil qe System /q:*[System[(EventID7036)]]可以快速筛选服务启动相关事件。如果发现异常启动行为第一件事就查这个日志不要瞎猜。最后再分享一个小技巧如果你需要让普通用户启动驱动但又不希望开放内核服务的全部启停权限可以用sc.exe sdset精确控制。通过只允许启动SW而不允许停止RP配合组策略可以实现“能开不能关”的效果——这个在一些安全合规场景下还挺有用的。我个人的体会是权限够用就好少一分防意外多一分嫌浪费。照着这个原则走普通用户打开内核驱动这个需求既能顺畅落地又能守住安全底线。
返回列表