首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
编程技术
发新帖
0
0
[原创]SCM 服务与驱动启动类型的枚举
发表于: 2026-7-17 21:31
989
[原创]SCM 服务与驱动启动类型的枚举
WangWei_CM.
2026-7-17 21:31
989
# SCM 服务与驱动启动类型的枚举  所有功能均已合并进KswordARK:<mark class="encrypted">b11K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6w2f1%4N6G2M7X3c8p5c8g2k6Q4x3V1k6w2f1%4N6G2M7X3c8Q4x3V1j5`.</mark> 服务控制管理器保存 Win32 服务、内核驱动和文件系统驱动的管理信息。读取时要分别记录服务类型、启动类型、映像路径和服务账户。`Auto` 只是其中一种启动类型,不能覆盖 Boot、System、Demand 和 Disabled 的差异。 ## 一、对象关系和完整流程 服务与驱动共享服务数据库中的管理入口,但启动阶段和运行环境不同。Win32 服务由服务控制管理器统一管理。Boot 与 System 类型的驱动可能在服务控制管理器全面接管 Win32 服务之前加载。枚举程序仍可通过服务控制管理器 API 获取两类对象的管理记录。 ```text 安装程序或系统组件写入配置 -> 服务数据库保存记录 -> Windows 根据服务类型和启动类型选择处理阶段 -> 加载 Win32 服务或驱动 -> 运行状态随系统活动变化 ``` 枚举程序先读取对象列表,再逐项查询静态配置。列表中的当前状态与逐项查询到的配置来自不同调用,需要按服务键名组合。 ```text 打开服务控制管理器 -> 枚举 Win32 服务和驱动 -> 保存当前状态 -> 逐项打开服务记录 -> 查询服务类型、启动类型、映像路径和账户 -> 合并结果与错误状态 ``` ### 1. Windows 服务是由 SCM 管理的后台工作单元 服务控制管理器(Service Control Manager,SCM)是 Windows 的服务管理组件。它维护活动服务数据库,并响应启动、停止、查询和配置请求。图形界面中的“服务”列表、`sc.exe` 命令和服务相关 Win32 API 都通过这一管理机制访问服务信息。 服务通常不依附于某个用户桌面。它可以在系统启动后运行,也可以在满足条件时被请求启动,还可以保持停止状态。服务记录包含服务键名和显示名。键名用于 API 定位,例如 `WinDefend`。显示名用于界面展示,例如“Microsoft Defender 防病毒服务”。两者用途不同,枚举结果要同时保存。 一个服务的完整配置至少包含以下独立字段。它们回答的问题不同,不能把其中一个字段的值复制到另一个字段的含义上。 | 字段 | 回答的问题 | 常见来源 | | --- | --- | --- | | 服务类型 | 这是 Win32 服务、内核驱动还是文件系统驱动? | `dwServiceType` | | 启动类型 | 系统在什么阶段或什么条件下尝试启动它? | `dwStartType` | | 当前状态 | 此时它正在运行、暂停、等待还是停止? | `SERVICE_STATUS_PROCESS` | | 映像路径 | 服务主程序或驱动文件使用了什么原始命令行? | `lpBinaryPathName` | | 服务账户 | Win32 服务在什么账户上下文中运行? | `lpServiceStartName` | ### 2. Win32 服务和内核驱动共享配置入口,执行环境不同 服务类型决定对象的运行环境。`SERVICE_WIN32_OWN_PROCESS` 表示服务拥有独立进程,`SERVICE_WIN32_SHARE_PROCESS` 表示服务由共享进程承载。这两类服务运行在用户态,由 SCM 与服务控制处理函数协作管理。 `SERVICE_KERNEL_DRIVER` 和 `SERVICE_FILE_SYSTEM_DRIVER` 表示驱动服务。驱动由内核加载到内核地址空间。文件系统驱动还会参与文件系统栈。它们也通过服务数据库保存名称、映像路径和启动设置,因此可以使用相同的管理 API 查询。它们的启动阶段、错误后果和运行上下文与 Win32 服务不同。 启动类型必须结合对象种类理解。`SERVICE_BOOT_START` 与 `SERVICE_SYSTEM_START` 主要用于需要在早期引导阶段加载的驱动,`SERVICE_AUTO_START` 描述服务控制管理器的自动启动配置,`SERVICE_DEMAND_START` 表示按需启动,`SERVICE_DISABLED` 禁止启动请求成功执行。当前状态仍需从枚举的状态结构单独读取。 ### 3. 服务数据库中的配置与注册表中的持久化记录有关联 管理 API 与持久化位置承担不同职责。大多数服务配置持久化在 `HKLM\SYSTEM\CurrentControlSet\Services` 下的服务键中。应用读取服务配置时应优先使用 SCM API。SCM API 按服务管理语义组织字段,也能避免把注册表文本误解为完整运行状态。 注册表中的 `Start`、`Type`、`ImagePath` 等值是持久化配置字段的一部分。SCM 查询返回的 `QUERY_SERVICE_CONFIGW` 是面向服务管理语义的结构,其中已经把服务类型、启动类型、二进制路径和账户等信息组织到固定字段。运行状态、进程标识和接受的控制码则来自 `EnumServicesStatusExW` 的状态数据,不应从 `Start` 值推断。 ### 4. 枚举反映某个时间点附近的状态 服务集合可能在枚举期间变化。系统组件、安装程序和管理员都可能创建、删除、启动或停止服务。`EnumServicesStatusExW` 分批返回记录时,下一批列表可能已经变化。逐项调用 `OpenServiceW` 失败,也可能表示对象刚刚被删除。 枚举结果要保存枚举时间、服务键名、服务显示名、当前状态和每次 API 失败的 Win32 错误码。这些字段可以区分“当前没有返回该对象”“对象存在但无法打开”“对象在读取中变化”三类情况。 ### 5. 服务配置、服务依赖和服务安全描述符是不同字段组 启动类型只是服务配置的一部分。服务还可以声明依赖服务或依赖组,SCM 会在启动时按依赖关系协调顺序。服务失败操作描述异常退出后的恢复行为。服务安全描述符说明谁有权查询、启动、停止、修改或删除服务。这些信息分别属于依赖关系、恢复策略和访问控制,不能从 `dwStartType` 推导。 服务安全描述符中的 DACL(Discretionary Access Control List,自主访问控制列表)由多条访问控制项组成。SCM 会比较调用进程令牌和 DACL,决定是否授予 `OpenServiceW` 请求的权限。已经枚举到服务键名,但 `SERVICE_QUERY_CONFIG` 被拒绝时,要保存“可枚举、不可查询配置”的状态。查询失败不能用来猜测启动方式或服务账户。 ### 6. 服务句柄、普通句柄和驱动对象引用的释放方式不同 句柄要按来源调用对应的释放函数。`OpenSCManagerW` 与 `OpenServiceW` 返回 `SC_HANDLE`,调用方使用 `CloseServiceHandle`。`CreateFileW` 返回普通 `HANDLE`,调用方使用 `CloseHandle`。内核驱动中的对象引用遵循内核对象引用计数规则。这些资源的释放方式不能互换。 查询服务配置的缓冲区也有自己的生命周期。`QueryServiceConfigW` 把 `QUERY_SERVICE_CONFIGW` 以及其中的字符串指针写入调用方分配的连续字节缓冲区。指针只在这块缓冲区仍存在时有效。需要长期保存的文本必须复制到独立存储。保存缓冲区内部地址后再释放缓冲区,会留下无效指针。 ### 7. 一条服务记录应同时保存静态配置和瞬时状态 每条服务记录都要区分静态配置和瞬时状态。静态配置包括服务键名、显示名、服务类型、启动类型、二进制路径、服务账户、依赖和失败操作。瞬时状态包括当前状态、进程标识、接受的控制码和枚举时间。配置与状态都可能变化,两个字段组不能互相覆盖。 当某项 API 返回失败时,要记录服务键名、请求访问掩码、失败函数和错误码。这些字段可以区分“服务没有返回”“当前令牌不能读配置”“服务刚被删除”和“服务处于停止状态”,避免将失败码误标为服务行为结论。 服务数据库中的条目可能因安装、更新、策略或管理员操作而增加或删除。枚举结果是一组带时间戳的观察。比较两次结果时,要按服务键名与服务类型配对。权限变化、系统重启和读取范围变化也要作为差异原因记录。 完整的身份与状态字段让每一行都能回到明确的服务对象、读取时刻和 API 返回状态。 服务键名是 SCM API 使用的稳定定位文本,显示名仅适合人类界面展示。比较或导出时优先保留键名与类型组合,再将显示名作为附加字段,避免本地化显示名或管理员重命名造成错误配对。 ## 二、先打开服务控制管理器 ### 1. SCM 句柄与服务句柄是专用资源 打开服务控制管理器的宽字符 API 返回 `SC_HANDLE`。空指针表示调用失败。枚举只需要 `SC_MANAGER_ENUMERATE_SERVICE`,不需要创建、启动、停止或删除服务的权限。 ```cpp // 意义:打开本机或远程计算机的服务控制管理器数据库。 // 返回:非空 SC_HANDLE 表示成功。nullptr 表示失败。 SC_HANDLE OpenSCManagerW( LPCWSTR lpMachineName, // 输入:nullptr 表示本机。 LPCWSTR lpDatabaseName, // 输入:nullptr 表示活动服务数据库。 DWORD dwDesiredAccess // 输入:SC_MANAGER_ENUMERATE_SERVICE 等所需权限。 ); // 意义:关闭 OpenSCManagerW 或 OpenServiceW 返回的专用句柄。 // 返回:非零表示成功。不能用 CloseHandle 关闭 SC_HANDLE。 BOOL CloseServiceHandle(SC_HANDLE hSCObject); // 意义:关闭 CreateFileW、CreateProcessW 等返回的普通 HANDLE。 // 返回:非零表示成功。SC_HANDLE 不属于普通 HANDLE,不能传入此函数。 BOOL CloseHandle(HANDLE hObject); ``` 正确做法:请求最小权限,并在读取结束时调用 `CloseServiceHandle`。 ```cpp SC_HANDLE scm = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ENUMERATE_SERVICE); if (scm != nullptr) { // 枚举服务和驱动。 CloseServiceHandle(scm); } ``` 错误示例:把 `SC_HANDLE` 交给 `CloseHandle`。服务控制管理器句柄有专用释放 API,混用关闭函数会留下资源或产生错误状态。 ```cpp // 错误:SC_HANDLE 必须调用 CloseServiceHandle。 CloseHandle(scm); ``` ## 三、用可变长度缓冲区枚举服务与驱动 ### 2. 枚举 API 可能分多批返回记录 扩展枚举 API 返回 `ENUM_SERVICE_STATUS_PROCESSW` 数组。缓冲区不足时,它可能返回部分记录并设置 `ERROR_MORE_DATA`。`lpResumeHandle` 保存下一批继续读取的位置。 ```cpp // 意义:读取 SCM 中满足服务类型和状态筛选条件的服务记录。 // 返回:非零表示本批成功结束。返回 0 且错误码为 ERROR_MORE_DATA 表示还有下一批。 BOOL EnumServicesStatusExW( SC_HANDLE hSCManager, DWORD InfoLevel, DWORD dwServiceType, DWORD dwServiceState, LPBYTE lpServices, DWORD cbBufSize, LPDWORD pcbBytesNeeded, // 输出:下一批所需字节数。 LPDWORD lpServicesReturned, // 输出:当前缓冲区中的记录数量。 LPDWORD lpResumeHandle, // 输入/输出:下一批继续位置。 LPCWSTR pszGroupName ); // 意义:取得当前线程最近一次失败的 Win32 错误码。 // 返回:当前线程错误码。只应紧跟在文档说明会设置该状态的失败调用后读取。 DWORD GetLastError(void); ``` 正确做法:先用零容量调用探测当前记录集大小,再按返回字节数重新调用。`SERVICE_WIN32 | SERVICE_DRIVER` 用于覆盖服务和驱动。`SERVICE_STATE_ALL` 用于保留当前状态差异。两次调用之间服务集合发生变化时,要重新探测并有限重试。 ```cpp DWORD needed = 0, returned = 0; EnumServicesStatusExW( scm, SC_ENUM_PROCESS_INFO, SERVICE_WIN32 | SERVICE_DRIVER, SERVICE_STATE_ALL, nullptr, 0, &needed, &returned, nullptr, nullptr); if (GetLastError() == ERROR_MORE_DATA && needed != 0) { std::vector<BYTE> buffer(needed); returned = 0; const BOOL ok = EnumServicesStatusExW( scm, SC_ENUM_PROCESS_INFO, SERVICE_WIN32 | SERVICE_DRIVER, SERVICE_STATE_ALL, buffer.data(), static_cast<DWORD>(buffer.size()), &needed, &returned, nullptr, nullptr); if (ok != FALSE) { auto* entries = reinterpret_cast<ENUM_SERVICE_STATUS_PROCESSW*>(buffer.data()); for (DWORD index = 0; index < returned; ++index) { // 为 entries[index] 打开 SERVICE_QUERY_CONFIG,再读取配置。 } } } ``` 错误示例:使用固定的小缓冲区,然后把失败写成“没有服务”。`ERROR_MORE_DATA` 表示当前记录集超过缓冲区。此时要重新探测并扩大缓冲区,不能把它解释为正常结束。 ## 四、逐项读取启动类型和映像配置 ### 3. 服务配置需要两次查询 打开单个服务的宽字符 API 返回服务句柄。空指针可能表示服务已删除、权限不足或名称无效。只读配置使用 `SERVICE_QUERY_CONFIG`。 ```cpp // 意义:在已打开 SCM 中打开一个具体服务或驱动。 // 返回:非空 SC_HANDLE 表示成功。nullptr 表示失败。 SC_HANDLE OpenServiceW( SC_HANDLE hSCManager, // 输入:OpenSCManagerW 成功返回的句柄。 LPCWSTR lpServiceName, // 输入:服务键名,不是显示名。 DWORD dwDesiredAccess // 输入:SERVICE_QUERY_CONFIG。 ); // 意义:读取服务类型、启动类型、映像路径和服务账户等配置。 // 返回:非零表示成功。首次零容量调用通常返回 0,并给出所需字节数。 BOOL QueryServiceConfigW( SC_HANDLE hService, LPQUERY_SERVICE_CONFIGW lpServiceConfig, DWORD cbBufSize, LPDWORD pcbBytesNeeded ); ``` 正确做法:先用零容量调用取得 `pcbBytesNeeded`。确认错误码为 `ERROR_INSUFFICIENT_BUFFER` 后,再按字节分配缓冲区。`QUERY_SERVICE_CONFIGW` 内部的字符串指针指向这块缓冲区。释放缓冲区前,要复制或输出需要的文本。 ```cpp DWORD requiredBytes = 0; QueryServiceConfigW(service, nullptr, 0, &requiredBytes); if (GetLastError() == ERROR_INSUFFICIENT_BUFFER && requiredBytes != 0) { std::vector<BYTE> storage(requiredBytes); auto* config = reinterpret_cast<QUERY_SERVICE_CONFIGW*>(storage.data()); if (QueryServiceConfigW(service, config, requiredBytes, &requiredBytes) != FALSE) { // config->dwServiceType、dwStartType、lpBinaryPathName、lpServiceStartName 有效。 } } ``` 错误示例:保存缓冲区内部指针,然后释放缓冲区。`lpBinaryPathName` 与 `lpServiceStartName` 不拥有独立内存。离开 `storage` 作用域后再使用它们,会访问无效地址。 ## 五、正确解释服务与驱动启动类型 ### 4. 类型和启动方式需要分别记录 服务类型要保留原始分类。`SERVICE_WIN32_OWN_PROCESS`、`SERVICE_WIN32_SHARE_PROCESS`、`SERVICE_KERNEL_DRIVER` 和 `SERVICE_FILE_SYSTEM_DRIVER` 表示不同运行环境。内核驱动的 Boot 或 System 启动语义与 Win32 服务的 Auto 不同,不能统一标成“自动启动”。 启动类型要保留原始 `dwStartType`。`SERVICE_BOOT_START`、`SERVICE_SYSTEM_START`、`SERVICE_AUTO_START`、`SERVICE_DEMAND_START` 和 `SERVICE_DISABLED` 分别表示不同的加载或启动配置。当前运行状态来自另一组状态字段,不能由启动类型替代。 映像路径要保存完整原始文本。服务配置中的路径可能包含引号、参数或环境变量。服务账户也属于独立字段。按第一个空格截取路径,会误解带引号的映像名。 逐项查询可能因权限不足而失败。SCM 可以枚举到某个服务,同时拒绝 `SERVICE_QUERY_CONFIG`。程序要记录服务键名和错误码,再继续处理其它记录。只读查询不应改变服务状态。 完整可运行程序在附件。  <mark class="encrypted">039K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4j5h3&6Y4N6$3g2A6j5$3#2Q4x3X3g2D9j5h3&6*7L8%4g2#2i4K6u0W2j5$3!0E0i4K6u0r3K9f1A6H3M7q4j5K6N6%4A6J5y4r3k6U0</mark>
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
收藏
・
0
点赞
・
0
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
0
)
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
WangWei_CM.
30
发帖
1
回帖
40
RANK
关注
私信
他的文章
[原创] 进程模块枚举、映像身份与线程启动地址关联
215
[原创]Windows 进程 CPU、内存、I/O 与调度指标采样
981
[原创]跨位数进程的映像、命令行与环境块读取
535
[原创]进程实例身份、父子关系与会话归属
293
[原创]Windows 进程枚举NtQuerySystemInformation(SystemProcessInformation
635
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部