关键词:UserAssist、RunMRU、TypedPaths、MountPoints2、Shellbags、RecentDocs、UserAssist 编码、ROT13、AppCompatFlags
难度:进阶
前置知识:注册表取证、ROT13 与 Base64 编码、时间线分析方法
一、概述
Prefetch 回答「程序有没有跑过」。用户行为痕迹回答另一件事——人在干什么。
这类痕迹散落在注册表的用户配置单元(NTUSER.DAT)里,一行记录对应一个真实动作:打开过某个应用、在地址栏敲过某个路径、挂载过某个盘、点进过某个目录。
密度高,误读率也高。UserAssist 的值经过 ROT13 编码,RunMRU 是二进制链表,Shellbags 记的是资源管理器的导航历史。不解码,拿到手的就是一堆字节。把编码后的字符串直接抄进报告,是这类工件最常见也最致命的错误。
局限同样硬。绝大多数条目只存在于该用户的 NTUSER.DAT,配置文件被删除就全部消失;部分键有固定条目上限(UserAssist 旧版本为 100),到顶后循环覆盖;它们只能证明「使用了某个入口」,证明不了操作成功,更证明不了恶意。
这一层位于分析环节的「归属与行为」,上游是执行证据——程序执行证据见Prefetch 深度解析,注册表解析方法见注册表取证。上游要的是用户 hive 提取与归属判定准不准。这些键值全在用户自己的 hive 里,系统账户 hive(SYSTEM)内没有这些内容——只有用户行为才有「用户」。分析系统账户会得到「该用户从未使用过任何程序」的错误结论,因为那些数据根本不在那里。
解析方式必须按版本决定。UserAssist 有两种取值布局(WinXP 16 字节、Win7+ 72 字节),混用会得出完全错误的次数;Shellbags 的结构按版本差异较大,把旧结构当新结构读会得到完全错位的路径。下游支撑行为画像:Shellbags 容量不固定、无硬上限,是长周期案件中少有的「用户活动档案」;MountPoints2 在外泄与溯源案件中价值极高。
全部痕迹位于 NTUSER.DAT 内:
| 痕迹 |
位置(Software\ 下) |
记录内容 |
容量特性 |
| UserAssist |
Microsoft\Windows\CurrentVersion\Explorer\UserAssist\{GUID}\Count |
程序打开次数与最后使用时间 |
上限约 100 条,循环覆盖 |
| RunMRU |
...\Explorer\RunMRU |
运行对话框中手工输入过的命令 |
上限约 59 项 |
| TypedPaths |
...\Explorer\TypedPaths |
地址栏手工输入的路径 |
上限约 30 项 |
| WordWheelQuery |
...\Explorer\WordWheelQuery |
「打开方式」列表中输入过的关键词 |
有上限 |
| RecentDocs |
...\Explorer\RecentDocs |
最近打开文档的完整路径 |
按扩展名分子键 |
| MountPoints2 |
...\Explorer\MountPoints2 |
曾挂载过的卷、目录与 GUID 路径挂载 |
按卷标识分子键 |
| Shellbags |
Microsoft\Windows\Shell\Bags 及 ...\Software\Classes\Local Settings\...\Shell\Bags |
资源管理器导航历史与排序偏好 |
滚动保留,无硬上限 |
ROT13 编码的适用范围只有一个:值名(子键名)里的程序路径。
| 元素 |
是否 ROT13 |
说明 |
| 值名 / 子键名中的路径 |
是 |
P:\Windows\System32\pqnq.rkr → C:\Windows\System32\cmd.exe |
| Win7+ 的 Run Count |
否 |
是二进制 DWORD,直接按小端读 |
| Win10+ 时间戳子键名中的时间 |
否 |
是 FILETIME(16 字节),只对路径部分做 ROT13 |
据此能回答的是:某用户打开过哪些程序及其次数、输入过哪些路径、挂载过哪些卷或网络共享到哪个目录、浏览过哪些目录,以及某目录的文件已被删除时用户是否进过该目录(Shellbags 独有,5.2 就是靠它定位了目录访问)。
回答不了的部分,最容易踩坑的是前两种:
- 把 Win7+ 的计数当成 ROT13 字符串去「解码」。 ROT13 只作用于字母,数字原样保留,所以把一个 DWORD 误读成 ROT13 字符串时,往往会得到一个看似合理却完全错误的次数。正确做法是先看值长度(16 / 72 字节)判定版本,再决定解析方式。
- 拿时间来定位行为时刻。 时间证明力普遍弱于事件日志与 NTFS 时间戳。UserAssist 最后使用时间部分版本有、部分版本没有(缺失不等于没使用);TypedPaths / RunMRU 只有值没有独立时间戳,其注册表键的最后写入时间会被其他写入刷新,不精确。绝不能单独用于精确定位行为时刻,只能作为「某用户与某路径/程序发生过交互」的存在性证据。
- 时间戳的类型搞混。 RecentDocs 与 Shellbags 里的时间是键的最后写入时间,反映用户活动,但不是「文件被访问」的时间。
- 操作是否成功。 这些键值只证明「使用了某个入口」,证明不了操作完成。
- 行为是否恶意。 中性的使用记录,定性需要行为证据与哈希查库。
- 入口数量之外的完整历史。 UserAssist、RunMRU、TypedPaths 都有条目上限并循环覆盖,「最近 N 条」之外的部分不可知。
- 已删除用户配置文件的痕迹。 用户配置文件被删除即全部消失,这类缺失必须限定结论范围。
往下读之前先确认你已经掌握注册表 hive 的提取与路径解析(尤其是用户 hive 的逐用户选取,见注册表取证)、ROT13 与 Base64 这类编码的本质(ROT13 只作用于字母,数字原样保留)、Windows 版本差异对键值结构的影响(UserAssist 与 Shellbags 的布局都随版本变化),并且会按值长度与实际取值验证布局假设,不要照搬任何一版布局。此外要掌握时间戳的纪元与 FILETIME 换算——Win10+ 的时间戳藏在子键名里——并理解**「存在性证据」与「时点证据」的区别**,这些工件只提供前者。
二、核心原理
2.1 工件矩阵与落点
| 痕迹 |
位置(NTUSER.DAT 内) |
记录内容 |
容量特性 |
| UserAssist |
Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist\{GUID}\Count |
程序打开次数与最后使用时间 |
上限约 100 条,循环覆盖 |
| RunMRU |
Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU |
运行对话框中手工输入过的命令 |
上限约 59 项 |
| TypedPaths |
Software\Microsoft\Windows\CurrentVersion\Explorer\TypedPaths |
资源管理器地址栏手工输入的路径 |
上限约 30 项 |
| WordWheelQuery |
Software\Microsoft\Windows\CurrentVersion\Explorer\WordWheelQuery |
文件关联「打开方式」列表中输入过的关键词 |
有上限 |
| RecentDocs |
Software\Microsoft\Windows\CurrentVersion\Explorer\RecentDocs |
最近打开文档的完整路径 |
按扩展名分子键 |
| MountPoints2 |
Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2 |
曾挂载过的卷、目录与 GUID 路径挂载 |
按卷标识分子键 |
| Shellbags / ShellBagMRU |
Software\Microsoft\Windows\Shell\Bags 及 Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\Bags |
资源管理器窗口导航历史与排序偏好 |
滚动保留 |
| BAM / UserAssist 扩展 |
Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist 子项 |
部分版本额外记录执行计数 |
视版本 |
全在用户自己的 hive 里。SYSTEM 装的是 ControlSet、服务、会话这类系统级信息,SOFTWARE 是 64 位与 32 位机器级配置,两边都没有用户行为。
查错 hive 得到的空结果,是「数据不在这里」而不是「没有行为」。
2.2 UserAssist 的编码与结构
UserAssist 有两种取值布局,必须按 Windows 版本区分。
混用会得出完全错误的次数。
Win7 / Win8.x(Windows 7 起,值长度 72 字节)
| 偏移 |
长度 |
内容 |
| 0 |
4 |
Session ID |
| 4 |
4 |
Run Count(二进制小端 DWORD,不是字符串) |
| 8 |
8 |
Focus Count(Win7/8 存在;Win8.1 起不再填充) |
| 16 |
56 |
其余为保留填充(0);Win7 布局在此处不含时间字段 |
Win7 的 72 字节布局在偏移 16 之后是纯填充;部分 Win8 构建会在偏移 60 处放入最后使用时间 FILETIME。该字段位置随版本变化,必须用实际检材的长度与取值验证,不要照搬任何一版布局。
WinXP(值长度 16 字节)
| 偏移 |
长度 |
内容 |
| 0 |
4 |
Run Count(DWORD) |
| 4 |
4 |
0 |
| 8 |
4 |
0x00000000 |
| 12 |
4 |
0x000000FF(版本标记) |
最后使用时间的来源随版本变。Win10 / Win11 会另外在 UserAssist\<GUID>\Count 下追加带时间戳的子键(形如 \{GUID}\{timestamp}\{ROT13 路径}),时间在子键名里,不在值数据中。
Win8 及更早版本的值数据里通常不含最后使用时间。缺失不等于没使用过。
ROT13 编码的适用范围只有一个:值名(子键名)里的程序路径。
| 元素 |
是否 ROT13 |
说明 |
| 值名 / 子键名中的路径 |
是 |
P:\Windows\System32\pqnq.rkr → C:\Windows\System32\cmd.exe |
| Win7+ 的 Run Count |
否 |
是二进制 DWORD,直接按小端读 |
| Win10+ 时间戳子键名中的时间 |
否 |
是 FILETIME(16 字节),只对路径部分做 ROT13 |
最常见的错误:把 Win7+ 的计数当成 ROT13 字符串去「解码」。ROT13 只作用于字母,数字原样保留——所以把一个 DWORD 误读成 ROT13 字符串时,往往会得到一个看似合理却完全错误的次数。先看值长度(16 / 72 字节)判定版本,再决定解析方式。
2.3 Shellbags 的取证价值
Shellbags 记的是资源管理器窗口的导航序列:用户进过哪些目录、在每个目录里改过哪些排序与视图设置。
这让它能回答两个别处答不了的问题——
- 用户在被调查时间窗内是否浏览过某个特定目录,哪怕该目录下的文件已被删除;
- 目录的最后访问时间在注册表层面是否被更新过。
结构按版本差异较大:早期是 ShellBagMRU 下的树形节点,较新版本引入了独立的索引表与名称映射表。解析器必须显式区分版本,把旧结构当新结构读会得到完全错位的路径。
容量上它不固定,不像 UserAssist 那样有硬上限,是长周期案件里少有的「用户活动档案」。
2.4 MountPoints2 的卷与目录挂载痕迹
MountPoints2 的子键名是卷标识(Volume identifier)而非普通名字,键值是流式数据,其中可解析出:
- 该卷曾被挂载到哪些目录(例如把某个网络共享挂载到
Z:\ 或某个本地目录);
- 真实路径(网络路径、USB 标记等);
- 磁盘卷标。
它在数据外泄与溯源案件中极有价值:记录「用户把哪台机器/哪个共享映射到了哪里」。
而且是用户主动操作才会产生的痕迹,不能由系统自动生成,证明力较强。
2.5 时间语义
时间语义三条,差别很大。
UserAssist 最后使用时间:部分版本有,部分版本没有。缺失不等于没使用。
RecentDocs 路径与 Shellbags:其中的时间字段是注册表键的最后写入时间,反映用户活动,但不是「文件被访问」的时间。
TypedPaths / RunMRU:只有值,没有独立时间戳。对应注册表键的最后写入时间可作参考,但会被其他写入刷新,不精确。
结论很直接:这些工件的时间证明力普遍弱于事件日志与 NTFS 时间戳。绝不能单独用于精确定位行为时刻,只能作为「某用户与某路径/程序发生过交互」的存在性证据。
三、操作步骤
3.1 提取用户 hive
mmls /work/DigiForensics.dd
fsstat -o 2048 /work/DigiForensics.dd
fls -o 2048 -r -p /work/DigiForensics.dd \
| grep -Ei '/Users/[^/]+/(NTUSER\.DAT|UsrClass\.dat)$' | grep -E '^r/r' | awk '{print $2}' \
| while read -r ino; do
u=$(echo "$ino" | cut -d: -f2)
icat -o 2048 /work/DigiForensics.dd "$ino" > "/evidence/registry/users/$u/$(basename "${ino%%:*}")"
done
UsrClass.dat 承载了 HKCU\Software\Classes 的内容。旧版本中 Shellbags 的部分数据就在其中。
不能只提 NTUSER.DAT。
3.2 导出并解码 UserAssist
用注册表解析工具加载 hive 后,导出 UserAssist\{GUID}\Count 下的全部子键。
先按值长度定版本,再选解析路径:
值长度 = 72 字节 → Win7/8.x:Run Count = DWORD(偏移 4, 小端)
值长度 = 16 字节 → WinXP:DWORD(偏移 0, 小端)
子键名形如 \{GUID}\{FILETIME}\{ROT13路径} → Win10+,取子键名中的 FILETIME 作为最后使用时间
无论哪种版本,值名中的路径都要做 ROT13 还原。
ROT13 只作用于字母,数字与符号原样通过。所以还原后的路径里出现数字和反斜杠是正常的,不是解码失败。
3.3 解析 TypedPaths 与 RunMRU
TypedPaths 值形如:
"D:\Projects\2024\Q1"= (0x03 0x00 ...)
RunMRU 结构为 MRU 链表:
0x00 'm' 'r' 'u' + 计数 + 各条目 "(0x...)null 字符串 null"
RunMRU 不是普通值列表,是一个 MRU 链表。头部是 ASCII 的 mru 加计数,其后是若干 (0x...) 标记与 null 终止的路径字符串,按最近使用顺序排。
解析时跳过标记字节,只保留以 null 结尾的字符串。
3.4 导出 Shellbags
使用专门的 Shellbags 解析器导出,明确指定目标系统版本以启用正确的结构解析。导出结果应包含:导航路径、每条记录对应的窗口特征、该路径在检材中的时间戳。
分析时的实用筛选:
- 只看时间窗内的记录(与案件时间窗比对);
- 关注指向敏感目录的路径(如财务、研发、
\财务\、客户资料目录);
- 与 NTFS 目录
$STANDARD_INFORMATION 的访问时间互证。
3.5 交叉验证
用户行为痕迹必须与其他证据配合:
- Prefetch:确认该程序确实执行。
- LNK / Jump List:确认打开了哪个具体文件。
- 事件日志 4624 / 4634:确认登录与注销区间,判断行为是否发生在有人值守时段。
- NTFS
$UsnJrnl:把「访问过某目录」收敛到具体的文件创建/修改操作。
四、常见陷阱
本章按解码与结构判读 → 容量与缺失解释 → 落点与版本差异 → 结论表述边界归组,每条给出:现象、为什么会误判、误判代价、正确做法。
★ 标记表示该操作会导致原始检材永久灭失或对检材造成不可逆改变,执行前必须停下来确认。
4.1 解码与结构判读
陷阱 1:忘记 ROT13,直接引用值名里的原始字符串
现象
UserAssist 导出结果里出现 P:\Windows\System32\pqnq.rkr、C:\Users\victim\AppData\Roaming\bhfgvn\pbagragf,报告照抄其中一条作为「该用户运行过的程序」。附件里放的是同一份乱码字符串。
为什么会误判
ROT13 编码的适用范围只有一个:值名(子键名)里的程序路径。 系统在写入 UserAssi