需先启用“审核对象访问”策略并为目标文件夹配置sacl,再以管理员权限通过evtsubscribe订阅security日志中event id 4670事件,解析其sddl字段比对acl差异来识别具体权限变更。

用 Windows API 监控文件夹权限变更需要开启审计策略
Windows 不会默认记录文件或文件夹的权限修改事件(比如 SetAcl、SetSecurityInfo 调用),必须提前配置本地或域策略,否则你在事件日志里根本看不到相关条目。
关键操作是启用「对象访问」审计,并为具体文件夹设置 SACL(系统访问控制列表)——不是 DACL。很多人卡在这一步,以为调用 ReadEventLog 就能直接捞到日志,结果返回空。
- 打开「组策略编辑器」(
gpedit.msc) → 计算机配置 → Windows 设置 → 安全设置 → 本地策略 → 审核策略 → 启用「审核对象访问」(成功/失败都勾) - 右键目标文件夹 → 属性 → 安全 → 高级 → 审计 → 添加新条目:选主体为「Everyone」,勾选「更改权限」和「取得所有权」这两项(其他如「创建文件/文件夹」按需)
- 注意:SACL 修改后,后续所有对该文件夹的权限变更才会被记录;历史操作不会补录
从 Security 日志读取权限变更事件得过滤 Event ID 4670
权限变更在 Windows 事件日志中固定对应 Event ID 4670(“权限已更改”),它属于 Security 日志,不是 Application 或 System。直接用 OpenEventLog 打开 "Security" 即可,但要注意权限问题。
普通用户进程默认无权读取 Security 日志,必须以管理员身份运行,或在 manifest 中声明 requireAdministrator。否则 OpenEventLog 返回 NULL,GetLastError() 是 ERROR_ACCESS_DENIED。
-
Event ID 4670的SubjectUserName字段标识谁改的权限,ObjectName是被修改的路径,PrivilegeList通常为空,重点看AccessList(含旧/新 DACL 的 SDDL 表示) - 日志字段是 XML 格式,推荐用
EvtQuery+EvtRender(Windows Vista+)替代老旧的ReadEventLog,后者不支持结构化数据提取 - 不要轮询整个日志:用
QUERY_EVENT_LOG_START_AT配合最后读到的RecordNumber增量读取,否则性能极差
用 EvtSubscribe 实现实时响应比轮询更可靠
轮询 Security 日志有延迟且易漏事件(尤其高并发时),Windows 提供基于回调的订阅机制 EvtSubscribe,能真正实时捕获新到达的 4670 事件。
但必须传入正确的查询字符串,且处理线程需保持活跃——回调函数在你指定的线程上下文中执行,如果主线程退出,订阅自动失效。
- 查询字符串示例:
*[System[(EventID=4670) and TimeCreated[timediff(@SystemTime) (5 分钟内) - 回调函数里用
EvtRender解析EventRecordId、TimeCreated、ObjectName等字段,避免手动解析 XML - 注意内存泄漏:每次
EvtRender返回的缓冲区需用LocalFree释放;EvtClose必须成对调用 - 若程序长期运行,建议每小时重建一次订阅(
EvtClose后重EvtSubscribe),避免句柄泄漏或事件积压
权限变更日志不包含完整 ACL 差分,只能靠 SDDL 对比
Event ID 4670 日志只记录变更前后的完整 SDDL 字符串(在 AccessList 字段),不提供「哪些 ACE 被增删」这种差分信息。想定位具体哪条权限被改,必须自己解析两段 SDDL 并逐条比对。
别指望 ConvertStringSecurityDescriptorToSecurityDescriptor 后直接 memcmp——SDDL 顺序不保证一致,且可能含冗余空格或注释。稳妥做法是用 GetSecurityDescriptorDacl 提取 DACL,再遍历 ACE 结构体比较 Trustee、AccessMask、AceType。
- SDDL 解析失败常见于日志字段被截断(长路径或复杂权限),此时
EvtRender返回ERROR_INSUFFICIENT_BUFFER,需重试并增大缓冲区 - 某些第三方工具(如 PowerShell 的
Get-Acl)输出的 SDDL 和事件日志中的格式略有差异,不能直接字符串比对 - 如果文件夹启用了继承,日志里的 SDDL 会包含
I标志,但不会告诉你继承源是谁——这得额外查父目录的 DACL
真正难的不是监听,是把一段 SDDL 变成人类可读的「用户 A 被移除了写入权限」。这部分逻辑没有现成 API,得自己写解析和比对逻辑,而且要覆盖所有 ACE 类型(ALLOW、DENY、OBJECT_ALLOW 等)。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











