<!--
发布小贴士(不会显示在正文中):
- 本文配图共 3 张,均在同目录 images/ 下:cover.png(头图)、screenshot_ui.png(工具真实界面)、timeline.png(开发时间线)。
- 本地 Markdown 编辑器(Typora / VSCode)可直接预览;发布到掘金 / 知乎 / 公众号等平台时,
将 3 张图片手动上传,并把文中 images/xxx.png 替换为上传后的图片链接即可。
-->
每隔一两分钟,屏幕角落就闪过一两个黑色命令行窗口,快得连标题都没看清,它就消失了。
没有报错,没有声音,只剩一点点说不出的不对劲。
这篇文章记录了我花一个中午抓住这个"幽灵"的全过程:两版失败的脚本、一次漂亮的抓现行、一个意料之外的真相,
以及一个意外留下来、之后可能救我很多次的排查工具。

一、幽灵
事情是上个月的一个中午开始的。
我正在干活,眼角余光里,屏幕右下角忽然闪过两个黑色的窗口——一闪,就没了。起初我以为是幻觉,直到第三次、第四次、第五次……它像上了闹钟一样,每隔一两分钟准时来一趟。弹出来,消失,弹出来,消失。
用过 Windows 的朋友大概都见过这种"黑框"。多数时候它是某个软件在后台偷偷执行命令:检查更新、同步数据、跑个脚本。窗口本身无害,烦人的是那种感觉——你的电脑里有什么东西在活动,而你不知道它是谁。
这就跟半夜听见天花板上有脚步声一个性质。声音不大,但你再也没法专心干别的事了。
行,那就把它揪出来。
二、第一轮:翻遍每个角落,一无所获
我的第一反应很"教科书":这种定时弹窗,八成来自自启动项或者计划任务。于是我翻开注册表、启动文件夹和任务计划程序,把每个角落都查了一遍:
- 注册表 Run 键里翻出 17 个自启动项,其中"雷神 AI 下载器"(
downtool.exe)看着最可疑; - 计划任务里有几个高频选手:金山打字通的
TypeEasyNotify(每小时)、抖音守护LaunchDouyinGuard、WPS 更新、夸克更新…… - 连 WMI 永久事件订阅都查了——这是恶意软件最爱藏的地方,所幸干干净净。
我把嫌疑人排成一排挨个审问,结果发现一个尴尬的事实:他们都有不在场证明。雷神下载器不常驻,计划任务们最快的也是每小时一次,没有一个能解释"每分钟弹一次"的节奏。
静态排查走不通。那就换个思路——蹲点抓现行。黑框不是会弹吗?我就盯着进程列表,看它弹出的那一刻,是谁把它拉起来的。
我自信满满地写下了第一版脚本。
三、第一版脚本:两颗子弹全打歪(13:47)
第一版用的是 PowerShell + WMI 事件订阅,思路很漂亮:注册 Win32_ProcessStartTrace 事件,系统里任何进程创建的瞬间都会通知我,我过滤出 cmd 相关的进程,再回查它的父进程。理论上,这是最优雅的方案——不用轮询,绝不漏抓。
$query = "SELECT * FROM Win32_ProcessStartTrace"
Register-CimIndicationEvent -ClassName Win32_ProcessStartTrace -Query $query -SourceIdentifier "CmdWatch"
# 过滤出命令行相关进程
if ($procName -match '^(cmd|conhost|powershell|pwsh|cscript|wscript)$') { ... }跑起来之后,我盯着屏幕等黑框,等来的却是日志文件里满屏的天书:
==== watch_cmd 鍚姩浜?2026-08-14 13:47:47 ====
绛夊緟 cmd / conhost 杩涚▼鍒涘缓浜嬩欢... (鎸?Ctrl+C 閫€鍑?第一个 bug:编码。脚本用 UTF-8 写日志,Windows PowerShell 却按 GBK 去读,"启动于"就成了"鍚姩浜"。这是 Windows 中文环境经久不衰的经典坑,坑了一代又一代脚本小子,那天中午它坑到了我。
但这还不是最致命的。真正让我后背发凉的是我复盘时才意识到的第二个 bug——看上面那段正则:
if ($procName -match '^(cmd|conhost|powershell|pwsh|cscript|wscript)$')WMI 报上来的进程名是带后缀的:cmd.exe、conhost.exe。而我这个正则要求"整串精确匹配",没写 .exe。
也就是说,这版脚本从第一秒起,就注定一条事件都匹配不上。乱码只是遮住了它的尸体。
13:47 写完,13:49 我就放弃了它。
四、第二版脚本:两分钟,颗粒无收(13:49)
痛定思痛,我换了个笨办法:不订阅事件了,直接轮询。写了个 quick_watch.ps1,每 700 毫秒查一次系统里的 cmd.exe,用"PID + 创建时间"去重,跑满 120 秒收工。
while ((Get-Date) -lt $end) {
Get-CimInstance Win32_Process -Filter "Name='cmd.exe'" | ForEach-Object { ... }
Start-Sleep -Milliseconds 700
}两分钟后,我打开了日志:
==== quick_watch 13:49:41 ====
==== done 13:51:41 ====就这两行。起跑线,终点线。中间什么都没有。
那两分钟里黑框明明又弹了好几次。一个"监控脚本"蹲了整整两分钟,连猎物的毛都没捡到一根,说出去都丢人。
五、停下来:为什么抓不到(13:51)
我当时很想直接上第三版,但两次失败让我学乖了——先别写代码,先回答"为什么抓不到"。
坐下来画了画时间线,问题其实清清楚楚,一共三层:
- 猎物认错了。我一直盯着
cmd.exe,可黑框弹出的那一下,真正被创建的是conhost.exe——Windows 的控制台窗口宿主。cmd 是执行命令的大脑,conhost 是显示窗口的躯壳,那个"一闪而过"的,恰恰是躯壳。第二版脚本就算跑一整天也白搭,因为它压根没把 conhost 列入监视名单。 - 快门速度不够。黑框从出生到自杀,存活时间不到一秒。700 毫秒的轮询间隔,约等于用一秒的快门去拍一道闪电,拍不到才是常态。
- 该用趁手的家伙了。PowerShell 的 CIM 查询每轮开销不小,事件订阅又会丢事件。要在亚秒级窗口里稳定抓捕,我需要一个高频、轻量、完全由自己掌控的轮询器。
想明白这三点,方案几乎是自己浮出来的:用 Python + psutil,400 毫秒一轮,同时盯住 cmd 和 conhost,抓住后立刻回溯父进程。
六、第三版:给它造一双眼睛(13:54)
这版工具(cmd_monitor.py)是和 AI 结对着写的,从说清需求到跑通,前后不过几分钟——毕竟前两版的失败已经把需求锤打得无比清晰。核心逻辑简单到有点朴素:
WATCH_NAMES = {"cmd.exe", "conhost.exe"} # 上次的教训:两个都要盯
def scan_once(self):
for proc in psutil.process_iter(attrs=["name", "pid", "ppid", ...]):
if name not in WATCH_NAMES: continue
if pid in self.seen_pids: continue # 去重:每个 PID 只报一次
self.seen_pids.add(pid)
parent = psutil.Process(proc.info["ppid"]) # 关键一步:回溯父进程
... # 记下父进程的名称、路径、完整命令行每 400 毫秒扫一遍全系统进程,用 seen_pids 集合去重;一旦发现新面孔,立刻抓住它的父进程,把"谁、从哪来、要干什么"(父进程名、可执行路径、完整命令行)全部记录在案。再套上一个 PyQt5 的实时表格界面:拦截到的事件标红、白名单放行的标灰、所有事件落盘成 JSON 日志。
顺手还加了两个"道德开关":
- 白名单:
explorer.exe、Windows Terminal、VSCode 这些拉起的 cmd,是我自己的操作,看都不用看,直接放行; - 拦截模式:勾选后,凡是黑名单父进程(抖音守护、WPS 启动器、各类 updater……)拉起的 cmd,出生即终结。
13:54,工具就绪。我把它开在桌面上,然后——什么都不做,静静等。
七、抓现行!
它没让我等太久。
运行一分钟,表格里跳出了 33 条事件。元凶就大剌剌地躺在"父进程"那一列,出现得最多,像一个惯犯反复路过案发现场:

verge-mihomo.exe。
每隔约 20 秒,它就拉起一个 conhost,而所有这些黑框的父进程命令行都整整齐齐地写着同一串:
\??\C:\Windows\system32\conhost.exe 0x4铁证如山。表格里还有几个陪跑的:华硕奥创中心的助手进程、某个远程终端,以及我自己的排查工具——对,抓捕行动本身也会产生 cmd 事件,工具连"我自己正在抓你"这件事都如实记录了。这种诚实的笨拙,反而让人踏实。
那一刻的心情怎么说呢——像在自家墙壁的夹层里,用手电筒照见了一双眼睛。又惊又喜,还有点想笑。
八、真相:原来是你
顺着 verge-mihomo.exe 这个名字摸过去,我愣住了。
这是我自己的代理工具,Clash Verge 的内核。凶手不是什么流氓软件,是我亲手装、天天用的自己人。
翻开它的配置文件,整条作案链路清清楚楚:
# verge.yaml —— TUN 模式开着,系统代理关着
enable_tun_mode: true
enable_system_proxy: false
# clash-verge.yaml —— 问题就出在这一行
tun:
auto-route: true
auto-detect-interface: true # ← 每 20 秒探测一次默认网卡机制是这样的:TUN 模式要接管全系统的流量,就得通过它随装的系统服务去配置路由表和 DNS;而 auto-detect-interface: true 会让它每 20 秒重新探测一次默认网卡、重设路由。这个探测动作在 Windows 上的表现形式,就是拉起一个 conhost 执行命令——黑框闪现,任务完成,窗口自尽。周而复始。
而它之所以"敢"明目张胆地弹窗,是因为我这版内核比较老——新版本早就改用无窗口方式调用系统命令了。
所以最终结论有点无厘头:不是病毒,不是流氓软件,是我自己的代理工具太勤快。
九、杀,还是不杀?
抓到元凶后,我面前有两个选择。
讽刺的是,我那个兴冲冲做出来的"拦截模式"恰恰是不该用的那个。理由在排查报告里我写得毫不留情:
- 工具拦截的是
cmd.exe,而本案黑框是conhost.exe——掐死躯壳,大脑里的命令照样执行,治标不治本; - 更糟的是,TUN 的路由探测被中途掐断,网络可能断掉几秒再重连。为了眼不见心不烦,换来间歇性断网,亏大了。
真正该做的是两件事,各花了一分钟:
- 立刻:Clash Verge 设置里关掉 TUN、打开系统代理——日常场景完全够用,黑框应声消失;
- 长期:升级 Clash Verge 到新版本,新内核不再弹窗;实在要用 TUN,就把
auto-detect-interface关掉、手动指定网卡。
这个取舍让我印象很深:工具能帮你定位凶手,但处置要讲武德。对系统级的程序挥刀,伤的往往是自己。拦截模式后来留给谁用?留给抖音守护、打字通后台、WPS 启动器这些真正的"惯犯"——黑名单里那几个名字,个个都是有案底的。
十、这个工具:cmd_monitor
排查结束后,工具没有删,转正成了我机里的常备"法医箱"。
| 能力 | 说明 |
|---|---|
| 实时监控 | 400ms 高频捕获所有新建的 cmd.exe / conhost.exe |
| 父进程回溯 | 记录触发者的进程名、可执行路径、完整命令行 |
| 自动拦截 | 可选;白名单防误杀,黑名单精准打击 |
| 事件日志 | 自动落盘 JSON,每行一条,事后可查 |
| 图形界面 | PyQt5 表格,拦截标红、放行标灰,一键导出 |
| 无依赖降级 | 没有 PyQt5 时自动退回命令行模式 |
一句话概括它的哲学:你不需要知道黑框是谁弹的,你只需要在它弹出的瞬间,问一句"你爸是谁"。
今后任何"自动弹窗"问题——不管是 update 弹、守护弹还是别的什么弹——双击 启动监控工具.bat,等它再弹一次,答案就在表格第一屏里。
十一、22 分钟开发记录
回看那天中午的文件时间戳,整个"造眼睛"的过程被精确地记录了下来:

13:47 第一版 PowerShell,死于乱码和一个没写 .exe 的正则;13:49 第二版,盯错了猎物,两分钟颗粒无收;13:51 停下来复盘十分钟;13:54 第三版 Python 一击命中;13:56 启动脚本;14:06 排查报告定稿;14:09 使用文档收尾。
从第一行代码到工具"毕业",22 分钟。其中最值钱的不是最后那版代码,而是 13:51 那次停下来——想明白"为什么抓不到",比多写十版脚本都有用。
尾声:一些真心话
那次排查给我留下三句话。
第一句:别猜,抓现行。静态排查当然有用,但面对"周期性、瞬时性"的问题,再多的推理都不如一双眼睛。证据会过期,抓到才算数。
第二句:失败是有信息的。第一版告诉我"事件订阅会丢包、编码会骗人",第二版告诉我"猎物不是 cmd 是 conhost"。如果没有那两次失败,我不会想到去盯 conhost,也不会有后来 400 毫秒的轮询设计。失败不是弯路,是排雷。
第三句:最可怕的敌人,往往是自己人。绕了一大圈,揪出来的不是木马不是病毒,是我天天在用的代理工具的一次"勤快"。这大概也是排查工作的真相:绝大多数闹鬼的房子里,都没有鬼。
工具我留下了。下次电脑再闹任何幺蛾子,我会先开它,再开骂。
彩蛋
写这篇博客的当晚,我需要一个 PowerShell 脚本去截自己桌面上的窗口,保存路径里带了个中文文件夹名。
跑起来,报错:GDI+ 中发生一般性错误——一看日志,路径被读成了 cmd宸ュ叿。
UTF-8 写,GBK 读。"cmd工具"变成了"cmd宸ュ叿"。
一个月前那版脚本的死法,一模一样。
我坐在那里笑了半分钟,然后默默把路径改成了纯 ASCII。有些 bug 你修不掉它,你只能记住它——然后再踩一次的时候,笑出声来。
工具与排查报告:cmd_monitor.py、排查报告.md、工具文档.md,就在我的桌面上静静躺着。
如果你也常被"一闪而过的黑框"困扰,欢迎试试这套思路:先盯住它,再问它爸是谁。
评论
抢个沙发还没有评论,来说点什么吧。