Windows进程资源访问监控:Procmon、ETW、Sysmon与句柄快照
发布时间:2026/9/30 1:29:58 锦皓数字建站

1. 标题里的监听先掰扯清楚观测一个进程到底在碰什么先澄清一个很容易被带偏的点。标题里的监听程序不是指某个进程在 socket 上 listen 端口而是指对目标程序做监听观测——我想知道这个 exe 从双击到退出这段时间里究竟动过哪些文件、写过哪些注册表、连过哪些地址、加载过哪些模块。这是两件完全不同的事前者是网络编程后者是运行时行为分析。这个需求在实际工作里出现得非常多。比如接手一个没有源码的第三方工具想知道它偷偷往哪写数据比如自己写的客户端在某台机器上启动就闪退但日志什么都没打比如做软件兼容性验证要确认安装包有没有污染系统目录比如做安全自查需要一份这个程序访问过什么的清单来交代。还有一类场景是做性能排查——某个服务跑着跑着句柄数一路涨你得先知道它在开什么才能判断是泄漏还是正常业务。问题的难点在于Windows 上资源这个概念远比大多数人想象的宽。文件是资源注册表是资源但互斥体、事件对象、命名管道、共享内存段、设备句柄、ALPC 端口同样是资源甚至线程和进程本身也是被句柄引用的资源。很多人第一次用工具抓了一遍以为自己看全了其实只看到了文件那一层剩下的全漏了。这篇文章就按资源分类、观测点选择、工具实操、原生审计、网络侧、自动化脚本、踩坑排查这条线把我这几年反复用过的方法整理一遍。不管你是刚接触这类工具的新手还是已经用过几轮但总觉得结果对不上的老手应该都能捞到点东西。2. 先把资源拆开Windows 进程的七类可观测对象2.1 为什么必须从内核对象入手Windows 的进程不直接持有资源它持有的是句柄。每个进程在内核里有一张句柄表表里每一行是句柄值 指向内核对象的指针 访问权限掩码。文件、注册表键、事件、互斥体、管道、进程、线程、节对象在内核眼里全是对象全都挂在对象管理器那套统一命名空间下面比如\Device\HarddiskVolume3\...、\REGISTRY\MACHINE\...、\BaseNamedObjects\...、\Sessions\1\BaseNamedObjects\...。理解这一点非常关键因为它决定了你后面所有工具的选择。凡是能在句柄这一层做的观测都是最接近真相的凡是只在用户态 API 上挂钩子的观测都天然存在被绕过、被漏掉的缺口。你看到工具里显示C:\Users\xxx\AppData\Local\Temp\abc.tmp那是工具帮你把\Device\HarddiskVolume3\Users\...翻译过来的结果遇到符号链接和重定向的时候这个翻译过程本身就可能误导你。2.2 七类资源与常见误判我把实际会碰到的资源分成七类每类都列一下最容易被忽略的点。资源类别典型表现容易踩的坑文件与目录CreateFile、ReadFile、WriteFile分不清 Reparse 重定向看到的路径不是真实路径注册表RegOpenKey、RegQueryValue、RegSetValueWOW64 下Wow6432Node镜像容易读错分支网络TCP/UDP 连接、绑定端口环回流量抓不到IPv6 与 IPv4 双栈混淆同步对象互斥体、事件、信号量基本不体现在文件类工具里需要句柄快照命名管道 / 邮件槽\\.\pipe\xxx在 Procmon 里长得像文件事件容易被当成磁盘 IO进程与线程OpenProcess、CreateThread父进程创建子进程后行为断链设备与驱动\\.\PhysicalDrive0、\\.\Nul普通用户权限下根本看不到第三类里的环回流量是个经典坑。很多程序内部用 127.0.0.1 通信你用普通抓包工具在物理网卡上盯着什么都看不到因为流量根本没出协议栈。这时候要么用支持 Npcap 环回适配的方式抓要么直接用内置的pktmon。2.3 观测点的三个层次决定了你能看到多少这是我认为最值得先建立的心智模型。观测点分三层能力各不相同。第一层是用户态 API 挂钩。原理是在目标进程里把kernel32!CreateFileW、advapi32!RegQueryValueExW这类函数的入口改成跳转先记录再转发。优点是信息丰富能拿到调用栈和参数原文缺点是只要程序直接用ntdll!NtCreateFile发系统调用或者自带静态链接的 CRT或者反调试检测挂钩你就抓瞎了。第二层是内核回调与文件系统过滤驱动。文件系统微过滤驱动Minifilter挂在 IO 栈上所有文件操作无论从哪个 API 发起都要经过它覆盖度最高。代价是驱动本身要签名、要加载和某些安全软件的驱动撞车时轻则丢事件重则系统卡死。很多商用 EDR 就在这一层工作你同时装两个出问题不奇怪。第三层是ETWEvent Tracing for Windows。内核和系统组件主动把事件投递给订阅者开销低、稳定性好、不需要注入任何东西适合长时间开着。缺点是可观测的内容取决于 provider 提供了什么粒度不如前两层细比如想看这次 ReadFile 读了多少字节到哪个缓冲区ETW 一般给不了。一个务实的组合是短时间深挖用 Procmon第二层长时间在线监控用 ETW 加 Sysmon第三层做合规留痕用系统原生审计第三层查内存里的残留用句柄快照第一层偏静态。单一工具包打天下的想法试过就知道不成立。3. Process Monitor 抓一次完整的资源访问3.1 启动前的准备与过滤器的正确写法Procmon 是目前单次分析场景下最顺手的工具。第一次打开的时候它会立刻开始刷屏几秒钟就能跑到几十万条事件所以先设过滤器再让目标程序启动这个顺序不能反。启动方式必须是右键以管理员身份运行。普通权限下很多事件直接看不到尤其是 SYSTEM 账户的操作。启动后会弹出过滤器设置窗口我的习惯是先点 Reset 清空然后按下面的顺序加条件Process Nameistarget.exeOperationisCreateFile注意这里是精确匹配不是模糊匹配写CreateFileW是匹配不到的因为 Procmon 用的是内核层的操作名ResultisSUCCESS这里有个小技巧如果程序会拉起子进程比如安装程序、启动器只用 Process Name 过滤会漏掉子进程的行为。稳妥做法是在 Tools 菜单里打开Enable Advanced Output这样 Result 旁边会多出 PID 列然后改用PIDis1234过滤或者干脆先不加进程过滤抓完再按进程名筛选。菜单里的几个关键选项值得单独说Filter Drop Filtered Events默认开启意思是被过滤掉的事件不进内存。抓长时间的场景时建议保留开启否则内存会被垃圾事件吃满但如果你的过滤条件写窄了想事后放宽就必须关掉它因为被丢弃的事件找不回来了。Options History Depth控制内存里保留多少事件写 0 表示全留在内存不落盘。分析启动过程时我会设成 0 并配合 Backing File 落盘。Options Enable Boot Logging这个能捕获开机到登录阶段的驱动加载和文件访问。排查开机就蓝屏或服务启动失败时特别有用重启后在 Procmon 里就能看到整个启动过程的事件流。注意Filter 里的 Operation 匹配是精确值Path 是唯一支持contains的字段之一。写过滤条件时不要想当然Path 用contains而不是is因为完整路径太长你几乎不可能写全。3.2 读结果三类最有价值的事件抓到事件之后怎么读比怎么抓更重要。我优先看三类。第一类是结果是NAME NOT FOUND的事件。很多人第一反应是这是错误跳过恰恰相反这类事件信息量最大。它代表程序主动去探测了某个路径只是没找到。你能从里面看出程序的意图它在找自己的配置文件、找某个运行库、找注册表里的授权信息、找前一次运行的缓存。如果一个恶意程序在往C:\Users\Public\下面找某个文件名那基本就是在确认自己之前是否落地过。分析的时候把这类事件单独导出价值很高。第二类是带REPARSE或路径明显异常的事件。比如路径里出现\Device\HarddiskVolumeShadowCopy说明程序在访问卷影副本出现\ProgramData\Package Cache说明在处理安装缓存出现\Windows\SysWOW64说明是 32 位进程在被重定向。这些都需要结合上下文判断不能光看表面路径。第三类是写入类操作。WriteFile、SetEndOfFile、SetRenameInformationFile、SetDispositionInformationFile、RegSetValue这些是程序实际改动了系统状态的证据。做卸载残留分析、做程序到底写没写注册表这类判断时只统计这一类就够了。打开右侧的Event 面板重点看三个字段Desired Access这一位特别关键。Generic Read和Generic Write差别巨大而Read Attributes、Synchronize这类权限基本可以忽略因为太多程序在探测文件存在性时会带上它们。ShareMode别人能不能同时读。如果程序用的是独占模式没有FILE_SHARE_READ那它在文件打开期间会阻塞其他程序这也是文件被占用类问题的根因。Detail 里的 OptionsNon-Directory File、Open Reparse Point、Complete If OpLocked这些标志能帮你判断程序是不是在做小文件读写优化。3.3 保存、导出与二次统计Procmon 自己统计能力有限遇到几百万条事件的时候靠眼睛翻是不现实的。我的常规做法是Save 成 PML 保留原始现场同时导出 CSV 到本地做聚合。导出的时候选 All Events列建议全选但 CSV 体积会非常大一般控制在一百万行以内比较舒服。导出的 CSV 用 PowerShell 处理起来很快。下面这段统计出现次数最多的路径 Top 20我在好几个项目里都直接复用Import-Csv .\Logfile.csv | Group-Object Path | Sort-Object Count -Descending | Select-Object -First 20 Count, Name | Format-Table -AutoSize再换一个维度统计哪些进程在写自己的目录Import-Csv .\Logfile.csv | Where-Object { $_.Operation -in WriteFile,SetEndOfFile,SetRenameInformationFile } | Group-Object Process Name | Sort-Object Count -Descending | Format-Table Count, Name -AutoSize路径里如果混着临时目录和时间戳分组结果会很碎可以先做一次正则清洗再分组。这属于数据分析的常规操作但很多人卡在导出之后不知道怎么下手这一步。4. 句柄快照不装驱动也能看清资源清单4.1 快照法的价值与边界Procmon 有两个明显短板一是需要驱动二是它记录的是事件流不是当前持有状态。当你想回答这个进程此刻还开着哪些资源时事件流帮不上忙——它可能开了又关关了你还在日志里看到一堆记录。这时候要看句柄快照。Process Explorer 打开后按CtrlH显示句柄视图选中目标进程下方面板就会列出该进程当前持有的所有句柄包括类型、名称、权限。想看某个 DLL 被谁加载了按CtrlF调出 Find Handle or DLL输入文件名就能列出所有引用它的进程这个在排查某文件被占用无法删除时几乎是唯一解。命令行版本是handle.exehandle.exe -a -p 1234 handle.exe -u -p 1234 handle.exe -s -p 1234-a显示所有句柄类型不加只会显示文件句柄-u显示句柄所属的用户名-s按类型汇总输出一个计数表看句柄分布非常直观权限上和 Procmon 一样需要管理员。实际上它依赖SeDebugPrivilege管理员默认持有普通用户没有。4.2 用差分法找出短命进程的资源快照法最大的问题是只能看到现在还开着的。对于运行几秒钟就退出的程序等你打开工具去选进程它已经没了。解法是差分在程序启动前抓一次全量句柄快照运行结束后再抓一次对比差集。这样即使进程已经退出你也能推断出它在运行期间开了什么——当然前提是它没有把句柄都关干净。Python 的psutil做这事最省事import psutil, time, json def snapshot(pid): try: p psutil.Process(pid) files [f.path for f in p.open_files()] conns [ f{c.laddr} - {c.raddr} [{c.status}] for c in p.connections(kindinet) ] return {files: files, conns: conns} except Exception as e: return {error: str(e)} target int(input(输入 PID: )) before snapshot(target) print(运行中按回车结束采集...) input() after snapshot(target) print(新增文件句柄) for f in set(after.get(files, [])) - set(before.get(files, [])): print( , f) print(新增网络连接) for c in set(after.get(conns, [])) - set(before.get(conns, [])): print( , c)PowerShell 版本用Get-Process加Modules也能做类似的事$pid 1234 $before (Get-Process -Id $pid).Modules | Select-Object -ExpandProperty FileName Start-Sleep -Seconds 10 $after (Get-Process -Id $pid).Modules | Select-Object -ExpandProperty FileName Compare-Object $before $after | Format-Table -AutoSize两种方式的共同前提是程序运行时间足够长能用轮询的方式采到。如果程序只跑 200 毫秒轮询一定漏。这时候就老老实实回到 Procmon 或者改用 ETW。提示句柄快照的采样间隔别设成 1 秒以下open_files()和connections()在句柄多的进程上是有开销的实测在一个开了一万多个句柄的进程上每次调用会卡住几百毫秒采样太密会把目标拖慢。5. 让 Windows 自己留痕原生审计与事件日志5.1 审计策略的开启方式前面两种方法都需要你主动去抓抓完东西在你自己手里。但如果需求是长期留痕、事后可追溯原生审计才是正解因为它是系统自己写的日志谁在什么时候动了哪个文件记录得很实在。开启分两步。第一步是打开审计子类别auditpol /get /category:* auditpol /set /subcategory:文件系统 /success:enable /failure:enable auditpol /set /subcategory:注册表 /success:enable /failure:enable auditpol /set /subcategory:句柄操作 /success:enable /failure:enable auditpol /set /subcategory:进程创建 /success:enable /failure:enable第二步是关键的一步很多人卡在这光开子类别没用还得在具体的文件或注册表键上设置 SACL。SACL 是系统访问控制列表和常见的 DACL权限列表不是一回事它专门用来描述什么操作要记日志。设置它需要SeSecurityPrivilege普通管理员默认持有。icacls D:\AppData /audit:everyone:(OI)(CI)(F)注册表这边没有直接对应的命令行用 PowerShell 更顺手$key [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey( SOFTWARE\YourApp, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, [System.Security.AccessControl.RegistryRights]::ChangePermissions ) $acl $key.GetAccessControl() $rule New-Object System.Security.AccessControl.RegistryAuditRule( Everyone, [System.Security.AccessControl.RegistryRights]::FullControl, [System.Security.AccessControl.InheritanceFlags]::ContainerInherit, [System.Security.AccessControl.PropagationFlags]::None, [System.Security.AccessControl.AuditFlags]::Success ) $acl.AddAuditRule($rule) $key.SetAccessControl($acl)5.2 关键事件 ID 与读取方法审计打开之后相关事件落在安全日志里。几个必须记住的 ID事件 ID含义关注点4688进程创建命令行、父进程、创建者4656请求对象句柄对象名、访问权限4663对象访问尝试访问掩码、进程名4658句柄关闭配合 4656 算持有时长4660对象被删除删除行为取证5140网络共享访问远程来源地址要拿到 4688 的命令行内容还得额外开一个策略否则命令行字段是空的。这个在组策略里叫审核进程创建时包含命令行命令行开启方式如下reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Audit /v ProcessCreationIncludeCmdLine_Enabled /t REG_DWORD /d 1 /f读日志用Get-WinEvent比图形界面高效得多。下面这段查最近一小时里某个进程访问过的对象$start (Get-Date).AddHours(-1) Get-WinEvent -FilterHashtable { LogName Security Id 4663 StartTime $start } | Where-Object { $_.Message -match target\.exe } | Select-Object TimeCreated, {nObject;e{$_.Properties[8].Value}} | Format-Table -AutoSize这里有个必须提醒的点安全日志的量增长极快。在一个正常办公终端上只对某个应用目录开 SACL一小时也能产生几万条 4663。如果不做日志轮转和容量规划默认 20MB 的安全日志几小时就被冲掉了前面的记录全丢。生产环境里要么把审计范围压到最小只审计关键目录别整个盘要么配置日志转发集中存储。5.3 Sysmon 作为补充原生审计的短板是它只能看到你配了 SACL 的对象看不到内核模块加载、看不到网络连接的归属进程、看不到 WMI 操作。Sysmon 正好补上这块它提供的几个事件 ID 值得记住ID 1进程创建带哈希和命令行ID 3网络连接带源端口、目标 IP 和端口、归属进程ID 11文件创建ID 12/13/14注册表创建/设置/重命名ID 22DNS 查询ID 8远程线程创建这个是很多注入行为的信号配置文件不要照抄网上的全开版那个在终端上一天能写几百 MB。我的习惯是先用比较宽的配置跑两天统计一下事件分布再按实际噪音量裁剪。Sysmon 的配置更新不需要重启系统改完sysmon64.exe -c config.xml直接生效这点比装驱动舒服很多。6. 网络资源端口、连接与流量归宿6.1 从进程反查连接文件那套工具对网络其实覆盖得一般网络侧要单独看。最基础的是netstat -ano重点看最后一列的 PIDnetstat -ano | findstr LISTENING netstat -ano -p tcpPowerShell 的版本信息更结构化还自带进程名Get-NetTCPConnection -State Listen | Select-Object LocalAddress, LocalPort, OwningProcess, {nProc;e{(Get-Process -Id $_.OwningProcess).ProcessName}}这个组合在排查某个端口被谁占了时非常快。注意 IPv6 的监听会同时出现在 IPv4 和 IPv6 两张表里同一端口看到两条记录是正常的别以为冲突了。6.2 pktmon不用装第三方就能抓包Windows 10 1809 之后内置了pktmon很多 Server 版本也有。它的好处是纯系统组件不需要额外装驱动也不会和现有抓包工具抢网卡。pktmon filter remove pktmon filter add -p 8443 pktmon start --etw -m real-time pktmon stop pktmon etl2pcap PktMon.etl -o capture.pcap转成 pcap 之后可以用常规分析工具打开这个流程在客户现场特别管用因为很多时候不让装别的软件。要抓环回流量的话pktmon也能覆盖因为它挂的位置比物理网卡更靠上。6.3 ETW 网络事件需要长时间记录网络行为的时候ETW 的Microsoft-Windows-Kernel-Network是最稳的选择它记录了每个 TCP/UDP 连接的四元组和归属 PID开销比抓全量包小得多。配合Microsoft-Windows-DNS-Client还能拿到域名解析记录把 IP 反查回域名。如果只是想看某个进程连了哪些域名Sysmon ID 22 其实就够用了配置简单还能直接写进日志系统。7. 常见问题与排查速查表7.1 症状、原因与处理对照下面这张表是我这些年反复遇到过的按出现频率排的序。症状常见原因处理办法Procmon 里子进程行为全丢过滤条件只写了父进程名打开 Advanced Output 按 PID 过滤或先不加进程条件看到NAME NOT FOUND就以为是错误误判了探测行为这是高价值信息单独导出分析关键路径看不到进程以 SYSTEM 运行权限不够用psexec -s提升到 SYSTEM 再运行工具抓到的路径和实际不符WOW64 重定向或卷挂载点映射换算SysWOW64与System32检查\Device前缀系统明显变卡甚至卡死多个文件过滤驱动安全软件冲突减少同时运行的工具一次只开一个程序一启动工具就丢事件程序有反调试或反注入检测改用 ETW 或原生审计别做注入日志翻得太快历史记录没了安全日志容量太小调大Security日志上限或配置转发时间戳对不上多台机器时区或时间未同步统一 NTP日志一律用 UTC 做对齐7.2 几个值得展开的坑第一个坑是看不见不代表没发生。有一次我分析一个更新程序Procmon 里干干净净只有几条读配置文件的记录。后来用句柄快照一查它其实在启动时通过命名管道和已有服务通了信真正的下载逻辑在那个服务进程里。如果只看目标 exe结论会完全错。所以每次分析之前先想清楚这个程序有没有可能把活外包给别的进程有的话必须把关联进程一起纳入观测范围。第二个坑是权限导致的静默丢失。Procmon 在标准用户下运行时它不会报错只是默默漏掉高权限进程的事件。很多人第一次用没注意这个事后拿结果去下结论方向就跑偏了。养成习惯打开之前先确认标题栏有没有显示管理员模式。第三个坑是时间窗口。事件日志的默认保留策略是按需覆盖不是保留固定天数。分析一个前几天发生的问题时先查一下日志最早能追到什么时候Get-WinEvent -ListLog Security | Select-Object MaximumSizeInBytes, FileSize, RecordCount如果RecordCount已经被覆盖过一轮那前面的记录就没了只能靠之前导出的归档。这也是为什么长期留痕场景必须提前规划事后补救来不及。第四个坑是把命名管道当成文件。Procmon 里\Device\NamedPipe\xxx这种路径看起来很像文件操作但它的语义完全不同——这是进程间通信不是磁盘 IO。如果你在统计程序写了多少数据到磁盘时把它算进去数字会虚高。8. 我个人的一点使用顺序建议分析一个陌生程序我现在基本固定成这么一套流程供参考。先花五分钟做静态观察Get-Process看它的加载模块Get-NetTCPConnection看有没有已经建立的连接Process Explorer 扫一眼句柄数量和类型分布。这一步不费劲但能帮你判断后面该重点看什么——如果启动就有一堆网络连接那网络侧是主战场如果句柄数上千多半是数据库或缓存类应用文件侧是重点。然后用 Procmon 做一次完整的捕获过滤条件从宽到窄。抓完先看NAME NOT FOUND和写入类事件这两个列表看完程序的行为轮廓基本就出来了。剩下的读操作大多是运行库加载优先级低。如果怀疑有子进程或者行为分散在多个进程里就补一轮句柄快照做交叉验证。最后如果这个程序需要在多台机器上反复分析那就别每次都手工抓了写个脚本把 ETW 或者 Sysmon 的采集固定下来日志统一归档。手工抓一次两次可以抓二十次一定会出错而且不同人抓的口径还不一致数据没法横向比。至于那些内核层驱动、绕过用户态挂钩的手段坦白说我遇到的不多而且这类对抗性场景往往需要专门的工具链配合不是一篇文章能讲透的。对日常的故障排查、兼容性验证、安全自查来说上面这套组合已经覆盖了绝大多数情况。真到了需要看内核回调的深度说明问题本身已经超出常规分析的范畴了。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。