简介USBViewer是一款面向IT运维人员、系统管理员及普通用户的U盘使用记录查询工具用于查看计算机上所有曾连接过的USB设备历史信息帮助排查设备问题、审计数据流动或为误删文件恢复提供线索。资源包共4个文件包含可执行程序、HTML格式的说明文档、RTF使用手册以及界面截图整体约547KB体积轻巧便于携带。其中可执行文件用于运行查询说明文档与手册介绍许可信息、系统需求及操作方式截图则直观展示图形化界面。该工具可记录设备ID、制造商、产品名称、序列号等设备信息并保留首次连接、最后连接及每次插拔的时间戳同时支持记录通过USB设备进行的复制、移动、删除等文件操作便于追踪数据流向与潜在安全风险。目前已有878人学习下载适合需要监控USB设备活动、进行系统审计或查找丢失文件的用户参考使用。1. 从一台公用电脑的 USB 痕迹说起USBViewer 到底能查到什么单位里那台放在走廊尽头的公用查询机上周被人插过三个不同型号的 U 盘系统日志里只留下「USB 存储设备已接入」这种含糊记录具体是谁、什么时候、插了多久一概查不到。这种场景下USBViewer 这类 U 盘使用记录查询工具就有了用武之地。它做的事情很聚焦把 Windows 系统里散落在注册表、事件日志、驱动安装记录中的 USB 设备痕迹汇总起来还原出「哪个设备、什么时候接入、什么时候拔出、设备序列号是什么」这条时间线。适合谁用做终端取证的、管公用电脑的、排查数据外带嫌疑的以及单纯想搞清楚自己电脑被插过什么设备的人。它不是杀毒软件也不做拦截只做一件事——把已经发生的 USB 接入行为翻出来给你看。2. USBViewer 的取证原理注册表、事件日志与设备枚举三条线2.1 为什么单看一个地方查不全Windows 对 USB 设备的记录是分散的这是很多人第一次用这类工具时最困惑的地方。设备第一次接入时系统会在注册表USBSTOR键下写入设备型号和序列号同时USB键下会记录设备的 VID厂商 ID和 PID产品 ID如果设备被挂载成盘符MountPoints2里还会留下盘符和卷 GUID 的对应关系。光看USBSTOR只能知道「这个设备来过」但不知道「什么时候来的」——时间信息得去事件日志里找。系统日志里的事件 ID 2003、2004、2100、2102 分别对应设备接入、拔出、驱动安装等动作但事件日志默认只保留一段时间滚动覆盖后就没了。所以一个合格的查询工具必须同时读这三条线缺一条就会漏。USBViewer 的思路就是把这三条线的数据做交叉比对用序列号把注册表记录和事件日志串起来再用 VID/PID 补全设备描述。这样出来的结果才是一份能拿得出手的记录而不是一堆孤立的键值。2.2 注册表里几个关键路径要理解工具读什么先得知道数据在哪。下面这几个路径是 USB 取证的核心我一般会先手动确认一遍再让工具去批量跑# 设备型号与序列号最核心 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USBSTOR # 设备 VID/PID 与首次接入信息 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB # 盘符挂载历史 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\MountPoints2 # 设备安装时间部分系统版本 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClassesUSBSTOR下面的子键名就是设备型号比如DiskVen_KingstonProd_DataTraveler再下一级是序列号。序列号这一层特别重要因为同一型号的 U 盘可能有好几个只有序列号能区分开。USB键下面则是 VID/PID 格式比如VID_0951PID_1666它记录的是设备首次接入时的父设备信息可以用来判断设备是不是原装接口接入的。2.3 事件日志的读取与时间对齐注册表告诉你「来过」事件日志告诉你「什么时候」。USBViewer 会去读Microsoft-Windows-DriverFrameworks-UserMode/Operational这个日志通道里面的事件 ID 2003 是设备接入、2004 是拔出、2100 是设备到达、2102 是设备移除。这些事件的时间戳精确到秒比注册表里的InstallDate只有日期靠谱得多。# 用 Python 读取 USB 相关事件日志的示意 import win32evtlog import xml.etree.ElementTree as ET def read_usb_events(channel, max_records500): 读取指定通道的 USB 事件返回时间与事件 ID 列表 hand win32evtlog.OpenEventLog(None, channel) flags win32evtlog.EVENTLOG_BACKWARDS_READ | win32evtlog.EVENTLOG_SEQUENTIAL_READ events [] while True: records win32evtlog.ReadEventLog(hand, flags, 0) if not records: break for rec in records: # 只保留 USB 设备相关的事件 ID if rec.EventID in (2003, 2004, 2100, 2102): events.append({ time: rec.TimeGenerated.Format(), event_id: rec.EventID, data: rec.StringInserts # 设备描述信息 }) if len(events) max_records: break return events这段代码的逻辑是打开指定日志通道用反向读取的方式从最新事件往前翻只保留 USB 设备相关的四个事件 ID。StringInserts里通常包含设备实例路径可以从中提取序列号。参数max_records用来控制读取量避免在日志很大的机器上卡住。实际用的时候如果事件日志已经被清空或滚动覆盖这一步就只能返回空这时候工具会退回到只用注册表数据并在结果里标注「时间信息缺失」。2.4 设备枚举与去重三条线的数据拿到后还要做去重和合并。同一个设备可能在USBSTOR和USB里都有记录事件日志里也可能出现多次接入。USBViewer 的做法是以序列号为主键做聚合同一个序列号下的所有记录合并成一条首次接入时间取最早的事件最后拔出时间取最晚的事件接入次数累加。如果某个设备只有注册表记录没有事件日志就标注为「时间未知」。# 按序列号聚合设备记录的示意 def merge_records(reg_records, event_records): 将注册表记录与事件日志按序列号合并 merged {} for r in reg_records: sn r[serial] if sn not in merged: merged[sn] { serial: sn, model: r[model], vid_pid: r[vid_pid], first_seen: None, last_seen: None, count: 0 } for e in event_records: sn extract_serial(e[data]) if sn and sn in merged: t e[time] if merged[sn][first_seen] is None or t merged[sn][first_seen]: merged[sn][first_seen] t if merged[sn][last_seen] is None or t merged[sn][last_seen]: merged[sn][last_seen] t merged[sn][count] 1 return list(merged.values())这里的关键是extract_serial函数它要从事件日志的设备实例路径里把序列号抠出来。不同厂商的序列号格式不一样有的带下划线有的带分隔符所以这个函数通常要写好几条正则来兜底。合并后的结果就是一份带时间线的设备清单按首次接入时间排序一眼就能看出哪台设备来得最早、哪台来得最频繁。3. 上手实操从解压到导出第一份 USB 记录报告3.1 运行环境与权限准备USBViewer 是绿色工具解压后直接运行但有一个硬性前提必须以管理员权限启动。原因很简单读HKEY_LOCAL_MACHINE\SYSTEM下的注册表键和系统事件日志都需要管理员权限普通用户权限下工具能打开但读不到数据结果就是一片空白。我一般会右键选择「以管理员身份运行」或者在命令行里用runas提权。# 以管理员权限启动在命令行中 runas /user:Administrator USBViewer.exe # 或者直接右键 exe 文件选择以管理员身份运行另一个要注意的是系统版本。Windows 7 到 Windows 11 都能跑但事件日志的通道名称在 Win7 和 Win10 之后有差异。Win7 用的是Microsoft-Windows-DriverFrameworks-UserMode/OperationalWin10 之后部分事件迁移到了Microsoft-Windows-Kernel-PnP/Configuration。工具一般会自动判断系统版本选择对应通道但如果发现事件日志读出来是空的可以手动检查一下通道是否存在。3.2 扫描与结果解读启动后主界面通常分三个区域设备列表、时间线视图、详情面板。点「扫描」按钮后工具会依次读取注册表和事件日志这个过程在设备多的机器上可能要十几秒。扫描完成后设备列表里每一行代表一个曾经接入过的 USB 存储设备列包括设备型号、序列号、VID/PID、首次接入时间、最后接入时间、接入次数。这里有个容易误读的地方列表里出现的设备不一定都是 U 盘。USBSTOR键下也会记录移动硬盘、读卡器、甚至某些手机以存储模式接入时的记录。判断是不是 U 盘要看设备型号里有没有DiskVen_前缀以及容量信息如果有的话。另外有些设备会显示「序列号不可用」这通常是因为设备本身没有烧录序列号系统用了一个占位符代替这种情况下同一型号的多个设备就无法区分了。3.3 导出报告与字段说明扫描结果可以导出成 CSV 或 HTML 报告。CSV 适合做进一步分析HTML 适合直接存档或打印。导出时我一般会勾选「包含原始注册表路径」这个选项这样每一条记录都能追溯到具体的注册表键方便复核。# 导出的 CSV 字段示例表头 设备型号,序列号,VID,PID,首次接入时间,最后接入时间,接入次数,注册表路径 DiskVen_KingstonProd_DataTraveler,001CC0EC34E1F0C1A7B3,0951,1666,2024-03-12 09:14:22,2024-03-12 17:45:10,3,USBSTOR\DiskVen_Kingston...字段里VID和PID是十六进制0951是某厂商的 VID1666是具体产品型号。首次接入时间和最后接入时间来自事件日志如果事件日志缺失这两列会显示「未知」。接入次数是事件日志里 2003 和 2004 配对统计出来的如果只有接入没有拔出记录比如系统直接断电次数可能会偏少。提示导出的 CSV 用 Excel 打开时序列号列可能被自动转成科学计数法。建议先用记事本打开确认或者在 Excel 里把该列预设为文本格式再导入。3.4 批量处理多台机器的记录如果是排查一个机房或一批公用电脑一台台手动跑效率太低。USBViewer 支持命令行参数可以配合批处理脚本远程或本地批量执行。常见做法是把工具放到共享目录用psexec或计划任务在每台机器上跑一遍把导出的 CSV 按机器名命名后汇总。# 批量扫描并导出结果按机器名保存 for /f %%i in (computers.txt) do ( psexec \\%%i -s -d C:\Tools\USBViewer.exe /scan /export:C:\Logs\usb_%%i.csv )这段脚本的逻辑是从computers.txt里逐行读取机器名用psexec以系统权限在远程机器上启动 USBViewer扫描后把结果导出到本地日志目录文件名带上机器名以便区分。-s参数表示以系统权限运行-d表示不等待远程进程结束。实际用的时候要注意远程机器得开启 admin$ 共享和远程注册表服务否则psexec会连不上。4. 避坑与排查USBViewer 使用中的五个血泪教训4.1 扫描结果为空但明明插过 U 盘现象工具以管理员身份运行扫描后设备列表一条记录都没有但用户确信这台机器插过 U 盘。原因最常见的是系统被清理过。某些优化工具或安全软件会定期清理注册表里的 USB 记录USBSTOR键被清空后工具自然读不到。另一种可能是 U 盘接入时没有安装存储驱动比如只充电的数据线这种情况下系统不会在USBSTOR下写记录。解决先手动打开注册表编辑器定位到USBSTOR键看下面有没有子键。如果确实是空的说明记录已被清理只能从事件日志里找残留信息。如果事件日志也被清了那就没有后悔药了。所以重要机器建议定期导出 USB 记录存档别等出事再查。4.2 时间显示为「未知」或明显不对现象设备列表里能看到型号和序列号但首次接入时间和最后接入时间都是「未知」或者显示的时间跟实际使用时间差了好几天。原因时间信息来自事件日志如果日志通道被禁用、日志文件被覆盖、或者系统时间被改过时间就会缺失或错乱。另外Win7 和 Win10 的事件通道不同工具如果没正确识别系统版本也会读不到时间。解决先检查事件查看器里Microsoft-Windows-DriverFrameworks-UserMode/Operational通道是否存在且已启用。如果通道存在但没数据说明日志已被滚动覆盖。这种情况下只能接受「时间未知」的结果或者从其他来源比如文件系统的最后访问时间做旁证。系统时间被改过的情况比较麻烦需要结合其他日志做时间线校正。4.3 同一型号多个 U 盘无法区分现象查出来好几条记录设备型号完全一样序列号列显示「不可用」或一串相同的占位符分不清哪个是哪个。原因部分廉价 U 盘主控没有烧录唯一序列号系统在枚举时用了一个固定的占位符比如000000000000。这种情况下同一型号的所有设备在注册表里看起来一模一样。解决这是硬件层面的限制软件无法突破。能做的补救是结合MountPoints2里的盘符挂载记录和事件日志的时间戳做交叉推断——比如两个设备接入时间不同可以按时间区分。但如果两个设备同时接入过那就真的分不开了。采购 U 盘时尽量选有唯一序列号的型号能省掉很多麻烦。4.4 工具被杀毒软件拦截或删除现象解压后 exe 文件被安全软件直接隔离或者运行到一半被终止。原因USB 记录查询工具的行为特征读注册表敏感键、读事件日志、枚举设备跟某些恶意软件有重叠启发式引擎容易误报。这不是工具本身有问题而是行为特征撞上了。解决在安全软件里把工具目录加入白名单或者临时关闭实时防护后再运行。如果是在单位环境里最好先跟安全管理员报备避免触发终端管控告警。运行完后记得把白名单移除别一直留着。4.5 导出的 CSV 在 Excel 里乱码现象CSV 文件用 Excel 打开后中文变成乱码或者所有内容挤在一列里。原因工具导出的 CSV 默认是 UTF-8 编码而 Excel 在中文系统下默认用 GBK 打开编码不匹配就乱码。另外如果字段里包含逗号但没有正确转义也会导致列错位。解决用记事本打开 CSV另存为时把编码改成 ANSI 或 GBK再用 Excel 打开。或者用 Excel 的「数据」→「从文本/CSV」导入功能手动指定 UTF-8 编码和逗号分隔符。如果字段里有逗号检查工具是否有转义选项没有的话只能导入后手动调整列。5. 进阶技巧把 USB 记录做成可检索的时间线5.1 用 SQLite 建一个 USB 记录库单次导出的 CSV 只能看一台机器的一次扫描结果如果要做长期监控或多机对比最好把数据落到数据库里。我一般用 SQLite 建一张表把每次扫描的结果插进去加一个scan_time字段标记扫描批次。这样后面要查「某个序列号在哪些机器上出现过」或者「某台机器在某个时间段内插过哪些设备」一句 SQL 就能搞定。-- 建表语句 CREATE TABLE usb_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, machine_name TEXT NOT NULL, scan_time TEXT NOT NULL, model TEXT, serial TEXT, vid TEXT, pid TEXT, first_seen TEXT, last_seen TEXT, access_count INTEGER, reg_path TEXT ); -- 查询某个序列号出现过的所有机器 SELECT DISTINCT machine_name, first_seen FROM usb_records WHERE serial 001CC0EC34E1F0C1A7B3 ORDER BY first_seen; -- 查询某台机器在指定时间段内的 USB 接入记录 SELECT model, serial, first_seen, last_seen FROM usb_records WHERE machine_name PC-001 AND first_seen BETWEEN 2024-03-01 AND 2024-03-31 ORDER BY first_seen;第一条查询用来追踪一个特定 U 盘在哪些机器上用过做横向排查时特别有用。第二条查询用来还原某台机器在某段时间内的设备接入情况做时间线分析。scan_time字段虽然不参与查询但保留它能让你知道每条记录是什么时候采集的避免把不同时期的扫描结果混在一起。5.2 用 Python 做时间线可视化数据落到 SQLite 后可以用 Python 的 matplotlib 画一条时间线把每台设备的接入区间画成横条一眼就能看出哪些设备在时间上有重叠。这个图在写报告的时候比表格直观得多。import sqlite3 import matplotlib.pyplot as plt from datetime import datetime def plot_timeline(db_path, machine_name): 绘制指定机器的 USB 设备接入时间线 conn sqlite3.connect(db_path) cur conn.cursor() cur.execute( SELECT model, serial, first_seen, last_seen FROM usb_records WHERE machine_name ? AND first_seen IS NOT NULL ORDER BY first_seen , (machine_name,)) rows cur.fetchall() conn.close() fig, ax plt.subplots(figsize(12, 6)) for i, (model, serial, first, last) in enumerate(rows): start datetime.strptime(first, %Y-%m-%d %H:%M:%S) end datetime.strptime(last, %Y-%m-%d %H:%M:%S) if last else start ax.barh(i, (end - start).total_seconds() / 3600, leftstart, height0.5) ax.text(start, i, f{model[:20]} ({serial[:8]}), vacenter, fontsize8) ax.set_xlabel(时间) ax.set_ylabel(设备序号) ax.set_title(f{machine_name} USB 设备接入时间线) plt.tight_layout() plt.savefig(ftimeline_{machine_name}.png, dpi150) plt.show()这段代码从 SQLite 里读出指定机器的所有设备记录用barh画水平条横轴是时间纵轴是设备序号。每个条上标注设备型号和序列号前八位。first_seen和last_seen之间的时长就是设备的大致接入时长。如果last_seen为空只有接入没有拔出记录就按零时长处理画成一个点。生成的图可以直接贴进报告里比纯文字描述有说服力。5.3 一个我踩过的坑别把扫描结果当实时监控最后说一个我自己的教训。有段时间我想用 USBViewer 做实时监控写了个脚本每隔五分钟跑一次扫描结果发现同一个设备在列表里反复出现接入次数一直在涨。后来才想明白每次扫描都是重新读一遍注册表和事件日志事件日志里的历史事件会被重复统计接入次数自然越滚越大。USBViewer 是事后取证工具不是实时监控工具它的定位是「查已经发生的事」不是「盯着正在发生的事」。要做实时监控得用文件系统过滤驱动或者 WMI 事件订阅那是另一个层面的东西了。从那以后我每次用 USBViewer都只把它当快照工具用需要的时候跑一次导出结果存档需要对比的时候拿两次快照做 diff。这样数据干净结论也可靠。希望这个经验能帮你少走一段弯路。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?