AIGC标识 双份 tasklist 快照揪出隐藏后门:一次 Backdoor/Lotok(银狐)木马的排查与清除实录

摘要:一次真实环境下的 Windows 后门木马应急排查实录。起点只有两份普通的 tasklist 快照,最终定位并清除一个采用「白利用 + BYOVD 内核对抗」的远控后门——微软命名 Backdoor/Lotok(国内安全社区通称 「银狐」家族)的变种。文中给出完整的排查思路、可复用脚本与 IOC 清单。

 

〇、TL;DR

一份普通的 tasklist 快照里藏着远控后门。我靠两份间隔 14 分钟的进程快照比对锁定异常进程,顺着查到 4 个持久化复活点(伪装服务 ×2、Winlogon Userinit 劫持、隐藏计划任务、内核漏洞驱动),完成证据固定 → 提权清除 → 重启复核,全程系统自带 PowerShell,零第三方工具。

事后经公开情报比对定性:样本属于 Backdoor/Lotok 家族(国内安全社区通称 「银狐」)的变种——一个以白利用 + BYOVD 内核对抗著称的远控后门木马。

沉淀出的方法论:排查五步(定异常 → 验身份 → 挖持久 → 查外联 → 核情报)+ 处置五则(断网 → 留证 → 拆除 → 复核 → 善后)。三个脚本附在文末,IOC 在第十一章。

 

一、背景

某天怀疑自己的 Windows 11 工作机中招(Windows PowerShell 5.1 环境)。手头能用的起点只有两份 tasklist /v /fo csv 快照,导出间隔约 14 分钟。

tasklist /v /fo csv > 快照1.csv
:: 间隔 10~15 分钟后再导出一次
tasklist /v /fo csv > 快照2.csv

两次导出、Excel 或 Beyond Compare 一对比即可(也可以直接目视 CPU 时间列)。当然,能上机的话直接用后面的脚本全量扫也行,快照比对适合"先期低打扰排查"和"远程指导别人自查"的场景。

最终结果:定位并清除了一个活跃的远控后门。后经情报比对,定性为 Backdoor/Lotok(国内通称「银狐」)家族变种,采用白利用 + BYOVD 内核对抗,重启后无复活。

二、总体思路

木马排查本质上就回答五个问题:

步骤

问题

手段

① 定异常

哪个进程不该在?

双快照比对 CPU 时间增量

② 验身份

它是什么文件、从哪来?

路径 / 数字签名 / 版本信息 / 哈希

③ 挖持久

它靠什么复活?

服务、Winlogon、计划任务、驱动、Run 键

④ 查外联

它在跟谁通信?

TCP 连接归属到 PID

⑤ 核情报

它是不是已知家族?

哈希/路径组合搜威胁情报

确认感染后,处置按五则执行:断网 → 留证 → 拆除 → 复核 → 善后。

三、第一步:定异常(双快照比对)

核心看 CPU 时间 列——它是累计值,只有持续干活的进程才会涨。本例数据:

进程

PID

会话

CPU 时间(前→后)

备注

XUo.exe

5148

Services

0:01:05 → 0:02:55

14 分钟涨了 110 秒,后台一直在狂跑

aWNCH.exe

5012

Services

0:00:03 → 0:00:05

与 XUo 同目录同体积(110,208 字节)

aWNCH.exe

8384

Console(当前用户)

0:00:06 → 0:00:07

内存 97MB,父进程 winlogon.exe

判读要点:

  1. Services 会话 + 随机大小写混淆命名(XUo / aWNCH)+ 状态 Unknown,这组特征本身就是强信号;

  2. 只看 CPU 会漏人:aWNCH 几乎不耗 CPU(纯控制型后门),是靠"内存占用 + 会话归属 + 父进程"交叉出来的;

  3. 父进程是 winlogon.exe 是最大红旗——说明它由"登录流程"拉起,十有八九存在持久化劫持;

  4. 后续还观察到过该进程的第三个实例(PID 22120),说明有自我拉起 / 多实例机制。

四、第二步:验身份(路径 / 签名 / 哈希)

坑位 1:普通权限读不到系统进程的路径

Win32_Process 的 ExecutablePath 对 SYSTEM 进程直接返回空。绕过方法:用服务信息反查——Win32_Service.PathName 或注册表 HKLM\SYSTEM\CurrentControlSet\Services\<服务名>\ImagePath(代码见 9.1 脚本的 A 段)。

反查结果:C:\Drivers\MdudrUu\aWNCH.exe。

