這是一篇兼具警示意義的個人環境事件報告。
同樣的手法,會直接略過那些「已經安裝 Defender 就沒事」的環境。
文章末尾附有一份可用管理員 PowerShell 在 5 分鐘內跑完的檢查清單,沒印象被動過的人也建議跑一次。
先坦白自己的問題。其實幾天前就覺得怪怪的。
到了當天,我有一套從 Discord 操作 Claude Code 的環境,其中有一個 會用 VOICEVOX 唸出回應的自製 Bot。這個 Bot 突然變得不穩定。
就在前不久,我才請 Claude 做了 session 復原相關的功能實作。所以我對它說:
「大概是你前面改了那些東西,結果語音朗讀 bot 壞掉了。請幫我讓它們能共存。」
我完全以為是 AI 的錯。 腦中根本沒出現過病毒這個字。
以下是 Claude 實際做了什麼。
對我提出的指控是「你改了東西,害 Bot 掛掉」。但我只動了 ~/.claude/hooks/ 底下的腳本和 slash command,沒有碰語音朗讀 Bot 的程式碼一行。 因果關係說不通,所以我沒有先下結論,而是先看 Bot 的狀態。
程序還活著。
Get-CimInstance Win32_Process -Filter "Name='pythonw.exe'"
# → C:\Users\<使用者名稱>\Desktop\yomiage-bot\bot.py 正在執行
明明說掛了,程序卻還在。這時我發現「症狀的描述」和「實際發生的事」不一致。 於是查看日誌。
WARNING discord.gateway Shard ID None heartbeat blocked for more than 10 seconds.
WARNING discord.gateway Can't keep up, shard ID None websocket is 28.0s behind.
INFO discord.gateway Shard ID None session has been invalidated.
這不是程式碼本身的問題。 heartbeat 被阻塞、websocket 落後 28 秒,這通常代表 程序根本分不到 CPU 時間。這種情況不可能是我那次修改造成的。
我又看了時間序列。
21:58 登入成功,成功連上語音頻道
22:01 正常完成唸出
22:14 合成失敗並重試
22:24 連線失敗連續 30 次
22:33 heartbeat blocked
21:58 時一切都正常,22:14 之後才開始壞掉。 我寫檔的時間雖然接近,但寫的檔案和 Bot 無關。 只因為時間接近就認定兇手,這很像常見的誤判。
我判斷原因在 CPU 端,便往那邊查。
先看整體。
Get-CimInstance Win32_Processor | Measure-Object -Property LoadPercentage -Average
# → 100 %
100% 滿載。 這時候如果直接去看工作管理員最上面那個,很容易看錯。因為顯示的 CPU% 是瞬間值,會跳動,所以要用一段時間內 CPU 時間的增量來量。
$snap1 = Get-Process | Select-Object Id, ProcessName, CPU
Start-Sleep -Seconds 5
$snap2 = Get-Process | Select-Object Id, ProcessName, CPU
# 以 Id 為 key 計算 CPU 差分並排序
結果如下。
Id Name CPU增量秒
-- ---- ------
22072 updater 75.55 ← 5 秒內 75.55 秒 = 吃掉 15 核心
2024 herdr 1.16
31400 Discord 1.12
只有一個程序在 5 秒內用了 75 秒的 CPU 時間。 到這一步,「只是剛好很多重工作同時在跑」這個可能性就可以排除了。單一程序持續占用 15 個核心,並不是一般應用程式的行為。
程序名稱是 updater.exe。一眼看起來就像某種「更新程式」的名字。 這種時候如果相信名字,就輸了。
updater.exe 是提升權限程序,正常權限下甚至連可執行檔路徑都讀不到。既然有些資訊讀不到,那就放棄它,改收集所有能讀到的資訊。
tasklist /FI "PID eq 22072" /V /FO LIST
Image Name: updater.exe
Status: Not Responding
CPU Time: 9:54:38
Window Title: XMRig 6.21.3 ← 這裡
視窗標題寫著 XMRig 6.21.3。
XMRig 是 Monero 的挖礦軟體,本身是合法的 OSS,但也常被未授權挖礦惡意程式拿來使用。它用 updater.exe 的名字偽裝,但視窗標題沒有藏。
我還不會直接下定論,先取執行證據。
Get-NetTCPConnection -State Established | Where-Object { $_.OwningProcess -eq 22072 }
# → RemoteAddress 15.235.234.199 RemotePort 3333
3333 是挖礦池常見的連接埠。 它正在連到外部挖礦池,也就是說此刻仍在挖。觀測到的累計 CPU 時間已達 9 小時 54 分(程序啟動後 50 分鐘時)。
到這裡就確認了。 名稱、視窗標題、CPU 消耗、連線目標四者同時成立,才足以判定為「惡意程式」。
只停掉程序沒用,重開機後還會復活。所以要往上追它是從哪裡啟動的。
claude 看到的父子關係:
svchost.exe (Task Scheduler)
└ update.exe
└ updater.exe (XMRig)
起點是排程工作。這裡的關鍵不是找「名字可疑的工作」,而是找「執行路徑可疑的工作」;這就是這次能抓到的分水嶺。
Get-ScheduledTask | ForEach-Object {
$t = $_
foreach ($a in $t.Actions) {
if ($a.Execute -match 'AppData|\\Temp\\|\\Public\\') {
[pscustomobject]@{ 狀態=$t.State; 工作=($t.TaskPath+$t.TaskName); 執行=$a.Execute; 權限=$t.Principal.RunLevel }
}
}
} | Format-List
結果是這些:
[執行中] \Microsoft\Windows\Shell\FamilySafetyRefreshingTask
→ C:\Users\<使用者名稱>\AppData\Local\Microsoft\Edge\System\update.exe
[執行中] \Microsoft\Windows\Multimedia\SystemRecordService
→ C:\Users\<使用者名稱>\AppData\Roaming\DriversUpdate\Core\UpdateCoreDrivers.exe
[待機] \Microsoft\Windows\USB\Usb-Notification
→ C:\Users\<使用者名稱>\AppData\Roaming\DriversUpdate\Runtime_Broker.exe
[待機] \Microsoft\Windows\MUI\RPRemove
→ C:\Users\<使用者名稱>\AppData\Roaming\Microsoft\Crypto\CRC\Runtime.exe /verysilent
[待機] \Microsoft\Windows\MUI\FPRemove
→ C:\Users\<使用者名稱>\AppData\Roaming\DriversUpdate\taskhostupdate.exe
這 5 個全都偽裝成 Windows 的正規工作名稱與階層。 而且全部都以「最高權限」執行。
真正的 FamilySafetyRefreshingTask 應該指向 Windows 系統資料夾,絕不會指向 AppData。
不能靠工作名稱判斷,必須看執行路徑。
檔案放置位置也用了同樣的偽裝方式。
C:\Users\<使用者名稱>\AppData\Local\Microsoft\Edge\System\ ← 偽裝成 Edge 資料夾
updater.exe 6.36MB ← XMRig 本體
update.exe 12.8KB ← 啟動器
SearchUI.exe 509KB
WinRing0x64.sys 14.5KB ← 核心模式驅動程式
C:\Users\<使用者名稱>\AppData\Roaming\DriversUpdate\
core.exe / SQLite.Interop.dll / Core\UpdateCoreDrivers.exe / RuntimeBroker.exe
C:\Users\<使用者名稱>\AppData\Roaming\Microsoft\Crypto\CRC\
Runtime.exe
所有檔案都在同一天 18:21 建立,而且全部沒有數位簽章。
另外要特別注意的是,Edge 和 Microsoft\Crypto 都是 Windows 真正存在的資料夾。 惡意程式是把自己的資料夾藏在它們底下,因此刪除時如果末層資料夾名稱看錯,可能會誤刪正版資料。 特別是 Crypto,那是儲存加密金鑰的位置,整個刪掉會弄壞憑證相關功能。
這可能是重點。Defender 的即時保護是開啟的,而且病毒碼也是當天更新。但它仍然漏掉了。
我們請人檢查排除清單(這需要管理員權限,所以必須由人工執行)。
Get-MpPreference | Select-Object -ExpandProperty ExclusionPath
C:\Users\<使用者名稱>\Documents\Celemony\Separations ← 使用者自己加的
C:\Users\<使用者名稱>\AppData\Local\Microsoft\Edge\System ← ★ 惡意程式新增
C:\Users\<使用者名稱>\AppData\Roaming\DriversUpdate ← ★ 惡意程式新增
惡意程式把自己的位置從 Defender 的監控範圍中排除了。
從這件事還能再確認一點:新增排除路徑需要管理員權限。 也就是說,在 18:21 投放時,就已經拿到管理員權限了。
再看偵測記錄,Defender 其實曾經跳過一次警示。
Get-MpThreatDetection
# 2026-08-10 19:46:04 ThreatID=2147937641 file:_C:\Windows\SystemTemp\UDDCD4D.tmp
Get-MpThreat
# VulnerableDriver:WinNT/Winring0 Severity=5 IsActive=False
WinRing0 是 XMRig 用來做 CPU 底層控制與提權的核心模式驅動程式。 它一旦出現在排除資料夾外面(C:\Windows\SystemTemp),Defender 就抓到並處理了。
所以放在磁碟上的 WinRing0x64.sys 其實從未被載入核心。 我們去查實際執行中的驅動程式時,也沒有看到它被登錄。
# 找不到 WinRing0 的服務登錄
Get-ChildItem 'HKLM:\SYSTEM\CurrentControlSet\Services' | Where-Object { $_.PSChildName -match 'WinRing0' }
Defender 有在做事,只是沒人看到通知。
清除不是單純刪掉,而是順序要對。 我安排的順序如下:
1. 刪除偽裝排程工作 ← 先切斷起點,否則刪了又會被重新拉起
2. 停止程序 ← 用路徑辨識(後面會說)
3. 移除 Defender 排除項 ← 這步要先於刪檔,否則又被重建也看不到
4. 刪除檔案
5. 全面掃描
第 3 步一定要在第 4 步之前。 如果排除仍存在就直接刪檔,Defender 看不到的區域還是會留著。 一旦再次被放回去,可能又會像這次一樣 5 天都察覺不到。
'\Microsoft\Windows\Shell\FamilySafetyRefreshingTask',
'\Microsoft\Windows\Multimedia\SystemRecordService',
'\Microsoft\Windows\USB\Usb-Notification',
'\Microsoft\Windows\MUI\RPRemove',
'\Microsoft\Windows\MUI\FPRemove' | ForEach-Object {
$p = Split-Path $_ -Parent; $n = Split-Path $_ -Leaf
Stop-ScheduledTask -TaskPath "$p\" -TaskName $n -EA SilentlyContinue
Unregister-ScheduledTask -TaskPath "$p\" -TaskName $n -Confirm:$false -EA SilentlyContinue
}
Get-CimInstance Win32_Process | Where-Object {
$_.ExecutablePath -match 'AppData\\(Local\\Microsoft\\Edge\\System|Roaming\\DriversUpdate|Roaming\\Microsoft\\Crypto\\CRC)'
} | ForEach-Object {
Write-Output ("停止: PID " + $_.ProcessId + " " + $_.ExecutablePath)
Stop-Process -Id $_.ProcessId -Force
}
這一步做完後,CPU 從 100% 降到 3%。
Remove-MpPreference -ExclusionPath "C:\Users\<使用者名稱>\AppData\Local\Microsoft\Edge\System"
Remove-MpPreference -ExclusionPath "C:\Users\<使用者名稱>\AppData\Roaming\DriversUpdate"
Get-MpPreference | Select-Object -ExpandProperty ExclusionPath # 確認剩餘項目
Remove-Item "C:\Users\<使用者名稱>\AppData\Local\Microsoft\Edge\System" -Recurse -Force
Remove-Item "C:\Users\<使用者名稱>\AppData\Roaming\DriversUpdate" -Recurse -Force
Remove-Item "C:\Users\<使用者名稱>\AppData\Roaming\Microsoft\Crypto\CRC" -Recurse -Force
刪除後,我也確認沒有波及到其他東西。
消失 AppData\Local\Microsoft\Edge\System
消失 AppData\Roaming\DriversUpdate
消失 AppData\Roaming\Microsoft\Crypto\CRC
正常 AppData\Local\Microsoft\Edge ← 正常的 Edge 資料
正常 AppData\Roaming\Microsoft\Crypto ← 金鑰儲存位置
Start-MpScan -ScanType FullScan
步驟 2 的指令,我一開始是用「名稱」來寫的。
# 一開始給出的版本(不夠完整)
Stop-Process -Name updater,update,UpdateCoreDrivers,Runtime,Runtime_Broker,SearchUI -Force
後來我改成「以路徑來鎖定」的版本,結果這個版本抓到了一個我漏看的程序。
停止: PID 27444 C:\Users\<使用者名稱>\AppData\Roaming\DriversUpdate\RuntimeBroker.exe
為什麼會漏看? 因為程序清單裡有 10 個 RuntimeBroker,那是 Windows 正規程序。我看到清單時,以為「RuntimeBroker 有 10 個,都是正常的」,就把它們一起略過了。結果其中 1 個其實是偽物。
正確的看法應該是這樣:
Get-CimInstance Win32_Process -Filter "Name='RuntimeBroker.exe'" |
Select-Object ProcessId, ExecutablePath
真的程序全都位於 C:\Windows\System32\RuntimeBroker.exe。只要有一個出現在別的路徑,那就是答案。
攻擊者對排程工作名稱和程序名稱都用了同樣的偽裝邏輯,而我在其中一邊上當了。 一看到熟悉的名稱就以為「我知道這是什麼」,然後停止追查——我想這是人類和 AI 都很容易掉進去的洞。
低 CPU 潛伏型或竊資型攻擊,光看 CPU 使用率是找不到的。 所以我沒有把前一次結果當前提,而是做了獨立的重新調查。我完全沒有使用「CPU 負載低就代表安全」這種判斷。
項目結果排程工作只有使用者自己安裝的 4 個會從使用者區域執行Run / RunOnce / 啟動資料夾已知應用程式而已服務沒有從 AppData / Temp 啟動的東西WMI 事件訂閱**Consumer / Binding 都是 0 筆WinlogonShell=explorer.exe / Userinit=userinit.exe(預設值)AppInit_DLLs空白,LoadAppInit_DLLs=0IFEO沒有任何 Debugger 劫持執行中程序465 個中,所有不在標準路徑內的 40 個都逐一確認。沒有偽裝或可疑父子關係外部通訊27 個連線、26 個監聽埠,全都是已知應用。沒有像 C2 的連線 Defender 偵測記錄只有 WinRing0 的 1 筆鍵盤/滑鼠過濾驅動程式只有 kbdclass / mouclass
我特別在意、而且認為高度可信的陰性發現有兩項。
① WMI 事件訂閱完全是空的。
Get-CimInstance -Namespace root\Subscription -ClassName CommandLineEventConsumer
Get-CimInstance -Namespace root\Subscription -ClassName ActiveScriptEventConsumer
Get-CimInstance -Namespace root\Subscription -ClassName __FilterToConsumerBinding
這是無檔案持久化的重要藏身處。 這裡是空的,表示沒有那種「檔案刪掉後會從 WMI 再復活」的東西。
② 鍵盤過濾驅動程式是正常值。
因為對方有提到「最近鍵盤偶爾沒反應,不知道是不是惡意程式造成的」,所以我直接針對驅動型 keylogger 做了確認。
$kb = 'HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4d36e96b-e325-11ce-bfc1-08002be10318}'
(Get-ItemProperty $kb).UpperFilters # → 只有 kbdclass(完全正常)
(Get-ItemProperty $kb).LowerFilters # → 空白(正常)
如果有以驅動程式形式存在的 keylogger,這裡一定會看到名稱。結果沒有。 另外也能解釋症狀:被占用 15 個核心整整 5 天,輸入當然會卡。
調查途中,我在 C:\Windows\Temp 找到兩個來路不明、但有簽章的 DLL,先列為「待確認」。
.bdebfb7bce73bfbc-00000000.dll 3,786,120 B 建立 08-11 00:29:53
.bdebfb7bffe75f9c-00000000.dll 3,786,120 B 建立 08-11 00:30:02
之所以懷疑它們, 是因為感染後一天內,兩個同大小檔案相隔 9 秒被放進來,且以 . 開頭的十六進位檔名不是人會取的名字;同時在 Program Files 和 AppData 底下都找不到任何同大小檔案,也就是說它們不是已安裝應用程式的正常檔案。
最後證實完全是誤會。
深入查看簽章後,發行者是 Microsoft ID Verified CS AOC CA 03(Microsoft 用來驗證企業身分的簽章服務),而簽署者則是某個開發工具的供應商。再去查那個工具的 npm 套件安裝時間,和 DLL 建立時間精確到秒完全一致。VirusTotal 也沒有任何檢出。這是使用者當天安裝的 CLI 工具,在安裝過程中解壓到 Temp 的檔案。
這件事我認為值得留下來當教訓。
不能因為是「感染後不久的日期」就直接懷疑。懷疑是對的,但刪除前一定要先確認身分。
憑直覺刪掉正常檔案,才是最大的損失。 對這兩個檔案,我沒有刪除,只保留紀錄並提供給使用者判斷依據;同時也可用 查詢雜湊到 VirusTotal 的方式判斷(不用上傳檔案本身)。結果它們得以保留。
這裡只能寫「未知」。 我把查到的範圍老實列出來。
所以連「原本潛伏後才被啟動」還是「當下才進來」都無法判斷。
(以下是我=人類端的想法)最懷疑的還是 DTM 相關來源。 免費音源、外掛、素材包——這些通常以安裝程式形式散布、要求管理員權限,而且很容易對來源失去警覺。這次也不能排除是從這裡進來的。
但沒有證據,也沒有任何材料能直接點名某個軟體。 所以結論只能是:
入侵途徑不明。最可疑的是 DTM 相關外掛安裝,但沒有足夠證據。
這次的教訓很明確。
Defender 有在工作;沒注意到的是人。
因此我請 Claude 幫我做了一個機制:只要 Defender 偵測到任何東西,就強制把通知送到我手邊。 只要透過事件記錄觸發腳本,再丟到聊天工具即可。單靠 Windows 排程器就能完成。
$subscription = @'
<QueryList><Query Id="0" Path="Microsoft-Windows-Windows Defender/Operational">
<Select Path="Microsoft-Windows-Windows Defender/Operational">
*[System[(EventID=1116 or EventID=1117 or EventID=1006 or EventID=1015)]]
</Select></Query></QueryList>
'@
$class = Get-CimClass -ClassName MSFT_TaskEventTrigger -Namespace Root/Microsoft/Windows/TaskScheduler
$trigger = New-CimInstance -CimClass $class -ClientOnly
$trigger.Subscription = $subscription
$trigger.Enabled = $true
$action = New-ScheduledTaskAction -Execute $pythonw -Argument '"C:\path\to\notify.py"'
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName 'DefenderAlertToDiscord' -TaskPath '\' `
-Action $action -Trigger $trigger -Principal $principal
事件 ID 分別是 1116(偵測)、1117(已處置)、1006(掃描偵測)、1015(可疑行為)。讀取 Defender 的 Operational 記錄需要管理員權限,所以用 SYSTEM 執行。
排程工作直接放在 \ 底下,名稱就取成一看就知道用途。 Claude 的理由是:「這次惡意程式偽裝成 \Microsoft\Windows\... 底下的正規名稱,所以我們就不該再用隱藏的名稱,而應該用明白的名稱放在不隱藏的位置。」我認為很有道理。
挖礦本身不會偷資料。但以相同權限能做任何事,這是事實。
另外,刪除的資料夾裡有 SQLite.Interop.dll(2MB)。挖礦軟體根本不需要 SQLite。 但 Chrome 儲存的密碼、Cookie、自動填入資料全都是 SQLite 資料庫。
我們也請人檢查了 Chrome,沒有發現被攻擊的痕跡。
DevToolsActivePort)即便如此,Claude 還是建議「以已外洩前提處理」。理由是「讀取不會留下痕跡」。 我接受這個建議,所以照做了。
優先順序如下:
第 1 項要先做,這點很重要。 如果拖到後面,即使改了密碼,對方的 session 可能還是活著。
用管理員 PowerShell 直接往下跑即可。就算覺得沒問題,也值得檢查一次。
# 1. Defender 排除清單裡,有沒有不認識的路徑(這次最重要)
Get-MpPreference | Select-Object -ExpandProperty ExclusionPath
Get-MpPreference | Select-Object -ExpandProperty ExclusionProcess
# 2. Defender 過去檢出過什麼(可能有被忽略的警示)
Get-MpThreatDetection | Sort-Object InitialDetectionTime
Get-MpThreat
# 3. 從使用者區域執行的排程工作(即使名稱偽裝,也會從路徑露餡)
Get-ScheduledTask | ForEach-Object { $t=$_
foreach ($a in $t.Actions) {
if ($a.Execute -match 'AppData|\\Temp\\|\\Public\\') {
"{0,-9} {1}{2}`n → {3}" -f $t.State, $t.TaskPath, $t.TaskName, $a.Execute
}
}
}
# 4. 不是從標準路徑執行的程序(看路徑,不看名稱)
Get-CimInstance Win32_Process | Where-Object {
$_.ExecutablePath -and $_.ExecutablePath -notmatch '^C:\\(Windows|Program Files)'
} | Select-Object ProcessId, Name, ExecutablePath | Sort-Object ExecutablePath
# 5. WMI 常駐(無檔案持久化的常見藏身處,空白才正常)
Get-CimInstance -Namespace root\Subscription -ClassName CommandLineEventConsumer
Get-CimInstance -Namespace root\Subscription -ClassName ActiveScriptEventConsumer
# 6. 鍵盤過濾驅動程式(若不是 kbdclass,就要懷疑 keylogger)
(Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4d36e96b-e325-11ce-bfc1-08002be10318}').UpperFilters
重點只有四個。
這段完全是自我反省。
我自己現在很常讓 AI 幫我寫程式,也就是所謂的 vibe coding。好玩,而且真的讓我做事更快。但這種做法會讓你越來越不清楚自己電腦裡多了什麼。
想到的問題有這些:
AppData 底下有執行檔變成「很正常」的事。 很多開發工具都裝在那裡,所以惡意程式混進去也不會顯得突兀攻擊者把位置選在 AppData 很合理。 對現代開發者來說,那裡早就是「有執行檔很正常」的地方。環境越雜,異物越不顯眼。
這不是在呼籲你把安全措施全補齊。只是提醒你偶爾要數一數自己的環境裡到底有什麼。 不需要完全理解每一個項目,但:
「這個程序是什麼、我說不出來」累積太多,本身就是風險。
這次我把 40 個不在標準路徑內的程序一個一個查過,能全部說明清楚,多少是因為最近剛好整理過環境。要是當時有 100 個、而且一半說不出來,那個偽造的 RuntimeBroker 可能還在跑。
上面的檢查清單,5 分鐘就能跑完。 一年做幾次就夠了。特別是:
我很建議你把自己的電腦,暫時當成別人的電腦來看一次。
最後。這次從發現、清除到後續全面檢查,實際作業全部都是 Claude 做的。 我自己做的只有說「Bot 壞了,應該是你搞的」以及執行那些需要管理員權限的命令。
AI 被拿來改環境的風險,大家已經講很多了;但 AI 被拿來查環境的好處,還可以講更多。 465 個程序逐一比對路徑、簽章與父子關係,這種事我自己絕對不會做。
更重要的是,因為我沒有直接接受「都是你的錯吧」這句話,而是去看了實際狀況,才有了發現的起點。
這篇文章只是「普通人出包後,設法復原」的紀錄,不是專家的分析。如果你覺得步驟或判斷有漏洞,歡迎直接指出。
特別是以下資訊,對我和遇到相似情況的讀者都很有幫助:
尤其是入侵途徑,我到現在還是不知道。 如果你也遇過類似狀況,或是知道這種手法通常從哪裡進來,請務必告訴我。
另外,如果你讀完後也去跑了文末的檢查清單,結果有發現東西——也請留言告訴我。只要有一個人跑出異常,這篇文章就有意義。