一、没有EDR,溯源从何开始?
突发 NDR告警,内网一台终端主机持续解析已知恶意域名。
你登录上机,找到了木马进程。但这台终端/服务器没有EDR,也没有Sysmon。你知道终端中了马,却不知道它怎么进来的,用户到底做了什么。
Windows 操作系统在运行过程中,会像“黑匣子”一样自动记录大量用户行为和系统活动的痕迹。它们记录的是程序执行、文件访问、文件夹浏览等具体行为,比传统安全事件日志更贴近溯源需求。当商业监控体系缺席时,这些痕迹就是我们重建攻击时间线的最后底牌。
本文将重点剖析四类最核心的Windows活动痕迹——Prefetch、UserAssist、Recent文件夹、软件安装记录。单看一项可能只是碎片,拼在一起,还原出尽可能完整的“中马”事件链。
二、Prefetch
2.1 机制与文件位置
Windows Prefetch(预读取)机制的设计初衷,是为了加速应用程序启动。当可执行程序首次运行时,系统会分析其加载的DLL和相关文件,并将这些信息保存到 C:\Windows\Prefetch\ 目录下的 .pf 文件中。下次启动同一程序时,系统根据这些预读取信息提前将数据载入内存,从而缩短启动时间。

每个 Prefetch 文件采用 程序名-哈希值.pf 的格式命名,如 NOTEPAD.EXE-D8414F97.pf。哈希值是根据可执行文件的完整路径计算得出的,因此即使同一程序从不同目录运行,也会生成不同的 Prefetch 文件。
从取证角度看,Prefetch文件的价值在于:它能够证明某个可执行文件确实在系统中运行过。即使原始程序文件已经被删除,只要对应的 .pf 文件还存在,就能提取出程序最近最多8次执行的时间戳。
2.2 二进制文件结构
Prefetch 文件是二进制存储,不同 Windows 版本使用不同的内部结构版本号:
Windows XP / Vista:版本 17
Windows 7:版本 23
Windows 8 / 8.1:版本 26
Windows 10:版本 30
Windows 11:版本 31
以 Windows 10 / 11(版本 30 / 31)为例,文件的核心结构如下:
偏移 0x00:4字节版本号(30 或 31)
偏移 0x04:4字节签名,固定为
SCCA(0x41434353)偏移 0x10:60字节的Unicode程序名(如在注册表中的显示名)
偏移 0x4C:4字节路径哈希值
偏移 0x80:8个连续的 FILETIME 结构(每个8字节,共64字节),记录最近8次执行时间,按从新到旧排列。若执行次数不足8次,剩余位置填充为0。
偏移 0x64(版本 30/31):4字节的字符串区偏移量。该区域由多个以 null 结尾的Unicode字符串组成,包含程序的完整路径以及加载过的DLL或文件列表。
2.3 MAM 压缩与解压
从 Windows 10 开始,Prefetch 文件默认被 MAM(Microsoft Application Management)压缩。这意味着直接读取 .pf 文件会看到以 MAM 为开头的压缩数据,必须先解压才能获得上述原始结构。
解压可以使用 Windows 内核提供的函数。在 C++ 中,可以动态加载 ntdll.dll 中的 RtlDecompressBufferEx 与 RtlGetCompressionWorkSpaceSize 来完成。
2.4 提取关键信息
解压获得原始数据后,提取执行时间十分简单。直接从偏移 0x80 处按顺序读取8个 FILETIME 结构,并过滤掉全为零的条目即可:
std::vector<FILETIME> ExtractExecutionTimes(const BYTE* data, DWORD dataSize) {
std::vector<FILETIME> times;
if (dataSize < 0x80 + 8 * sizeof(FILETIME)) return times;
FILETIME* ft = (FILETIME*)&data[0x80];
for (int i = 0; i < 8; i++) {
if (ft[i].dwLowDateTime != 0 || ft[i].dwHighDateTime != 0)
times.push_back(ft[i]);
}
return times;
}而要提取程序执行时的完整路径,则需要根据版本号定位字符串区域,然后遍历其中所有的 null 结尾字符串,找到包含程序名且带有 .EXE 扩展名的那一个。Prefetch 中记录的路径可能带有 \Volume{GUID} 前缀,为了可读性可以在解析时将其去除或转换为盘符。
2.5 取证价值与工具
Prefetch 文件是确认程序是否被执行过的首要证据。在无 EDR的场景下,它是证明“恶意程序曾经运行过”最有力的唯一痕迹。很多攻击者会删除恶意程序本体,却不会去清空 Prefetch 目录。
常用的解析工具包括:LastActivityView(常用)、WinPrefetchView、PECmd
三、UserAssist
3.1 记录机制与位置
UserAssist 是用来记录用户通过图形界面启动程序行为的一套注册表项。与 Prefetch 不同,它只记录通过双击可执行文件或快捷方式这种“主动交互”方式启动的程序,不包括后台服务、命令行脚本静默启动的进程。因此 UserAssist 更贴近“用户实际上做了什么”这一层面。
UserAssist数据存储在HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist。
该路径下有两个 GUID 子键,分别对应不同记录类型:
{CEBFF5CD-ACE2-4F4F-9178-9926F41749EA}:记录可执行文件(.exe)