注意:这是根目录下自建的"非常规目录"。正常软件不会把组件丢在 C:\Drivers 这种地方——红旗 +1。

签名 + 版本信息交叉验证

  • 签名状态:Valid,签名主体还是正规厂商(本案是某知名办公软件厂商);

  • 但版本信息全空——CompanyName、FileDescription 都是空白。

这是典型的签名滥用 / 白利用:借有效签名过信誉检查,但正规软件不可能连版本信息都不写。 "有有效签名" ≠ "该文件可信"。 三者交叉(签名 + 版本信息 + 哈希)才能定性。

再取 SHA256 留作情报检索:

B5AE5E8325AB0D35EFA8F0C6B9B50702EEDE7F32EDEDE7516CDFDFE3D42951C6  (aWNCH.exe)

坑位 2:文件偶尔"读不到" ≠ 文件不存在

排查中曾出现文件明明在目录里、Get-Item 却报找不到的情况(被拦截/占用/保护)。 换个时机、提权或进安全模式重试即可,不要因此下"文件不存在"的结论。

 

五、第三步:挖持久(四个复活点)

5.1 伪装服务 ×2

LogHelper     => cmd /c cd /d "C:\Drivers\MdudrUu" && start "" "C:\Drivers\MdudrUu\aWNCH.exe"
RemoteHandler => cmd /c cd /d "C:\Drivers\MdudrUu" && start "" "C:\Drivers\MdudrUu\XUo.exe"

特征:cmd /c ... && start 链式启动 + 指向非常规目录 + 自动启动 + LocalSystem 权限。

重点知识点:cmd 执行完 start 后就退出了,所以 SCM 里这两个服务显示的都是**"已停止"**,但目标进程早就被拉起来了。服务显示"Stopped"绝不代表没干活——它的"干活"就是把木马拉起来然后功成身退。

5.2 Winlogon Userinit 劫持

Userinit = C:\Windows\system32\userinit.exe,C:\Drivers\MdudrUu\aWNCH.exe.,

在原值后面追加一个启动项(还带个".,"尾巴做障眼法),每次登录它都复活。这也是 8384 号进程父进程是 winlogon.exe 的原因。

5.3 隐藏计划任务

同名计划任务 LogHelper → aWNCH.exe。

这个是清理阶段扫出来的——排查时容易只盯服务和注册表而漏掉计划任务。持久化清单里务必包含:服务、Winlogon、计划任务、Run/RunOnce、IFEO、驱动、AppInit。

5.4 内核漏洞驱动(BYOVD)

ardrv => \??\C:\Drivers\MdudrUu\ardrv.sys   (KERNEL_DRIVER)

该驱动被指为 OPSWAT ARD(AppRemover Driver)的漏洞利用载体(CVE-2026-36425),是典型的 BYOVD(Bring Your Own Vulnerable Driver,自带易受攻击驱动) 手法:用带合法签名但存在漏洞的驱动进内核,然后关防护、杀安全软件进程/回调。这就是为什么普通杀软对它束手无策。

自查命令(只读):

