分析 Windows LNK 檔案以提取鑑識跡證
使用時機
- 從 Windows 捷徑檔案重建使用者檔案存取歷程時
- 追蹤曾被存取的檔案、網路共享和可移除媒體時
- 調查時需要證明使用者開啟過特定文件時
- 將檔案存取與其他時間線跡證進行關聯時
- 識別遠端系統或 USB 裝置上的存取路徑時
先決條件
- 能夠存取鑑識映像中的 LNK 檔案(Recent、Desktop、Quick Launch 等目錄)
- 安裝 LECmd (Eric Zimmerman)、python-lnk 或 LnkParser 等分析工具
- 了解 LNK 檔案結構(Shell Link Binary 格式)
- 熟悉 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
# 複製自動跳躍清單
cp /mnt/evidence/Users/*/AppData/Roaming/Microsoft/Windows/Recent/AutomaticDestinations/*.automaticDestinations-ms \
/cases/case-2024-001/lnk/recent/ 2>/dev/null
# 複製自訂跳躍清單(釘選的項目)
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"
# 處理跳躍清單檔案
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
# 檢查 Startup 資料夾 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', '未知')
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 |
| 跳躍清單 (Jump Lists) | 每個應用程式的最近與釘選檔案清單(內嵌 LNK 資料) |
| 自動目的地 | 系統管理的跳躍清單項目,記錄最近開啟的檔案 |
| 自訂目的地 | 使用者釘選的跳躍清單項目,會持續存在直到手動移除 |
工具與系統
| 工具 | 用途 |
|---|---|
| LECmd | Eric Zimmerman 的命令列 LNK 檔案解析器,支援 CSV/JSON 輸出 |
| JLECmd | Eric Zimmerman 的跳躍清單解析器 |
| LnkParse3 | 用於程式化分析 LNK 檔案的 Python 函式庫 |
| lnk_parser | 替代的 Python LNK 解析工具 |
| Autopsy | 內建 LNK 檔案分析模組的鑑識平台 |
| KAPE | 自動化收集 LNK 與跳躍清單 artifacts 的工具 |
| Plaso | 時間線工具,內建 LNK 檔案解析器以建立超級時間線 |
| LNK Explorer | 用於互動式檢查 LNK 檔案的圖形化工具 |
常見情境
情境 1:透過 USB 隨身碟資料外洩 分析 Recent 資料夾中的 LNK 檔案,找出指向可移除磁碟機的目標;將磁碟機序號與 USBSTOR 登錄項目關聯;建立從 USB 裝置存取過的檔案清單;確認哪些文件是從可移除磁碟機開啟的;與檔案複製時間戳進行交叉比對。
情境 2:透過 Startup 捷徑的惡意軟體持續性 檢查 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
Startup 持續性:
updater.lnk -> C:\ProgramData\svc\updater.exe(可疑)
OneDrive.lnk -> C:\Users\...\OneDrive.exe(正常)
時間線:/cases/case-2024-001/analysis/lnk_analysis.csv