{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}:记录快捷方式(.lnk)

每个 GUID 下面都包含一个名为 Count 的子键,其中存储了大量以 ROT13 编码的程序路径为值名的条目,值数据则是72字节的二进制结构。
3.2 ROT13 解码
ROT13 是一种简单的凯撒密码变体,将字母表中的字母替换为其后第13个字母,非字母字符保持不变。由于英文字母表长度为26,ROT13 是自反的,即加密和解密使用同一算法。
例如,编码后的路径 P:\Jvaqbjf\Flfgrz32\pzq.rkr 经过 ROT13 解码即得到 C:\Windows\System32\cmd.exe。
C++ 实现如下:
std::wstring DecodeROT13(const std::wstring& encoded) {
std::wstring out;
for (wchar_t c : encoded) {
if (c >= L'A' && c <= L'Z')
out += (wchar_t)((c - L'A' + 13) % 26 + L'A');
else if (c >= L'a' && c <= L'z')
out += (wchar_t)((c - L'a' + 13) % 26 + L'a');
else
out += c;
}
return out;
}3.3 数据字段解析
每条 UserAssist 记录的值数据固定占72字节(0x48),其中包含几个对取证极为关键的字段:
对于取证分析最主要的是偏移 0x3C 处的最后执行时间,一个 8 字节的 FILETIME 结构(包含dwLowDateTime和dwHighDateTime两个DWORD成员,以1601年1月1日UTC为起点计算100纳秒间隔时间)如下。

如图,该处值为 0x01DCB14E171611C0(小端序),使用 python 处理转换结果如下:
>>> from datetime import datetime, timedelta, timezone
>>> dwLowDateTime = 0x171611C0
>>> dwHighDateTime = 0x01DCB14E
>>> filetime_value = (dwHighDateTime << 32) | dwLowDateTime
>>> print(f"FILETIME值: 0x{filetime_value:016X}")
FILETIME值: 0x01DCB14E171611C0
>>> seconds_since_1601 = filetime_value / 10_000_000.0
>>> epoch_difference = 11644473600
>>> unix_timestamp = seconds_since_1601 - epoch_difference
>>> base_time = datetime(1970, 1, 1, tzinfo=timezone.utc)
>>> result_time = base_time + timedelta(seconds=unix_timestamp)
>>> print(f"最后执行时间: {result_time.strftime('%Y-%m-%d %H:%M:%S UTC')}")
最后执行时间: 2026-03-11 11:56:26 UTC3.4 解析工具
UserAssistView,可直接解码并显示 UserAssist 条目的程序和统计信息。
四、Recent 文件夹
4.1 记录原理与位置
当用户通过文件资源管理器或某些应用程序打开文件时,Windows 自动在 %APPDATA%\Microsoft\Windows\Recent\ 目录下创建或更新指向该文件的快捷方式(.lnk)。

该快捷方式的修改时间即代表用户最后一次访问此目标文件的时间,而非目标文件自身的修改时间。因此即使攻击者或用户删除了目标文件,Recent 文件夹中的 .lnk 文件依然可能留存,为取证提供宝贵的访问时间与目标路径信息。
4.2 Recent 取证价值
每个 .lnk 文件内部包含着目标文件的完整路径。通过解析快捷方式,可获取:
用户最近访问了哪些文档、图片、压缩包或可执行文件。
在数据泄露场景中,可以迅速判定用户是否打开过包含敏感信息的文件。
在恶意邮件附件场景中,可以验证用户是否双击打开了带毒的文档。
4.3 .lnk 文件解析实现
Windows 提供了标准的 COM 接口来解析快捷方式文件,无需手动处理复杂的二进制结构。核心接口为 IShellLink 和 IPersistFile。
std::wstring ResolveShortcut(const std::wstring& lnkPath) {
IShellLinkW* psl = nullptr;
IPersistFile* ppf = nullptr;
std::wstring target;
if (SUCCEEDED(CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
IID_IShellLinkW, (void**)&psl))) {
if (SUCCEEDED(psl->QueryInterface(IID_IPersistFile, (void**)&ppf))) {
if (SUCCEEDED(ppf->Load(lnkPath.c_str(), STGM_READ))) {
wchar_t buf[MAX_PATH];
if (SUCCEEDED(psl->GetPath(buf, MAX_PATH, NULL, 0)))
target = buf;
}
ppf->Release();
}
psl->Release();
}
return target;
}程序初始化时需要调用 CoInitialize(NULL) 来初始化 COM 库,结束时调用 CoUninitialize() 清理。
五、软件安装记录
5.1 注册表位置与字段
Windows 系统在软件安装过程中,通常会向注册表中写入软件信息,这些信息集中在以下两个注册表路径:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\UninstallHKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall(64位系统上记录32位软件)