# 找 cmd /c 链式启动的服务
Get-ChildItem 'HKLM:\SYSTEM\CurrentControlSet\Services' | ForEach-Object {
  $ip = (Get-ItemProperty -LiteralPath $_.PSPath).ImagePath
  if ($ip -and $ip -match 'cmd /c') { $_.PSChildName + ' => ' + $ip }
}
# 看 Userinit 是否被加料(正常值以 userinit.exe, 结尾)
(Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon').Userinit

六、第四步:查外联

把嫌疑 PID 拿去做 OwningProcess 过滤,一条命令揪出 C2:

Get-NetTCPConnection | Where-Object { $_.OwningProcess -in 5012,5148,8384 } |
  Select-Object OwningProcess, LocalAddress, LocalPort, RemoteAddress, RemotePort, State

输出(节选):

pid=8384  192.168.x.x:598xx -> 222.167.35.123:443  Established
pid=8384  192.168.x.x:598xx -> 222.167.35.123:443  TimeWait(多条)

aWNCH.exe(8384)与 222.167.35.123:443 保持长连接、且有多条 TimeWait——反复回连,实锤 C2。

提示:低 CPU 消耗的"纯控制型"木马,外联是它最藏不住的地方。

七、第五步:核情报

7.1 情报命中

拿"路径组合 + 哈希"去搜索引擎/情报平台检索,命中信息量很大:

  • 有网友在同期(感染次日)发帖报告完全相同的路径组合:C:\Drivers\MdudrUu + XUo.exe + ardrv.sys,截图中 Defender 对该文件报 Backdoor/Lotok.kw,并同样提到 ardrv.sys(CVE-2026-36425);

  • 说明这不是孤例,而是一波同期的投毒/挂马活动(我们的感染时间戳与网友案例只差一天)。

7.2 家族定性:Backdoor/Lotok(国内通称「银狐」)

以 Defender 检出名 Backdoor/Lotok.kw 为线索继续检索,样本的家族与类型就清晰了:

  • 家族名称:Backdoor/Lotok——微软 Defender 的命名(.kw 为变种后缀,同类还有 .MC、.sbc 等);国内安全社区与厂商(火绒、奇安信等)通常把该家族统称为 「银狐」(Silver Fox),奇安信沙箱等平台亦报 Backdoor.Lotok.xxx;

  • 恶意类型:远控后门木马(Backdoor / RAT)。模块化架构,可向受控端下发任意功能模块(窃密、远程控制、代理转发等);该家族源码曾在暗网外泄,导致变种极多、迭代极快;

  • 典型手法(与本案观察高度吻合):

    • 白利用(白加黑):正规签名程序本体 + 同目录恶意 DLL 侧加载。本案 aWNCH.exe 带有效签名但版本信息全空,同目录还配有 dtfbase.dll、ipc_core.dll、filter_installer_biz.dll 等组件——经典白加黑结构;

    • BYOVD 内核对抗:滥用带签名的漏洞驱动(本案 ardrv.sys / CVE-2026-36425)进入内核,关闭防护、杀安全软件进程;

    • 加密载荷 + 多重持久化 + 多实例守护(本案的加密数据块与 4 个复活点)。

  • 为什么杀软对它"视而不见":白利用骗过信誉检查、BYOVD 从内核层关掉防护,双重压制下许多终端 AV 全程无感——这也解释了本机 Defender 的"零检出、零排除项"。再次强调:杀软没报 ≠ 干净,签名、情报、外联要交叉验证。

参考:

八、处置与清除

8.1 处置原则

断网 → 留证 → 拆除 → 复核 → 善后

  1. 断网(拔网线 / 关 Wi-Fi):先掐 C2,防止外传和被下发新指令;

  2. 留证:先全量复制副本(本案 robocopy 备份了 3 个目录共 21 个文件 / 127MB),再删除原文件;对外只提供哈希,切勿传播样本;

  3. 提权:清理需要管理员权限(普通账号可用 Start-Process -Verb RunAs 拉起 UAC);

  4. 拆除顺序(重点):删服务(先断复活链)→ 修注册表 → 杀进程 → 删文件。顺序反了的话,刚杀死的进程会被残留服务立刻拉活,删文件永远删不干净;

  5. 复核:重启后对"服务 / 注册表键 / 进程 / 目录 / 网络连接"五项全量复查,确认无复活;

  6. 善后:Windows Defender 脱机扫描(专治内核级残留)+ 重要账号改密(按已泄露处理)+ 上报公司 IT/安全部门 + 观察一周。

8.2 清除脚本做了什么

cleanup_malware.ps1(见 9.3 第三个脚本)严格按上述顺序执行,特点:

  • 全程写日志(时间戳逐条落盘,留证据链);

  • 服务删除不写死名字,而是按 IOC 全量匹配(ardrv|MdudrUu|XUo|aWNCH)——正是这一步扫出了我们此前没发现的内核驱动服务和隐藏计划任务;

  • 删除前先 robocopy 留证;

  • 幂等设计,可安全地在安全模式下重跑。

重启后复查:三个服务"未安装"、注册表零残留、无恶意进程、无 C2、Userinit 正常——无复活,收工。

 

九、脚本分享

说明:以下为当时实际使用版本整理,已做通用化/脱敏(输出目录改为脚本所在目录,人员信息已去除)。三个脚本可分别另存为 .ps1 文件直接使用。 复用到自己的案例时,把脚本里的 IOC 字符串(进程名 / 服务名 / 路径 / IP)替换成你的目标值即可。

9.1 scan_suspects.ps1 —— 只读排查(大部分检查不需要管理员)

八个检查面:A 嫌疑进程详情(含签名/哈希)、A2 CPU TOP25、B 服务匹配、B2 用户可写目录服务(高风险)、C Run 键、C2 启动文件夹、D Defender 排除项与检出历史、E 嫌疑进程外联、F 标准目录之外的进程、G 文件定位、H 计划任务。

# scan_suspects.ps1 - read-only triage. Replace $names with your own suspect list / IOC pattern.
$ErrorActionPreference = 'SilentlyContinue'
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
Write-Output ('=== 0. admin=' + $isAdmin)
$names = @('XUo.exe','aWNCH.exe','shtctky.exe','aha_doctor.exe','FlutterPlugins.exe','qmbrowser.exe','eaio_service.exe','eaio_agent.exe','tposd.exe','tphkload.exe','FMService64.exe','SmartStandby.exe','LITSSvc.exe','WeMail.exe','WeMailNode_x64.exe','igfxCUIServiceN.exe','m365copilot_autostarter.exe','DynamicDependencyLifetimeManagerShadow.exe')
$procs = @(Get-CimInstance Win32_Process | Where-Object { $names -contains $_.Name })
$svcs  = @(Get-CimInstance Win32_Service)
Write-Output ('=== A. suspect processes running now: ' + $procs.Count)
if ($procs.Count -eq 0) { Write-Output '  (none of them are running now)' }
foreach ($p in $procs) {
  Write-Output ('---- ' + $p.Name + ' | PID=' + $p.ProcessId + ' | PPID=' + $p.ParentProcessId)
  $par = Get-CimInstance Win32_Process -Filter ('ProcessId=' + $p.ParentProcessId)
  if ($par) { Write-Output ('  Parent: ' + $par.Name) }
  Write-Output ('  Path : ' + $p.ExecutablePath)
  Write-Output ('  Cmd  : ' + $p.CommandLine)
  foreach ($s in ($svcs | Where-Object { $_.ProcessId -eq $p.ProcessId })) {
    Write-Output ('  Service: ' + $s.Name + ' | ' + $s.PathName + ' | as ' + $s.StartName)
  }
  $fp = $p.ExecutablePath
  if (-not $fp) {
    $s0 = $svcs | Where-Object { $_.ProcessId -eq $p.ProcessId } | Select-Object -First 1
    if ($s0 -and $s0.PathName) {
      if ($s0.PathName -match '^\s*"([^"]+)"') { $fp = $matches[1] }
      elseif ($s0.PathName -match '^\s*(\S+\.exe)') { $fp = $matches[1] }
    }
  }
  if ($fp -and (Test-Path -LiteralPath $fp)) {
    $fi  = Get-Item -LiteralPath $fp
    $sig = Get-AuthenticodeSignature -LiteralPath $fp
    Write-Output ('  Signer : ' + $sig.Status + ' / ' + $sig.SignerCertificate.Subject)
    Write-Output ('  Company: ' + $fi.VersionInfo.CompanyName + ' | ' + $fi.VersionInfo.FileDescription)
    Write-Output ('  SHA256 : ' + (Get-FileHash -LiteralPath $fp -Algorithm SHA256).Hash)
  } else { Write-Output '  (path not readable / no admin)' }
}
Write-Output '=== A2. top 25 CPU now ==='
Get-Process | Sort-Object CPU -Descending | Select-Object -First 25 | ForEach-Object { Write-Output ('  ' + $_.ProcessName + ' pid=' + $_.Id + ' cpu=' + [math]::Round($_.CPU,1) + 's mem=' + [math]::Round($_.WorkingSet64/1MB,0) + 'MB') }
Write-Output '=== B. services matching suspect names ==='
Get-ChildItem 'HKLM:\SYSTEM\CurrentControlSet\Services' | ForEach-Object {
  $ip = (Get-ItemProperty -LiteralPath $_.PSPath).ImagePath
  if ($ip -and $ip -match 'XUo|aWNCH|shtctky|aha_doctor|Flutter|qmbrowser|eaio|tposd|tphkload|FMService64') { Write-Output ('  ' + $_.PSChildName + ' => ' + $ip) }
}
Write-Output '=== B2. services running from user-writable dirs (high risk) ==='
Get-ChildItem 'HKLM:\SYSTEM\CurrentControlSet\Services' | ForEach-Object {
  $ip = (Get-ItemProperty -LiteralPath $_.PSPath).ImagePath
  if ($ip -and $ip -match 'AppData|ProgramData|\\Temp\\|Users\\Public|^\\\\') { Write-Output ('  ' + $_.PSChildName + ' => ' + $ip) }
}
Write-Output '=== C. Run keys ==='
foreach ($rk in @('HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run','HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run','HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run','HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce','HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce')) {
  $props = Get-ItemProperty -LiteralPath $rk
  if ($props) {
    Write-Output ('[' + $rk + ']')
    $props.PSObject.Properties | Where-Object { $_.Name -notlike 'PS*' } | ForEach-Object { Write-Output ('  ' + $_.Name + ' = ' + $_.Value) }
  }
}
Write-Output '=== C2. Startup folders ==='
Get-ChildItem -LiteralPath @('C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup', ($env:APPDATA + '\Microsoft\Windows\Start Menu\Programs\Startup')) | ForEach-Object { Write-Output ('  ' + $_.FullName) }
Write-Output '=== D. Defender exclusions / detections ==='
$mp = Get-MpPreference
Write-Output ('  ExclusionPath: ' + ($mp.ExclusionPath -join ' ; '))
Write-Output ('  ExclusionProcess: ' + ($mp.ExclusionProcess -join ' ; '))
$td = @(Get-MpThreatDetection)
if ($td.Count -gt 0) { $td | Sort-Object InitialDetectionTime -Descending | Select-Object -First 8 | ForEach-Object { Write-Output ('  Threat: ' + $_.ThreatID + ' | ' + ($_.Resources -join ',') + ' | ' + $_.InitialDetectionTime) } } else { Write-Output '  (no detection history, or need admin)' }
Write-Output '=== E. TCP conns of suspect PIDs ==='
$spids = @($procs | ForEach-Object { $_.ProcessId })
if ($spids.Count -gt 0) {
  Get-NetTCPConnection | ForEach-Object { if ($spids -contains $_.OwningProcess) { Write-Output ('  pid=' + $_.OwningProcess + ' -> ' + $_.RemoteAddress + ':' + $_.RemotePort + ' ' + $_.State) } }
}
Write-Output '=== F. processes outside Windows/Program Files ==='
Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -and $_.ExecutablePath -notmatch '(?i)^C:\\(Windows|Program Files|Program Files \(x86\))\\' } | Sort-Object Name | ForEach-Object { Write-Output ('  ' + $_.Name + ' | ' + $_.ExecutablePath) }
Write-Output '=== G. locate XUo/aWNCH/shtctky files ==='
foreach ($r in @('C:\Program Files','C:\Program Files (x86)','C:\ProgramData',($env:LOCALAPPDATA),($env:APPDATA),($env:USERPROFILE + '\AppData\Local\Temp'))) {
  Get-ChildItem -Path $r -Recurse -Include 'XUo.exe','aWNCH.exe','shtctky.exe' -Force | ForEach-Object { Write-Output ('  ' + $_.FullName + ' | ' + $_.LastWriteTime) }
}
Write-Output '=== H. scheduled tasks referencing suspects ==='
Get-ScheduledTask | ForEach-Object { $ex = (($_.Actions | ForEach-Object { $_.Execute }) -join '; '); if ($ex -match 'XUo|aWNCH|shtctky|aha_doctor|FlutterPlugins|qmbrowser|eaio|tposd|tphkload') { Write-Output ('  ' + $_.TaskPath + $_.TaskName + ' => ' + $ex) } }
Write-Output '=== DONE ==='

