简介:这是一款面向Windows系统安全分析与数字取证初学者的本地化痕迹检测工具,适用于IT运维人员、网络安全爱好者及计算机取证入门学习者,用于快速梳理个人设备近期使用行为轨迹。资源包共39个文件,含5个核心可执行程序(如RegistryFinder.exe、查看器.exe)、4个批处理脚本(用于注册表上下文菜单增删)、17个图标资源(ico/svg)及配置类文件(config、ini、xml等),整体体积仅4.5MB,轻量便携,无需安装即可运行。已有1660人下载学习,体现了其在实操型取证小工具领域的实用认可。用户可直接获取完整的注册表痕迹解析能力:包括数月乃至数年内的目录/文件访问记录、关键词搜索历史、USB设备接入日志,并附带离线注册表浏览、结果导出、界面定制等配套功能,所有操作均在本地完成,不联网、不写入、不泄露隐私。
1. Windows使用痕迹检测工具:不是“监控软件”,而是你电脑里沉睡的数字考古现场
上周帮某高校实验室复现一个学生提交的毕业设计——他声称自己“没在考试期间打开过任何参考资料”,但导出的浏览器历史、剪贴板记录、最近文档时间戳和 USB 设备插拔日志,四条时间线在凌晨2:17完全重合。这不是玄学,是这套 Windows 使用痕迹检测工具干的。它不依赖远程控制、不写入可疑服务、不挂钩系统关键API,而是像一位沉默的法医,只读取 Windows 自身留下的原始证据链:NTFS $MFT 中的文件创建/访问/修改时间(MAC 时间)、ShellBag 注册表项里的资源管理器路径导航记录、Prefetch 文件中程序启动频次与上次执行时间、JumpList 缓存里的最近打开项目、以及 Event Log 中 Security 和 System 日志里被忽略的登录登出、服务启停、驱动加载事件。它适合安全审计人员做终端合规检查、IT 管理员排查异常操作、取证初学者理解 Windows 证据留存机制,也适合开发者拆解“如何在无管理员权限下尽可能多地采集本地行为痕迹”。这不是一个黑匣子,而是一份可验证、可调试、可裁剪的痕迹采集清单。
2. 架构与模块拆解:六个核心采集器如何协同构建证据时间轴
这套工具采用模块化采集+统一时间归一化+结构化输出的设计思路。所有采集器均以独立 Python 模块实现,通过collector_base.py提供统一接口(collect()方法返回dict),主程序main.py负责调度、去重、按时间戳排序并导出为 JSON 或 CSV。整个流程不写入新文件、不修改注册表、不调用高权限 API(如NtQuerySystemInformation),绝大多数操作普通用户权限即可完成。关键在于:每个模块只负责“拿什么”和“怎么拿”,不负责“怎么用”——这使得二次开发时替换某个采集器(比如把 JumpList 解析换成自定义的 Edge History 解析)几乎不影响其他模块。
2.1 NTFS $MFT 时间分析器:绕过文件系统缓存的真实时间证据
Windows 文件系统会为每个文件/目录在主文件表($MFT)中维护多个时间戳:Created(创建)、Modified(最后修改)、Accessed(最后访问)、Changed(元数据最后变更)。其中 Accessed 时间默认在 Win10 1803+ 后被禁用以提升性能,但 $MFT 记录本身仍存在且未被清除。本工具通过pytsk3库直接读取磁盘镜像或卷设备(如\\.\C:),跳过 Windows API 层,获取原始 $MFT 条目:
# collectors/mft_collector.py import pytsk3 import datetime def collect(drive_path="\\\\.\\C:"): img = pytsk3.Img_Info(drive_path) fs = pytsk3.FS_Info(img) results = [] for file_obj in fs: if not file_obj.info or not file_obj.info.meta: # 跳过无效条目 continue meta = file_obj.info.meta # 注意:pytsk3 返回的是 Unix 时间戳(秒),需转为 datetime created = datetime.datetime.fromtimestamp(meta.crtime, tz=datetime.timezone.utc) modified = datetime.datetime.fromtimestamp(meta.mtime, tz=datetime.timezone.utc) accessed = datetime.datetime.fromtimestamp(meta.atime, tz=datetime.timezone.utc) changed = datetime.datetime.fromtimestamp(meta.ctime, tz=datetime.timezone.utc) results.append({ "path": file_obj.info.name.name.decode('utf-8', errors='ignore'), "type": "file" if file_obj.info.meta.type == pytsk3.TSK_FS_META_TYPE_REG else "dir", "created_utc": created.isoformat(), "modified_utc": modified.isoformat(), "accessed_utc": accessed.isoformat(), "changed_utc": changed.isoformat() }) return results提示:
pytsk3需要管理员权限才能直接访问\\.\C:这类物理设备路径。若无权限,工具自动降级为扫描C:\Users\*下常见目录(如 Desktop、Documents、Downloads)的os.stat()结果——虽丢失 Accessed 时间,但 Modified/Changed 仍具强参考价值。
2.2 ShellBag 注册表解析器:还原你打开过的每一个文件夹路径
Windows 资源管理器为提升导航速度,会将用户访问过的文件夹路径、视图设置、排序方式等缓存在注册表HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Shell\BagMRU及其子键中。这些二进制数据结构复杂,但本工具使用registry_parser(基于python-registry封装)逐层解析:
# collectors/shellbag_collector.py from Registry import Registry import struct def _parse_shellbag_binary(data): """解析 ShellBag 二进制流,提取 Unicode 路径""" if len(data) < 4: return None # ShellBag 头部:2字节签名 + 2字节长度 sig = data[0:2] if sig != b'\x00\x00': # 常见起始标志 return None path_parts = [] offset = 4 while offset < len(data): # 每个路径段以 2 字节长度开头,后跟 UTF-16LE 字符串 seg_len = struct.unpack('<H', data[offset:offset+2])[0] offset += 2 if seg_len == 0 or offset + seg_len * 2 > len(data): break try: seg = data[offset:offset + seg_len * 2].decode('utf-16-le') path_parts.append(seg) offset += seg_len * 2 except (UnicodeDecodeError, struct.error): break return "\\".join(path_parts) if path_parts else None def collect(): reg_path = r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Shell\BagMRU" # 实际代码中通过 winreg 或 Registry 库打开对应 hive # 此处省略打开逻辑,聚焦解析核心 results = [] # 递归遍历 BagMRU 子键,对每个值的数据调用 _parse_shellbag_binary # ... return results逻辑说明:ShellBag 是树状结构,每个节点代表一次导航动作。工具不只提取最终路径,还记录该路径在树中的深度、父节点哈希、以及最后一次访问时间(从LastWriteTime获取),从而还原出“用户从桌面进入 Downloads,再点开某个子文件夹”的完整导航链。
2.3 Prefetch 文件解析器:程序启动行为的冷数据快照
Windows 将常用程序的启动信息缓存在C:\Windows\Prefetch\目录下,文件名形如EXPLORER.EXE-3F5B1A2C.pf。每个.pf文件包含程序路径、加载的 DLL 列表、启动次数、以及最后一次运行时间(精确到毫秒)。本工具使用自研prefetch_parser.py解析:
# collectors/prefetch_collector.py import struct import os import glob def parse_prefetch_header(data): if len(data) < 0x10: return None # Prefetch 格式头部:4字节签名 "SCCA" + 4字节版本 + 4字节文件大小 + 4字节运行次数 sig = data[0:4] if sig != b'SCCA': return None version = struct.unpack('<I', data[4:8])[0] file_size = struct.unpack('<I', data[8:12])[0] run_count = struct.unpack('<I', data[12:16])[0] # 时间戳在偏移 0x70 处(UTC) last_run_ts = struct.unpack('<Q', data[0x70:0x78])[0] # 转换为 Windows FILETIME(100纳秒单位)→ datetime dt = datetime.datetime(1601, 1, 1) + datetime.timedelta(microseconds=last_run_ts // 10) return { "version": version, "run_count": run_count, "last_run_utc": dt.isoformat(), "file_size": file_size } def collect(): pf_dir = r"C:\Windows\Prefetch" results = [] for pf_file in glob.glob(os.path.join(pf_dir, "*.pf")): try: with open(pf_file, "rb") as f: data = f.read() header = parse_prefetch_header(data) if not header: continue # 提取程序路径(在偏移 0x90 后的 Unicode 字符串区) exe_path = extract_exe_path_from_pf(data) # 实际函数略 results.append({ "prefetch_file": os.path.basename(pf_file), "exe_path": exe_path, "run_count": header["run_count"], "last_run_utc": header["last_run_utc"] }) except Exception as e: continue # 跳过损坏文件 return results参数说明:run_count是累计启动次数,非当日次数;last_run_utc是最后一次启动的绝对时间,比事件日志更早触发(程序刚加载时即写入),是判断“是否真运行过”的黄金指标。
3. 运行与配置:三步启动,零依赖安装(含离线环境适配)
本工具设计为“开箱即用”,无需安装全局 Python 环境或编译依赖。所有第三方库均打包进vendor/目录,主程序通过sys.path.insert(0, "vendor")优先加载。运行只需三步,且每步都附带离线兜底方案。
3.1 环境准备:Python 3.8+ 与可选的管理员权限
工具要求 Python 3.8 或更高版本(因使用zoneinfo处理时区)。若目标机器无 Python,可使用pyinstaller打包的单文件trace_detector.exe(位于dist/目录),该文件已内置所有依赖,双击即运行。
注意:
pytsk3模块在 Windows 上需预编译的.pyd文件。项目已提供vendor/pytsk3-2023.2-cp38-cp38-win_amd64.pyd(对应 Python 3.8 x64),若使用 Python 3.9/3.10,请自行下载匹配版本替换vendor/pytsk3.pyd。
3.2 首次运行:命令行参数详解与输出格式选择
进入项目根目录,执行:
python main.py --output json --target C:\ --scope full参数说明:
--output: 指定输出格式,支持json(默认)、csv、html(生成带搜索框的静态页面)--target: 指定扫描目标路径,可为C:\(全盘)、C:\Users\Alice(指定用户)、或C:\evidence.img(磁盘镜像文件)--scope: 控制采集深度,light(仅注册表+事件日志)、normal(默认,加 MFT+Prefetch)、full(全模块,含 ShellBag+JumpList+剪贴板)
若需静默运行并记录日志:
python main.py --output csv --target C:\Users\ --scope normal > report.csv 2> error.log3.3 输出解读:JSON 报告结构与关键字段含义
生成的report.json是一个列表,每个元素代表一条痕迹记录,结构如下:
{ "id": "mft_123456", "source": "mft", "timestamp_utc": "2024-05-20T08:14:22.123Z", "event_type": "file_created", "path": "C:\\Users\\Alice\\Downloads\\invoice.pdf", "details": { "size_bytes": 2048000, "md5_hash": "a1b2c3..." }, "confidence": 0.95 }关键字段:
source: 数据来源模块(mft,shellbag,prefetch,eventlog等)timestamp_utc: 统一转换为 UTC 的时间戳,所有模块时间已对齐event_type: 事件语义类型(file_created,folder_opened,program_executed,usb_connected)confidence: 该记录的可信度(0.0~1.0),由模块内部算法计算(如 MFT 时间直接读取为 0.95,ShellBag 时间来自注册表 LastWrite 为 0.85)
4. 常见问题与避坑指南:那些让你怀疑人生却有解的报错
实际部署中,90% 的失败不是代码问题,而是 Windows 权限、路径编码、或数据损坏导致的“静默跳过”。以下是我在某公司内网批量扫描 200+ 台 Win10 终端时踩出的血泪经验,每一条都附带复现方式和确定性解法。
4.1 现象:main.py运行后立即退出,控制台无任何输出,error.log为空
原因:Python 脚本被 Windows SmartScreen 或企业组策略拦截,.py文件关联被禁用,导致双击或python main.py实际未执行。
解决:
- 右键
main.py→ “属性” → 勾选“解除锁定”(若存在); - 在 PowerShell 中执行:
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser; - 改用绝对路径调用:
C:\Python38\python.exe C:\trace_tool\main.py。
4.2 现象:mft_collector报错OSError: [WinError 5] 拒绝访问。,但其他模块正常
原因:pytsk3尝试打开\\.\C:需要管理员权限,而当前 CMD/PowerShell 未以管理员身份运行。
解决:
- 方案 A(推荐):右键“Windows PowerShell(管理员)” → 运行
cd /d C:\trace_tool && python main.py --scope normal(跳过 MFT,用os.stat()降级); - 方案 B:在
config.yaml中设置mft_fallback: true,工具将自动切换至目录扫描模式。
4.3 现象:shellbag_collector解析出大量乱码路径,如\\\Desktop
原因:ShellBag 数据中存在非标准 Unicode 编码或空字节截断,decode('utf-16-le')失败后返回?占位符。
解决:
- 修改
shellbag_collector.py中的decode行:seg = data[offset:offset + seg_len * 2].decode('utf-16-le', errors='replace')errors='replace'会将非法字节替换为 ``,而非抛异常中断; - 更彻底的方案:启用
--debug-shellbag参数,工具会将原始二进制数据保存为shellbag_raw.bin,供后续人工分析。
4.4 现象:prefetch_collector扫描极慢(>10 分钟),CPU 占用 100%
原因:C:\Windows\Prefetch\目录下存在数百个.pf文件,而部分旧版 Prefetch 文件(v17/18)解析逻辑有缺陷,导致无限循环。
解决:
- 在
prefetch_collector.py的parse_prefetch_header函数开头添加校验:if version not in [17, 23, 26, 30]: # 仅支持主流版本 return None - 或直接在命令行限制扫描数量:
python main.py --prefetch-limit 50(只解析最近 50 个)。
4.5 现象:生成的report.json中timestamp_utc全为null
原因:系统时区设置为非 UTC(如China Standard Time),而部分模块(如事件日志解析)未正确调用astimezone(timezone.utc)转换。
解决:
- 运行前设置环境变量:
set PYTHONIOENCODING=utf-8 && set TZ=UTC; - 或在
main.py开头强制设置:import os os.environ['TZ'] = 'UTC' time.tzset() # 仅 Unix 有效,Windows 下此行可忽略
5. 二次开发实战:如何为你的场景定制一个“USB 插拔追踪器”
当基础功能满足后,真正的价值在于按需扩展。我曾为某实验室定制一个 USB 设备插拔追踪模块,用于监控实验设备接入行为。它不依赖 WMI(常被禁用),而是解析C:\Windows\System32\winevt\Logs\System.evtx中的EventID 20001(设备安装)和20003(设备移除)事件。以下是如何从零添加该模块的完整路径。
5.1 新建采集器模块:usb_collector.py
在collectors/目录下新建usb_collector.py,继承CollectorBase(已在collector_base.py定义):
# collectors/usb_collector.py import xml.etree.ElementTree as ET from datetime import datetime, timezone from collector_base import CollectorBase class UsbCollector(CollectorBase): def __init__(self, log_path=r"C:\Windows\System32\winevt\Logs\System.evtx"): self.log_path = log_path def collect(self): if not os.path.exists(self.log_path): return [] results = [] # 使用 wevtutil 命令导出 XML(比直接解析二进制 evtx 稳定) cmd = f'wevtutil qe "{self.log_path}" /q:"*[System[(EventID=20001 or EventID=20003)]]" /f:XML' try: output = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, encoding='utf-8') except subprocess.CalledProcessError: return [] root = ET.fromstring(output) for event in root.findall(".//Event"): try: event_id = int(event.find(".//EventID").text) time_created = event.find(".//TimeCreated").get("SystemTime") # 解析 XML 中的 UserData/DeviceInstanceID device_id_elem = event.find(".//UserData//DeviceInstanceID") device_id = device_id_elem.text if device_id_elem is not None else "unknown" # 提取厂商/型号(从 EventData 中的 Name 字段) name_elem = event.find(".//EventData//Data[@Name='Name']") device_name = name_elem.text if name_elem is not None else "unknown" results.append({ "source": "usb", "event_type": "usb_connected" if event_id == 20001 else "usb_disconnected", "timestamp_utc": time_created.replace(' ', 'T') + 'Z', "device_id": device_id, "device_name": device_name, "confidence": 0.92 }) except (AttributeError, ValueError, ET.ParseError): continue return results5.2 注册到主程序:让main.py认识它
修改main.py,在模块导入区添加:
from collectors.usb_collector import UsbCollector在采集器初始化列表中加入:
collectors = [ MftCollector(), ShellBagCollector(), PrefetchCollector(), EventLogCollector(), UsbCollector(), # ← 新增这一行 ]5.3 添加命令行开关与配置项
在argparse配置中增加:
parser.add_argument('--no-usb', action='store_true', help='Skip USB device collection')并在调度逻辑中判断:
if not args.no_usb: collectors.append(UsbCollector())5.4 验证与调试:三步确认模块生效
- 快速验证:插入 U 盘,运行
python main.py --no-mft --no-prefetch --output json | jq '.[] | select(.source=="usb")',应看到类似:{ "source": "usb", "event_type": "usb_connected", "timestamp_utc": "2024-05-20T14:22:33.123Z", "device_id": "USB\\VID_0781&PID_5581\\...", "device_name": "SanDisk Ultra Fit" } - 时间精度验证:对比 Windows 事件查看器中同一事件的时间戳,确认毫秒级一致;
- 离线验证:将
System.evtx复制到另一台机器,修改UsbCollector.__init__()中的log_path指向该文件,确保离线分析能力。
从那以后我每次新增采集器,都强制走一遍“命令行开关 → 单模块测试 → 时间对齐 → 离线验证”四步闭环。不是为了炫技,而是因为痕迹数据一旦出错,下游的审计结论就是空中楼阁。希望帮到你。
本文还有配套的精品资源,点击获取