分析Windows LNK文件以提取人工痕迹
何时使用
- 需要从Windows快捷方式文件中重建用户文件访问历史时
- 用于追踪被访问的文件、网络共享和可移动介质
- 在调查过程中证明用户打开了特定文档
- 需要将文件访问与其他时间线痕迹进行关联时
- 用于识别远程系统或USB设备上被访问的路径
前提条件
- 能够访问取证镜像中的LNK文件(Recent、Desktop、Quick Launch等位置)
- 安装LECmd(Eric Zimmerman)、python-lnk或LnkParser等分析工具
- 理解LNK文件结构(Shell Link二进制格式)
- 了解Windows系统上LNK文件的存放位置
- 配备已安装分析工具的取证工作站
工作流
步骤1:从取证镜像中收集LNK文件
# 挂载取证镜像
mount -o ro,loop,offset=$((2048*512)) /cases/case-2024-001/images/evidence.dd /mnt/evidence
mkdir -p /cases/case-2024-001/lnk/{recent,desktop,startup,custom}
# 复制Recent文件夹中的LNK文件(主要来源)
cp /mnt/evidence/Users/*/AppData/Roaming/Microsoft/Windows/Recent/*.lnk \
/cases/case-2024-001/lnk/recent/ 2>/dev/null
# 复制自动目标跳转列表(Jump Lists)
cp /mnt/evidence/Users/*/AppData/Roaming/Microsoft/Windows/Recent/AutomaticDestinations/*.automaticDestinations-ms \
/cases/case-2024-001/lnk/recent/ 2>/dev/null
# 复制自定义目标跳转列表(固定的Jump List项)
cp /mnt/evidence/Users/*/AppData/Roaming/Microsoft/Windows/Recent/CustomDestinations/*.customDestinations-ms \
/cases/case-2024-001/lnk/custom/ 2>/dev/null
# 复制桌面快捷方式
cp /mnt/evidence/Users/*/Desktop/*.lnk /cases/case-2024-001/lnk/desktop/ 2>/dev/null
# 复制启动文件夹快捷方式(持久化)
cp /mnt/evidence/Users/*/AppData/Roaming/Microsoft/Windows/Start\ Menu/Programs/Startup/*.lnk \
/cases/case-2024-001/lnk/startup/ 2>/dev/null
cp "/mnt/evidence/ProgramData/Microsoft/Windows/Start Menu/Programs/Startup"/*.lnk \
/cases/case-2024-001/lnk/startup/ 2>/dev/null
# 在系统上查找所有LNK文件
find /mnt/evidence/ -name "*.lnk" -type f 2>/dev/null > /cases/case-2024-001/lnk/all_lnk_locations.txt
# 统计并计算哈希
ls /cases/case-2024-001/lnk/recent/ | wc -l
sha256sum /cases/case-2024-001/lnk/recent/*.lnk > /cases/case-2024-001/lnk/lnk_hashes.txt 2>/dev/null
步骤2:使用LECmd解析LNK文件
# 使用Eric Zimmerman的LECmd(Windows或通过Mono运行)
# 处理目录中的所有LNK文件
LECmd.exe -d "C:\cases\lnk\recent" --csv "C:\cases\analysis\" --csvf lnk_analysis.csv
# 处理单个LNK文件并输出详细信息
LECmd.exe -f "C:\cases\lnk\recent\document.pdf.lnk"
# 处理Jump List文件
JLECmd.exe -d "C:\cases\lnk\recent" --csv "C:\cases\analysis\" --csvf jumplist_analysis.csv
# 输出包含以下信息:
# - 源文件路径
# - 目标路径(被访问的文件)
# - 目标的创建、修改、访问时间戳
# - LNK文件的创建和修改时间戳
# - 工作目录
# - 命令行参数
# - 卷序列号和卷标
# - 驱动器类型(固定、可移动、网络)
# - 机器ID(NetBIOS名称)
# - MAC地址(来自跟踪器数据库)
# - 目标文件大小
步骤3:使用Python解析LNK文件
pip install LnkParse3
python3 << 'PYEOF'
import LnkParse3
import os, json, csv
from datetime import datetime
lnk_dir = '/cases/case-2024-001/lnk/recent/'
results = []
for filename in sorted(os.listdir(lnk_dir)):
if not filename.lower().endswith('.lnk'):
continue
filepath = os.path.join(lnk_dir, filename)
try:
with open(filepath, 'rb') as f:
lnk = LnkParse3.lnk_file(f)
info = lnk.get_json()
parsed = {
'lnk_file': filename,
'target_path': '',
'working_dir': '',
'arguments': '',
'target_created': '',
'target_modified': '',
'target_accessed': '',
'file_size': '',
'drive_type': '',
'volume_serial': '',
'volume_label': '',
'machine_id': '',
'mac_address': '',
}
# 提取头部时间戳
header = info.get('header', {})
parsed['target_created'] = str(header.get('creation_time', ''))
parsed['target_modified'] = str(header.get('modified_time', ''))
parsed['target_accessed'] = str(header.get('accessed_time', ''))
parsed['file_size'] = str(header.get('file_size', ''))
# 提取链接信息
link_info = info.get('link_info', {})
if link_info:
local_path = link_info.get('local_base_path', '')
network_path = link_info.get('common_network_relative_link', {}).get('net_name', '')
parsed['target_path'] = local_path or network_path
vol_info = link_info.get('volume_id', {})
if vol_info:
parsed['drive_type'] = str(vol_info.get('drive_type', ''))
parsed['volume_serial'] = str(vol_info.get('drive_serial_number', ''))
parsed['volume_label'] = str(vol_info.get('volume_label', ''))
# 提取字符串数据
string_data = info.get('string_data', {})
parsed['working_dir'] = str(string_data.get('working_dir', ''))
parsed['arguments'] = str(string_data.get('command_line_arguments', ''))
# 提取跟踪器数据(机器ID和MAC)
extra = info.get('extra', {})
tracker = extra.get('DISTRIBUTED_LINK_TRACKER_BLOCK', {})
if tracker:
parsed['machine_id'] = str(tracker.get('machine_id', ''))
parsed['mac_address'] = str(tracker.get('mac_address', ''))
results.append(parsed)
# 打印摘要
print(f"\n{filename}")
print(f" 目标: {parsed['target_path']}")
print(f" 修改时间: {parsed['target_modified']}")
print(f" 驱动器: {parsed['drive_type']} (序列号: {parsed['volume_serial']})")
if parsed['machine_id']:
print(f" 机器: {parsed['machine_id']}")
except Exception as e:
print(f" 解析{filename}时出错: {e}")
# 将结果写入CSV
with open('/cases/case-2024-001/analysis/lnk_analysis.csv', 'w', newline='') as f:
writer = csv.DictWriter(f, fieldnames=results[0].keys() if results else [])
writer.writeheader()
writer.writerows(results)
print(f"\n\n共解析LNK文件数: {len(results)}")
PYEOF
步骤4:分析调查价值
# 识别从可移动介质访问的文件
python3 << 'PYEOF'
import csv
with open('/cases/case-2024-001/analysis/lnk_analysis.csv') as f:
reader = csv.DictReader(f)
print("=== 从可移动介质访问的文件 ===\n")
removable = []
network = []
for row in reader:
if 'DRIVE_REMOVABLE' in row.get('drive_type', '').upper() or \
'removable' in row.get('drive_type', '').lower():
removable.append(row)
print(f" {row['target_modified']} | {row['target_path']} | 卷: {row['volume_serial']}")
if 'network' in row.get('drive_type', '').lower() or \
row.get('target_path', '').startswith('\\'):
network.append(row)
print(f"\n=== 从网络共享访问的文件 ===\n")
for row in network:
print(f" {row['target_modified']} | {row['target_path']}")
print(f"\n可移动介质文件数: {len(removable)}")
print(f"网络共享文件数: {len(network)}")
# 检查唯一机器(跟踪器数据)
machines = set()
for row in [*removable, *network]:
if row.get('machine_id'):
machines.add(row['machine_id'])
if machines:
print(f"\n发现的机器ID: {machines}")
PYEOF
# 检查启动文件夹中的LNK文件(持久化)
echo "=== 启动文件夹快捷方式(持久化) ===" > /cases/case-2024-001/analysis/startup_persistence.txt
for lnk in /cases/case-2024-001/lnk/startup/*.lnk; do
python3 -c "
import LnkParse3
with open('$lnk', 'rb') as f:
lnk = LnkParse3.lnk_file(f)
info = lnk.get_json()
target = info.get('link_info', {}).get('local_base_path', 'Unknown')
args = info.get('string_data', {}).get('command_line_arguments', '')
print(f' $(basename $lnk): {target} {args}')
" >> /cases/case-2024-001/analysis/startup_persistence.txt 2>/dev/null
done
关键概念
| 概念 | 描述 |
|---|---|
| Shell Link (.lnk) | Windows快捷方式文件格式,包含目标路径、时间戳和元数据 |
| 目标时间戳 | 快捷方式指向文件的创建、修改和访问时间 |
| 卷序列号 | 目标文件所在驱动器的唯一标识符 |
| 机器ID | 由分布式链接跟踪服务嵌入的NetBIOS名称 |
| MAC地址 | 创建该LNK文件的机器的网络适配器MAC地址 |
| 跳转列表 | 每个应用的最近文件和固定文件列表(包含嵌入式LNK数据) |
| 自动目标 | 系统管理的最近打开文件的跳转列表条目 |
| 自定义目标 | 用户固定的跳转列表项,在手动移除前一直存在 |
工具与系统
| 工具 | 用途 |
|---|---|
| LECmd | Eric Zimmerman的命令行LNK文件解析器,支持CSV/JSON输出 |
| JLECmd | Eric Zimmerman的跳转列表解析器 |
| LnkParse3 | 用于程序化LNK文件分析的Python库 |
| lnk_parser | 备用的Python LNK解析工具 |
| Autopsy | 包含LNK文件分析模块的取证平台 |
| KAPE | 自动化的LNK和跳转列表痕迹收集工具 |
| Plaso | 带有LNK文件解析器的时间线工具,用于创建超级时间线 |
| LNK Explorer | 用于交互式LNK文件检查的GUI工具 |
常见场景
场景1:通过USB驱动器泄露数据 分析Recent文件夹中的LNK文件,查找目标位于可移动驱动器上的条目,将卷序列号与USBSTOR注册表条目关联,构建从USB设备访问的文件列表,确定从可移动驱动器打开了哪些文档,并与文件复制时间戳进行关联。
场景2:通过启动快捷方式实现恶意软件持久化 检查Startup文件夹中的LNK文件是否有恶意目标,检查目标路径和参数中是否包含编码命令或可疑可执行文件,验证目标文件是否存在并检查它,将创建时间戳与初始入侵时间进行关联。
场景3:网络共享访问调查 筛选包含网络路径(UNC目标)的LNK文件,确定哪些网络共享被访问以及何时被访问,将机器ID与已知的企业系统关联,检查敏感文件服务器是否在正常职责范围之外被访问,构建合规调查的访问时间线。
场景4:法律诉讼中的文档访问时间线 提取所有Recent文件夹LNK文件,构建用户访问文档的时间顺序列表,识别与案件相关的特定文件,展示目标时间戳以显示文件何时被打开,并与电子邮件和通信时间线进行关联。
输出格式
LNK文件分析摘要:
用户配置文件:suspect_user
LNK文件总数:234 (Recent: 198, Desktop: 23, Startup: 5, 其他: 8)
文件访问统计:
本地驱动器 (C:): 156 个文件
可移动介质: 23 个文件(3个唯一卷序列号)
网络共享: 15 个文件 (\\server01, \\fileserver)
其他驱动器: 4 个文件
发现的机器ID:DESKTOP-ABC123, LAPTOP-XYZ789
MAC地址:AA:BB:CC:DD:EE:FF, 11:22:33:44:55:66
可移动介质访问:
卷序列号 1234-ABCD:
2024-01-15 14:32 - E:\Confidential\financial_report.xlsx
2024-01-15 14:45 - E:\Confidential\customer_database.csv
2024-01-15 15:00 - E:\Projects\source_code.zip
启动持久化:
updater.lnk -> C:\ProgramData\svc\updater.exe (可疑)
OneDrive.lnk -> C:\Users\...\OneDrive.exe (合法)
时间线:/cases/case-2024-001/analysis/lnk_analysis.csv