9.2 scan2_evidence.ps1 —— 证据固定(含本案 IOC,复用请替换)

# scan2_evidence.ps1 - evidence capture (case-specific IOCs: service names / IP / paths; replace when reusing)
$ErrorActionPreference='SilentlyContinue'
Write-Output '=== 1. C:\Drivers children ==='
Get-ChildItem 'C:\Drivers' -Force | ForEach-Object { Write-Output ('DIR : ' + $_.Name + ' | ' + $_.LastWriteTime) }
Write-Output '=== 1b. C:\Drivers\MdudrUu content ==='
Get-ChildItem 'C:\Drivers\MdudrUu' -Recurse -Force | ForEach-Object { Write-Output ('  ' + $_.FullName + ' | ' + $_.Length + ' B | created ' + $_.CreationTime + ' | modified ' + $_.LastWriteTime) }
Write-Output '=== 2. suspect file details ==='
foreach ($f in @('C:\Drivers\MdudrUu\aWNCH.exe','C:\Drivers\MdudrUu\XUo.exe')) {
  $i = Get-Item -LiteralPath $f
  if ($i) {
    $sig = Get-AuthenticodeSignature -LiteralPath $f
    Write-Output ($f + ' | ' + $i.Length + ' B | created ' + $i.CreationTime)
    Write-Output ('  sig: ' + $sig.Status + ' / ' + $sig.SignerCertificate.Subject)
    Write-Output ('  company: ' + $i.VersionInfo.CompanyName + ' | ' + $i.VersionInfo.FileDescription)
    Write-Output ('  sha256: ' + (Get-FileHash -LiteralPath $f -Algorithm SHA256).Hash)
  } else { Write-Output ($f + ' NOT FOUND') }
}
Write-Output '=== 3. service metadata LogHelper / RemoteHandler ==='
Get-CimInstance Win32_Service -Filter "Name='LogHelper' or Name='RemoteHandler'" | ForEach-Object {
  Write-Output ('  ' + $_.Name + ' | Display=' + $_.DisplayName + ' | Desc=' + $_.Description + ' | StartMode=' + $_.StartMode + ' | State=' + $_.State + ' | StartAs=' + $_.StartName)
  Write-Output ('    Path=' + $_.PathName)
}
Write-Output '=== 4. all services launched via cmd /c ==='
Get-ChildItem 'HKLM:\SYSTEM\CurrentControlSet\Services' | ForEach-Object {
  $ip = (Get-ItemProperty -LiteralPath $_.PSPath).ImagePath
  if ($ip -and $ip -match 'cmd /c') { Write-Output ('  ' + $_.PSChildName + ' => ' + $ip) }
}
Write-Output '=== 5. processes 5148 / 8384 now ==='
Get-Process -Id 5148,8384 | ForEach-Object { Write-Output ('  ' + $_.ProcessName + ' pid=' + $_.Id + ' cpu=' + [math]::Round($_.CPU,1) + 's started=' + $_.StartTime) }
Write-Output '=== 6. connections to 222.167.35.123 now ==='
Get-NetTCPConnection | Where-Object { $_.RemoteAddress -eq '222.167.35.123' } | ForEach-Object { Write-Output ('  pid=' + $_.OwningProcess + ' ' + $_.LocalAddress + ':' + $_.LocalPort + ' -> ' + $_.RemoteAddress + ':' + $_.RemotePort + ' ' + $_.State) }
Write-Output '=== DONE ==='

 