每个软件的子键下包含多个注册表值,其中最重要的几个值包括:DisplayName(软件显示名称)、InstallDate(安装日期,格式为 YYYYMMDD)、Publisher(发布者)、DisplayVersion(版本号)、InstallLocation(安装路径)。需要注意的是,并非所有软件都会记录完整的信息,特别是 InstallDate 字段,很多软件在安装时并不会写入这个值。
5.2 取证分析价值
在实际场景中,用户可能会通过钓鱼网站或诱导下载并安装假冒或带后门的应用程式,而白加黑利用中也常需要一个看似合法的程序作为宿主。通过排查 Uninstall 注册表,可以发现与恶意木马运行关联的程序,确定来源。
5.3 局限与补充验证
软件安装记录存在一个局限:InstallDate 为空,很多软件并不会记录安装日期,而且部分程序更新会更新值,导致我们无法为这些软件有效建立时间线。因此,当 InstallDate 缺失时,另外一个思路是可以将软件父路径(安装目录)的 文件系统创建时间 作为辅助判断依据。
六、其他可溯源痕迹速览
除了上述四项核心痕迹,Windows 还保留了其他活动记录,在特定场景下能提供更细颗粒度的证据。但由于版本限制较多、解析复制以及取证意义不大,个人觉得可以作为了解参考,以下逐一简述。
AppCompatCache (Shimcache)
存储于HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\AppCompatCache,记录系统级程序执行路径与时间。需注意不同 Windows 版本底层结构不同,且有时存在误报,必须与 Prefetch 交叉验证。工具:AppCompatCacheParser。Amcache
C:\Windows\AppCompat\Programs\Amcache.hve注册表 hive 文件,包含已执行程序的 SHA1 哈希和路径,即使程序被删除也有残留。工具:AmcacheParser。MuiCache
HKCU\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\MuiCache存储已运行应用程序的 FriendlyName 基本名称属性信息。ShellBags
HKEY_USERS\<SID>\Software\Microsoft\Windows\Shell\BagMRU与Bags记录用户浏览过的文件夹路径及最后访问时间,即使文件夹删除也依然留存。工具:ShellBags Explorer。ComDlg32 MRU
注册表中LastVisitedPidlMRU与OpenSavePidlMRU记录应用程序“打开/保存”对话框最后访问的文件夹,将程序与文件夹浏览行为直接绑定。Jump Lists
%APPDATA%\Microsoft\Windows\Recent\AutomaticDestinations\下的 OLE 复合文件,内含各应用程序最近打开的文件 LNK 流,精准关联程序与文件。工具:JLECmd。RecentDocs
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\RecentDocs按文件扩展名分类记录用户最近打开的文档,辅助快速搜索。SRUM
系统资源使用监视器数据库C:\Windows\System32\sru\SRUDB.dat,每30~60分钟快照一次每个应用的网络流量(上传/下载字节)和 CPU 消耗,保留最近30天数据。即使无网络监控,也能一定程度回溯数据外传行为。工具:srum-dump、SRUMECmd。RunMRU
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU记录“Win+R”运行对话框的命令历史,首次输入后会记录便于下次输入提示。
七、工具与总结
Windows 活动痕迹不算丰富完备,在所有可用的痕迹来源中,Prefetch、UserAssist、Recent 文件夹、软件安装记录四个方向可以优先关注。当然最好的方式对所有的数据来源进行统一提取分析,按照时间线进行梳理,我们可以使用老手工具LastActivityView、UserAssistView、WG-Win-Check 等工具功能,进一步直观尝试去溯源还原线索。
参考资料:
https://securelist.com/userassist-artifact-forensic-value-for-incident-response/116911
https://learn.microsoft.com/en-us/windows/win32/msi/uninstall-registry-key
https://www.magnetforensics.com/blog/shimcache-vs-amcache-key-windows-forensic-artifacts
https://www.magnetforensics.com/blog/forensic-analysis-of-windows-shellbags
https://www.magnetforensics.com/blog/what-is-mru-most-recently-used
https://www.cybertriage.com/blog/how-to-investigate-runmru-2026