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