9.3 cleanup_malware.ps1 —— 清除(必须管理员运行)

 

# cleanup_malware.ps1 - Backdoor removal (case: LogHelper/RemoteHandler + Userinit + drivers payload dir)
# Actions: delete fake services (incl. kernel driver), restore Userinit, kill processes, quarantine + remove payloads.
# MUST run as Administrator. Idempotent: safe to re-run (e.g. again in Safe Mode).
# Log + quarantine are written to the script's own directory.
$ErrorActionPreference = 'SilentlyContinue'
$root = $PSScriptRoot; if (-not $root) { $root = (Get-Location).Path }
$log  = Join-Path $root 'cleanup_log.txt'
$quar = Join-Path $root 'quarantine'
function L($m) { $line = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss') + '  ' + $m; Add-Content -LiteralPath $log -Value $line; Write-Host $line }

L '===== cleanup start ====='
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
L ('isAdmin=' + $isAdmin)
if (-not $isAdmin) { L 'ABORT: not running elevated'; exit 1 }

# ---- 0) before snapshot
L '--- BEFORE ---'
Get-Process -Name XUo,aWNCH | ForEach-Object { L ('proc: ' + $_.ProcessName + ' pid=' + $_.Id + ' path=' + $_.Path) }
$wk = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
L ('Userinit before: ' + (Get-ItemProperty -LiteralPath $wk).Userinit)

# ---- 1) stop & delete fake services (+ any service matching IOC paths)
$svcNames = @('LogHelper','RemoteHandler')
Get-ChildItem 'HKLM:\SYSTEM\CurrentControlSet\Services' | ForEach-Object {
  $ip = (Get-ItemProperty -LiteralPath $_.PSPath).ImagePath
  if ($ip -match '(?i)ardrv|MdudrUu|XUo|aWNCH') { $svcNames += $_.PSChildName }
}
$svcNames | Sort-Object -Unique | ForEach-Object {
  L ('svc stop/delete: ' + $_)
  (sc.exe stop $_ 2>&1) | ForEach-Object { L ('   ' + $_) }
  (sc.exe delete $_ 2>&1) | ForEach-Object { L ('   ' + $_) }
}

# ---- 2) restore Userinit hijack
Set-ItemProperty -LiteralPath $wk -Name Userinit -Value 'C:\Windows\system32\userinit.exe,' -Type String
L ('Userinit after: ' + (Get-ItemProperty -LiteralPath $wk).Userinit)
L ('Shell now: ' + (Get-ItemProperty -LiteralPath $wk).Shell)

# ---- 3) kill malicious processes (by name or by path under C:\Drivers\)
Get-Process | Where-Object { $_.ProcessName -match '(?i)^(XUo|aWNCH)$' -or $_.Path -match '(?i)^C:\\Drivers\\' } | ForEach-Object {
  L ('kill: ' + $_.ProcessName + ' pid=' + $_.Id + ' path=' + $_.Path)
  Stop-Process -Id $_.Id -Force
}
Start-Sleep -Seconds 2
Get-Process -Name XUo,aWNCH | ForEach-Object { L ('STILL ALIVE: ' + $_.ProcessName + ' pid=' + $_.Id) }

# ---- 4) quarantine copy, then delete payload
New-Item -ItemType Directory -Force -Path $quar | Out-Null
$dirs = @('C:\Drivers\MdudrUu','C:\Drivers\93Yb3h','C:\Drivers\oY8Uz')
foreach ($d in $dirs) {
  if (Test-Path -LiteralPath $d) {
    $dst = Join-Path $quar ((Split-Path $d -Leaf) + '_copy')
    robocopy $d $dst /E /COPY:DAT /R:0 /W:0 /NFL /NDL /NJH /NJS | Out-Null
    L ('quarantine copied: ' + $d + ' -> ' + $dst + ' (robocopy=' + $LASTEXITCODE + ')')
  }
}
$targets = @($dirs)
$targets += (Get-ChildItem 'C:\Drivers' -Force | Where-Object { $_.Name -like 'drivers.dat*' } | ForEach-Object { $_.FullName })
foreach ($t in $targets) {
  if (Test-Path -LiteralPath $t) {
    L ('remove: ' + $t)
    Get-ChildItem -LiteralPath $t -Recurse -Force | ForEach-Object { $_.Attributes = 'Normal' }
    (Get-Item -LiteralPath $t -Force).Attributes = 'Normal'
    Remove-Item -LiteralPath $t -Recurse -Force 2>&1 | ForEach-Object { L ('   ' + $_) }
    L ('   still exists: ' + (Test-Path -LiteralPath $t))
  }
}

# ---- 5) other persistence quick checks
L '--- Run keys check ---'
foreach ($k in 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run','HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce','HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run','HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce') {
  $props = Get-ItemProperty -LiteralPath $k
  if ($props) {
    $props.PSObject.Properties | Where-Object { $_.Name -notmatch '^PS' -and ($_.Value -match '(?i)MdudrUu|XUo|aWNCH|ardrv') } | ForEach-Object {
      L ('run entry FOUND: ' + $k + ' :: ' + $_.Name + ' = ' + $_.Value)
      Remove-ItemProperty -LiteralPath $k -Name $_.Name
      L ('run entry removed: ' + $_.Name)
    }
  }
}
L '--- scheduled tasks check ---'
Get-ScheduledTask | ForEach-Object {
  $task = $_
  $task.Actions | ForEach-Object {
    if ($_.Execute -match '(?i)MdudrUu|XUo|aWNCH' -or $_.Arguments -match '(?i)MdudrUu|XUo|aWNCH') {
      L ('task FOUND: ' + $task.TaskName + ' :: ' + $_.Execute + ' ' + $_.Arguments)
      Unregister-ScheduledTask -TaskName $task.TaskName -TaskPath $task.TaskPath -Confirm:$false
      L ('task removed: ' + $task.TaskName)
    }
  }
}
L '--- AppInit / leftover check ---'
L ('AppInit_DLLs = ' + (Get-ItemProperty -LiteralPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows' -Name AppInit_DLLs).AppInit_DLLs)
Get-ChildItem 'C:\Drivers' -Force | ForEach-Object { L ('leftover in C:\Drivers: ' + $_.Name) }

# ---- 6) after verification
L '--- AFTER ---'
Get-Service LogHelper,RemoteHandler | ForEach-Object { L ('svc still: ' + $_.Name + ' ' + $_.Status) }
Get-Process -Name XUo,aWNCH | ForEach-Object { L ('proc still: ' + $_.ProcessName + ' pid=' + $_.Id) }
'C:\Drivers\MdudrUu','C:\Drivers\93Yb3h','C:\Drivers\oY8Uz' | ForEach-Object { L ('path ' + $_ + ' exists=' + (Test-Path -LiteralPath $_)) }
Get-NetTCPConnection | Where-Object { $_.RemoteAddress -eq '222.167.35.123' } | ForEach-Object { L ('C2 conn still: pid=' + $_.OwningProcess + ' ' + $_.State) }
L '===== cleanup finished ====='

  

十、踩坑清单(PowerShell 5.1 特供)

  1. -f 格式化字符串遇 null 直接抛异常,整条输出丢失。排查中间产物里如果有 null 字段(比如读不到的进程路径),'{0}' -f $null 会把整行炸掉导致假数据丢失。改用字符串拼接 + null 判断;

  2. 中文字符的 .ps1 在 GBK 控制台输出乱码(UTF-8 无 BOM 保存的脚本)。应急排查脚本建议全 ASCII 标签,中文只写在终端外的文档里;

  3. sc 在 PowerShell 里是 Set-Content 的别名!删服务必须写 sc.exe,否则命令静默跑偏;

  4. 非管理员读不到 SYSTEM 进程的路径——用服务 PathName/注册表 ImagePath 反查绕过;

  5. 文件读取偶发失败 ≠ 文件不存在(被拦截/占用/保护),换时机或提权重试;

  6. 服务显示"已停止" ≠ 目标进程停了——cmd /c ... start 链式启动的服务拉起进程后就退出了;

  7. 只看 CPU 会漏掉"低消耗纯控制型"进程——至少 CPU + 内存 + 会话归属 + 父进程 + 外联 多维交叉。

十一、IOC 附录

类型

内容

恶意目录

C:\Drivers\MdudrUu\(Hidden+System;XUo.exe、aWNCH.exe 各 110,208B,ardrv.sys,dtfbase.dll、ipc_core.dll、filter_installer_biz.dll 及加密数据块)

样本哈希

aWNCH.exe SHA256 = B5AE5E8325AB0D35EFA8F0C6B9B50702EEDE7F32EDEDE7516CDFDFE3D42951C6

伪装服务

LogHelper(cmd /c 启动 aWNCH.exe)、RemoteHandler(cmd /c 启动 XUo.exe)、内核驱动服务 ardrv

注册表

HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon → Userinit 被追加 ,C:\Drivers\MdudrUu\aWNCH.exe.,

计划任务

LogHelper → C:\Drivers\MdudrUu\aWNCH.exe

C2

222.167.35.123:443(aWNCH.exe 长连接 + 多次 TimeWait 回连)

投放器

C:\Drivers\93Yb3h\installer.exe(47MB,伪装为"素材包",内含演示视频/图片/音频做掩护;创建时间 2026-07-16)

关联文件

C:\Drivers\oY8Uz\*.cnt、C:\Drivers\drivers.dat*(运行日志,排查当天仍在更新)

关联漏洞

CVE-2026-36425(OPSWAT ARD 驱动,BYOVD)

检出名

Backdoor/Lotok.kw(Microsoft Defender;国内通称「银狐」家族)

 

十二、快速自查清单

# 1) Userinit 是否被加料(正常值:C:\Windows\system32\userinit.exe, )
(Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon').Userinit

# 2) 有没有 cmd /c 链式启动的可疑服务
Get-ChildItem 'HKLM:\SYSTEM\CurrentControlSet\Services' | ForEach-Object {
  $ip = (Get-ItemProperty -LiteralPath $_.PSPath).ImagePath
  if ($ip -and $ip -match 'cmd /c') { $_.PSChildName + ' => ' + $ip }
}

# 3) 有没有服务从用户可写目录(AppData/ProgramData/Temp)运行
Get-ChildItem 'HKLM:\SYSTEM\CurrentControlSet\Services' | ForEach-Object {
  $ip = (Get-ItemProperty -LiteralPath $_.PSPath).ImagePath
  if ($ip -and $ip -match 'AppData|ProgramData|\\Temp\\|Users\\Public') { $_.PSChildName + ' => ' + $ip }
}

# 4) 有没有外联可疑 IP 的进程(把可疑 IP 换成你要查的)
Get-NetTCPConnection -RemoteAddress 222.167.35.123 -ErrorAction SilentlyContinue |
  Select-Object OwningProcess, State

# 5) 盘根目录下有没有陌生的自建文件夹(如 C:\Drivers、C:\Tools 之类非系统目录),里面有 exe 的更要警惕
Get-ChildItem 'C:\' -Directory -Force
 
posted on 2026-10-13 14:45  淮竹i  阅读(32)  评论(0)    收藏  举报