0x00 前言 JonMon-Lite 是一个很有意思的 Windows 安全监控 PoC。它的核心卖点不是“又写了一个 Agent”,而是反过来:利用 Windows 原生的 Performance Logs and Alerts、Data Collector Set、ETW 和 Event Log Provider,实现一种远程、无常驻 Agent 的事件采集方式。
这篇文章记录一次完整实验:主机作为检测端,虚拟机作为被监控测试机。最终效果是:
1 2 3 4 5 主机远程创建虚拟机上的 Data Collector Set 虚拟机产生 ETW 事件 ETL 文件写回主机共享目录 主机 JonMon-Lite 读取 ETL 事件写入主机 Event Viewer 的 JonMon-Lite/Operational
实验过程中遇到的关键问题是远程 PLA/DCOM 权限、SMB 共享权限、runas /netonly 网络身份,以及 ETL 回写路径。下面按原理和实践完整记录。
0x01 JonMon-Lite 的核心原理 JonMon-Lite 的 README 将它描述为一个 research proof-of-concept 的 “Remote Agentless EDR”。
它的核心由四部分组成:
1 2 3 4 JonMon-Lite.exe 主程序,负责创建远程 Data Collector Set 并处理 ETL JonMon-Lite.json 运行配置文件 JonMon-Lite.xml Data Collector Set 使用的采集配置 JonMon-Lite-Provider.man/dll 自定义事件 Provider,用于写入 Event Viewer
它不是在目标机器上部署长期运行的 Agent,而是通过 Windows 原生机制远程创建 ETW Trace Session。远程创建成功后,目标机器上的 ETW 事件会被写入 .etl 文件;主机端再读取 .etl,解析指定 Provider/Event ID,最后以 JonMon-Lite 自己定义的事件格式写入 Windows 事件日志。
换句话说,它的关键链路是:
1 2 3 4 5 6 7 8 9 10 11 12 13 主机 | | DCOM/PLA remote commit v 虚拟机 Data Collector Set | | ETW providers -> JonMon-Lite.etl v 主机共享目录 | | OpenTraceW / ProcessTrace v JonMon-Lite/Operational Event Log
0x02 实验环境 本次实验拓扑:
1 2 3 4 5 6 7 8 主机名: CHANGMEN 主机 VMnet8 IP: 192.168.59.1 主机共享目录: C:\JonMonDrop 主机共享 UNC: \\192.168.59.1\JonMonDrop 虚拟机名: DESKTOP-TC0RGKO 虚拟机 IP: 192.168.59.168 虚拟机用户: DESKTOP-TC0RGKO\jonmon
虚拟机是测试机器,主机是检测机器。最终要在主机事件查看器里看到虚拟机行为产生的检测事件。
0x03 准备主机共享目录 在主机管理员 PowerShell 中进入项目目录:
1 cd F:\SEC\github\JonMon-Lite-main \JonMon-Lite-main
运行项目自带脚本:
1 2 3 4 powershell -ExecutionPolicy Bypass -File .\scripts\setup-host-prereqs .ps1 ` -VmIp "192.168.59.168" ` -SharePath "C:\JonMonDrop" ` -ShareName "JonMonDrop"
成功后会看到类似:
1 2 3 4 Host prerequisites are ready. Share path: C:\JonMonDrop Share UNC: \\CHANGMEN\JonMonDrop VM IP: 192.168.59.168
这个脚本会创建主机共享目录,并把虚拟机准备脚本 setup-vm-prereqs.ps1 放进去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 param ( [string ]$VmIp = "192.168.59.168" , [string ]$SharePath = "C:\JonMonDrop" , [string ]$ShareName = "JonMonDrop" ) $ErrorActionPreference = "Stop" function Assert-Admin { $identity = [Security.Principal.WindowsIdentity ]::GetCurrent() $principal = [Security.Principal.WindowsPrincipal ]::new($identity ) if (-not $principal .IsInRole([Security.Principal.WindowsBuiltInRole ]::Administrator)) { throw "Run this script in an elevated PowerShell window." } } function Ensure-FirewallRule { param ( [string ]$Name , [string ]$DisplayName , [hashtable ]$Rule ) $existing = Get-NetFirewallRule -Name $Name -ErrorAction SilentlyContinue if ($existing ) { Set-NetFirewallRule -Name $Name -Enabled True -Action Allow -Direction Inbound Set-NetFirewallAddressFilter -AssociatedNetFirewallRule $existing -RemoteAddress $Rule .RemoteAddress return } New-NetFirewallRule @Rule -Name $Name -DisplayName $DisplayName | Out-Null } Assert-Admin $vmnet8 = Get-NetConnectionProfile -InterfaceAlias "VMware Network Adapter VMnet8" -ErrorAction SilentlyContinueif ($vmnet8 ) { Set-NetConnectionProfile -InterfaceAlias "VMware Network Adapter VMnet8" -NetworkCategory Private } New-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Force | Out-Null New-ItemProperty ` -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" ` -Name "LocalAccountTokenFilterPolicy" ` -PropertyType DWord ` -Value 1 ` -Force | Out-Null New-Item -Path $SharePath -ItemType Directory -Force | Out-Null icacls $SharePath /grant "*S-1-1-0:(OI)(CI)M" /T | Out-Null $share = Get-SmbShare -Name $ShareName -ErrorAction SilentlyContinueif (-not $share ) { New-SmbShare -Name $ShareName -Path $SharePath -FullAccess Everyone | Out-Null } else { Grant-SmbShareAccess -Name $ShareName -AccountName Everyone -AccessRight Full -Force | Out-Null } $vmSetupScript = Join-Path $PSScriptRoot "setup-vm-prereqs.ps1" if (Test-Path $vmSetupScript ) { Copy-Item $vmSetupScript (Join-Path $SharePath "setup-vm-prereqs.ps1" ) -Force } Ensure-FirewallRule ` -Name "JonMon-SMB-From-VM" ` -DisplayName "JonMon SMB from VM" ` -Rule @ { Direction = "Inbound" Action = "Allow" Protocol = "TCP" LocalPort = 445 RemoteAddress = $VmIp Profile = "Any" } Write-Host "Host prerequisites are ready." Write-Host "Share path: $SharePath " Write-Host "Share UNC: \\$env:COMPUTERNAME \$ShareName " Write-Host "VM IP: $VmIp "
0x04 准备虚拟机账号和权限 虚拟机上的远程 PLA/DCOM 调用需要管理员账号。如果虚拟机当前账号没有密码,建议创建一个实验专用账号。
在虚拟机管理员 PowerShell 中:
1 2 net user jonmon <YourStrongPassword> /add net localgroup administrators jonmon /add
然后把它加入远程 DCOM、Performance Logs 相关组:
1 2 3 net localgroup "Distributed COM Users" jonmon /add net localgroup "Performance Log Users" jonmon /add net localgroup "Performance Monitor Users" jonmon /add
确认:
应能看到它属于:
1 2 3 4 5 Administrators Distributed COM Users Performance Log Users Performance Monitor Users Users
0x05 在虚拟机上运行准备脚本 先确保虚拟机能访问主机共享。因为主机共享可能需要认证,可以在虚拟机管理员 PowerShell 中挂载:
1 net use \\192.168 .59.1 \JonMonDrop /user:CHANGMEN\jonmon <YourStrongPassword>
如果主机没有 jonmon,可以在主机也创建同名同密码账号。这样后续虚拟机上的 DESKTOP-TC0RGKO\jonmon 写主机共享时更容易通过认证。
虚拟机中运行:
1 2 powershell -ExecutionPolicy Bypass -File \\192.168 .59.1 \JonMonDrop\setup-vm-prereqs .ps1 ` -HostIp "192.168.59.1"
成功标志:
1 2 3 VM prerequisites are ready. Allowed host IP: 192.168.59.1 Service pla: Running
如果脚本报 HKLM 注册表权限不足,说明 PowerShell 不是管理员权限。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 param ( [string ]$HostIp = "192.168.59.1" ) $ErrorActionPreference = "Stop" function Assert-Admin { $identity = [Security.Principal.WindowsIdentity ]::GetCurrent() $principal = [Security.Principal.WindowsPrincipal ]::new($identity ) if (-not $principal .IsInRole([Security.Principal.WindowsBuiltInRole ]::Administrator)) { throw "Run this script in an elevated PowerShell window." } } function Ensure-FirewallRule { param ( [string ]$Name , [string ]$DisplayName , [hashtable ]$Rule ) $existing = Get-NetFirewallRule -Name $Name -ErrorAction SilentlyContinue if ($existing ) { Set-NetFirewallRule -Name $Name -Enabled True -Action Allow -Direction Inbound Set-NetFirewallAddressFilter -AssociatedNetFirewallRule $existing -RemoteAddress $Rule .RemoteAddress return } New-NetFirewallRule @Rule -Name $Name -DisplayName $DisplayName | Out-Null } Assert-Admin Set-Service -Name pla -StartupType ManualStart-Service -Name pla -ErrorAction SilentlyContinueNew-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Force | Out-Null New-ItemProperty ` -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" ` -Name "LocalAccountTokenFilterPolicy" ` -PropertyType DWord ` -Value 1 ` -Force | Out-Null Ensure-FirewallRule ` -Name "JonMon-ICMP-From-Host" ` -DisplayName "JonMon ICMP from host" ` -Rule @ { Direction = "Inbound" Action = "Allow" Protocol = "ICMPv4" IcmpType = 8 RemoteAddress = $HostIp Profile = "Any" } Ensure-FirewallRule ` -Name "JonMon-DCOM135-From-Host" ` -DisplayName "JonMon DCOM 135 from host" ` -Rule @ { Direction = "Inbound" Action = "Allow" Protocol = "TCP" LocalPort = 135 RemoteAddress = $HostIp Profile = "Any" } Ensure-FirewallRule ` -Name "JonMon-PLA-From-Host" ` -DisplayName "JonMon PLA service from host" ` -Rule @ { Direction = "Inbound" Action = "Allow" Protocol = "TCP" Program = "$env:SystemRoot \System32\plasrv.exe" RemoteAddress = $HostIp Profile = "Any" } Write-Host "VM prerequisites are ready." Write-Host "Allowed host IP: $HostIp " Write-Host "Service pla: $ ((Get-Service pla).Status)"
0x06 关键点:使用 runas /netonly 启动主机 PowerShell 这是本次实验最关键的点。
直接在主机当前用户下运行 JonMon-Lite,可能会出现:
1 pDataCollectorSet->Commit failed with 0x80070035
或:
1 pDataCollectorSet->Commit failed with 0x80070005
原因是远程 PLA/DCOM 的网络身份没有正确使用虚拟机管理员账号。
正确做法是在主机管理员 PowerShell 中打开一个 /netonly 窗口:
1 runas /netonly /user:DESKTOP-TC0RGKO \jonmon powershell
输入虚拟机 jonmon 的密码。
新窗口里执行:
可能仍显示:
这是正常的。/netonly 的特点是本地身份不变,但访问远程资源时使用指定的网络凭据。
0x07 验证远程 PLA 是否打通 不要一上来就跑 JonMon-Lite。先用 Windows 原生命令确认远程 Data Collector Set 创建能力。
在 /netonly 窗口中:
1 logman query -s DESKTOP-TC0RGKO
成功输出类似:
1 2 3 4 数据收集器集 类型 状态 ------------------------------------------------------------------------------- 命令成功结束。
然后测试最小远程 trace 创建:
1 2 3 logman create trace JonMonTest -s DESKTOP-TC0RGKO ` -o C:\JonMonDrop\JonMonTest.etl ` -p Microsoft-Windows-Kernel-Process 0 x10 5
查询:
1 logman query -s DESKTOP-TC0RGKO
应看到:
清理:
1 logman delete JonMonTest -s DESKTOP-TC0RGKO
只有这一步成功,JonMon-Lite 的远程核心才具备运行条件。
0x08 启动 JonMon-Lite 仍然在同一个 /netonly 主机窗口中执行:
1 cd F:\SEC\github\JonMon-Lite-main \JonMon-Lite-main
启动 JonMon-Lite:
1 2 3 4 5 6 7 powershell -ExecutionPolicy Bypass -File .\scripts\run-jonmon-remote .ps1 ` -ReleaseDir "F:\SEC\github\JonMon-Lite-main\JonMon-Lite-main\JonMon-Lite\x64\Release" ` -VmName "DESKTOP-TC0RGKO" ` -VmUser "DESKTOP-TC0RGKO\jonmon" ` -EtlFilePath "C:\JonMonDrop\" ` -RootPath "\\192.168.59.1\JonMonDrop\" ` -TraceName "JonMon-Lite"
参数含义:
1 2 3 4 5 6 ReleaseDir JonMon-Lite.exe、XML、manifest、provider dll 所在目录 VmName 远程虚拟机主机名 VmUser 虚拟机上用于运行 Data Collector Set 的管理员账号 EtlFilePath 主机本地读取 ETL 的目录 RootPath 虚拟机 Data Collector Set 写 ETL 的目录 TraceName Data Collector Set 名称
成功标志:
1 2 3 4 5 Credentials set successfully. pDataCollectorSet->put_RootPath was set successfully pDataCollectorSet->SetXml was set successfully Collector set 'JonMon-Lite' has been created/updated successfully. Collector set 'JonMon-Lite' started successfully.
如果启动初期出现:
1 ETL file not found: C:\JonMonDrop\DESKTOP-TC0RGKO_\JonMon-Lite.etl, waiting 4 seconds...
不一定是失败。处理线程可能比远程采集器启动更快。只要后面出现 Collector set ... started successfully,核心链路已经起来了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 param ( [string ]$ReleaseDir = "F:\SEC\github\JonMon-Lite-main\JonMon-Lite-main\JonMon-Lite\x64\Release" , [string ]$VmName = "DESKTOP-TC0RGKO" , [string ]$VmUser = "DESKTOP-TC0RGKO\20174" , [string ]$EtlFilePath = "C:\JonMonDrop\" , [string ]$RootPath = "\\192.168.59.1\JonMonDrop\" , [string ]$TraceName = "JonMon-Lite" ) $ErrorActionPreference = "Stop" function Assert-Admin { $identity = [Security.Principal.WindowsIdentity ]::GetCurrent() $principal = [Security.Principal.WindowsPrincipal ]::new($identity ) if (-not $principal .IsInRole([Security.Principal.WindowsBuiltInRole ]::Administrator)) { throw "Run this script in an elevated PowerShell window." } } function ConvertTo-PlainText { param ([securestring ]$SecureString ) $bstr = [Runtime.InteropServices.Marshal ]::SecureStringToBSTR($SecureString ) try { [Runtime.InteropServices.Marshal ]::PtrToStringBSTR($bstr ) } finally { [Runtime.InteropServices.Marshal ]::ZeroFreeBSTR($bstr ) } } Assert-Admin $ReleaseDir = (Resolve-Path $ReleaseDir ).Path$exe = Join-Path $ReleaseDir "JonMon-Lite.exe" $xml = Join-Path $ReleaseDir "JonMon-Lite.xml" $manifest = Join-Path $ReleaseDir "JonMon-Lite-Provider.man" $providerDll = Join-Path $ReleaseDir "JonMon-Lite-Provider.dll" $runtimeConfig = Join-Path $ReleaseDir "JonMon-Lite.runtime.json" foreach ($path in @ ($exe , $xml , $manifest , $providerDll )) { if (-not (Test-Path $path )) { throw "Missing required file: $path " } } New-Item -Path $EtlFilePath -ItemType Directory -Force | Out-Null $securePassword = Read-Host "Enter password for $VmUser " -AsSecureString $plainPassword = ConvertTo-PlainText -SecureString $securePassword $config = [ordered ]@ { XMLFilePath = $xml ETLFilePath = $EtlFilePath RootPath = $RootPath TraceName = $TraceName WorkstationName = @ ($VmName ) User = $VmUser Password = $plainPassword } try { $config | ConvertTo-Json -Depth 4 | Set-Content -Path $runtimeConfig -Encoding ASCII Write-Host "Starting JonMon-Lite. Type 'exit' in its console to stop collection." Push-Location $ReleaseDir try { & $exe $runtimeConfig } finally { Pop-Location } } finally { Remove-Item $runtimeConfig -Force -ErrorAction SilentlyContinue }
0x09 产生测试事件 在虚拟机中产生一些行为:
1 2 3 notepad.exe New-Item C:\Users\Public\demo.ps1 -ItemType File -Force Start-Process powershell -ArgumentList "-NoProfile -Command whoami"
JonMon-Lite 默认关注的事件包括:
1 2 3 4 5 6 7 8 9 10 11 Process Creation File Creation DotNetLoad WMIEventFilter RPCClientCall RPCServerCall CryptUnprotectData AMSI NetworkConnection ServiceCreation RegistryCreateKey
文件创建事件默认会过滤扩展名,比较关注:
1 .exe .sys .dll .js .vbs .ps1 .bat .cmd .hta .msi
0x0A 查看事件 在主机打开事件查看器:
路径:
1 2 3 应用程序和服务日志 -> JonMon-Lite -> Operational
英文系统:
1 2 3 Applications and Services Logs -> JonMon-Lite -> Operational
也可以用 PowerShell 查看:
1 2 Get-WinEvent -LogName "JonMon-Lite/Operational" -MaxEvents 20 | Format-Table TimeCreated, Id, ProviderName, Message -Auto
确认 ETL 是否生成:
1 dir C:\JonMonDrop\DESKTOP-TC0RGKO_
正常应有:
0x0B 停止采集 回到 JonMon-Lite 控制台,输入:
或:
程序会设置退出事件,停止远程 Data Collector Set,并清理 JonMon-Lite manifest。
0x0C 常见问题和排错 1. logman query -s DESKTOP-TC0RGKO 找不到网络路径 常见原因:
1 2 3 4 主机名解析到了 IPv6 link-local 远程 PLA/DCOM 没打通 没有使用 /netonly 网络身份 虚拟机防火墙没有开放远程管理
先确认:
1 2 Test-NetConnection DESKTOP-TC0RGKO -Port 135 Test-NetConnection DESKTOP-TC0RGKO -Port 445
如有必要,在主机 hosts 中添加:
1 192.168.59.168 DESKTOP-TC0RGKO
2. logman query -s DESKTOP-TC0RGKO 拒绝访问 如果普通 PowerShell 拒绝访问,使用:
1 runas /netonly /user:DESKTOP-TC0RGKO \jonmon powershell
然后在新窗口里重试:
1 logman query -s DESKTOP-TC0RGKO
如果仍失败,检查虚拟机上的用户组:
至少应包含:
1 2 3 4 Administrators Distributed COM Users Performance Log Users Performance Monitor Users
3. WMI 拒绝访问 主机测试:
1 2 3 4 $cred = Get-Credential DESKTOP-TC0RGKO \jonmonGet-WmiObject Win32_OperatingSystem -ComputerName DESKTOP-TC0RGKO ` -Credential $cred ` -Authentication PacketPrivacy
如果 WMI 不通,需要检查虚拟机 WMI/DCOM 权限和防火墙。
虚拟机中可执行:
1 2 3 Enable-NetFirewallRule -DisplayGroup "Windows Management Instrumentation (WMI)" Enable-NetFirewallRule -DisplayGroup "远程服务管理" Enable-NetFirewallRule -DisplayGroup "文件和打印机共享"
英文系统则使用:
1 2 3 Enable-NetFirewallRule -DisplayGroup "Windows Management Instrumentation (WMI)" Enable-NetFirewallRule -DisplayGroup "Remote Service Management" Enable-NetFirewallRule -DisplayGroup "File and Printer Sharing"
4. pDataCollectorSet->Commit failed with 0x80070035 这是“找不到网络路径”。在本次实验中,根因是主机没有用正确网络身份远程调用虚拟机 PLA。
解决关键:
1 runas /netonly /user:DESKTOP-TC0RGKO \jonmon powershell
并先确认:
1 logman query -s DESKTOP-TC0RGKO
成功后再跑 JonMon-Lite。
5. pDataCollectorSet->Commit failed with 0x80070005 这是“拒绝访问”。常见原因:
1 2 3 远程 PLA 权限不足 jonmon 不在 Performance Log Users 等组 RootPath 指向主机共享,但远程采集器身份无法写入
可以先用虚拟机本地 RootPath 验证远程创建:
1 -RootPath "C:\JonMonDrop\"
如果本地 RootPath 成功,共享 RootPath 失败,问题就在主机共享写入权限。
6. ETL file not found 启动初期看到:
1 ETL file not found: C:\JonMonDrop\DESKTOP-TC0RGKO_\JonMon-Lite.etl
不一定失败。因为 JonMon-Lite 的处理线程可能先于远程 Data Collector Set 创建 ETL 文件。
重点看后续有没有:
1 Collector set 'JonMon-Lite' started successfully.
然后确认:
1 dir C:\JonMonDrop\DESKTOP-TC0RGKO_
0x0D 安全注意事项 这套实验会涉及:
1 2 3 4 5 管理员账号 SMB 共享 DCOM/WMI 远程权限 Performance Logs and Alerts Event Log Provider 注册
因此建议只在实验环境使用。不要在生产环境直接使用弱密码或 Everyone Full Control。
实验结束后建议:
或至少修改密码。
如果主机上也创建了同名账号,也应清理:
同时删除临时共享:
1 net share JonMonDrop /delete
0x0E 总结 JonMon-Lite 的核心价值在于它演示了一个很有意思的思路:不用在目标机器上部署长期 Agent,而是通过 Windows 原生 PLA/DCOM 远程创建 ETW Trace Session,再把 ETL 回写到主机,由主机解析并转写成自定义 Event Log。
本次实验真正的关键不是编译,也不是 ETW 本身,而是远程 Windows 管理链路:
1 2 3 4 5 SMB 能写 WMI/DCOM 能远程认证 logman -s 能查询和创建 runas /netonly 提供正确网络身份 RootPath 能被虚拟机上的采集器写入
只要 logman create trace ... -s DESKTOP-TC0RGKO 能成功,JonMon-Lite 的远程核心基本就有了运行基础。最终成功标志是:
1 2 Collector set 'JonMon-Lite' has been created/updated successfully. Collector set 'JonMon-Lite' started successfully.
然后在主机事件查看器中查看:
1 2 3 Applications and Services Logs -> JonMon-Lite -> Operational
这就是一次完整的 JonMon-Lite remote agentless EDR 实验链路。
九、总结 PLA DCOM 接口并非新技术,它一直以来都是 Windows 的原生能力,只是从未被安全社区系统审视过。其核心价值在于:
无 Agent 的 ETW 采集 :在不对目标机器产生任何落地影响的前提下,获取与本地 EDR 同等质量的遥测数据
覆盖 EDR 盲区 :对不适合部署 Agent 的服务器、网络设备提供监控能力
攻击面意识 :防御方需意识到,攻击者同样可以利用这套接口进行隐蔽侦察和防御干扰
这一发现的方法论同样值得关注:对成熟工具(logman)进行逆向分析,往往能在已知功能的背面发现未被关注的安全盲区。
参考资料