Analyzes NTFS slack space, MFT entries, USN journals, and alternate data streams to recover hidden data and reconstruct file activity using TSK and Python tools.
npx claudepluginhub killvxk/cybersecurity-skills-zhThis skill uses the workspace's default tool permissions.
- 在文件系统松弛空间中搜索隐藏或残留数据
Analyzes NTFS slack space, MFT entries, USN journal, and alternate data streams using TSK, MFTECmd, and Python to recover hidden data and reconstruct file activity.
Analyzes NTFS slack space, MFT entries, USN journal, and alternate data streams to recover hidden data and reconstruct file activity. For digital forensics on disk images.
Analyzes NTFS $MFT records, $LogFile, $UsnJrnl, and slack space using MFTECmd, analyzeMFT, and X-Ways Forensics to recover deleted files' metadata and content in digital forensics investigations.
Share bugs, ideas, or general feedback.
# 确定分区布局
mmls /cases/case-2024-001/images/evidence.dd
# 提取关键 NTFS 系统文件
# $MFT - 主文件表
icat -o 2048 /cases/case-2024-001/images/evidence.dd 0 > /cases/case-2024-001/ntfs/MFT
# $UsnJrnl:$J - USN 变更日志
icat -o 2048 /cases/case-2024-001/images/evidence.dd 62-128 > /cases/case-2024-001/ntfs/UsnJrnl_J
# $LogFile - 事务日志
icat -o 2048 /cases/case-2024-001/images/evidence.dd 2 > /cases/case-2024-001/ntfs/LogFile
# 从卷中提取所有松弛空间
blkls -s -o 2048 /cases/case-2024-001/images/evidence.dd > /cases/case-2024-001/ntfs/slack_space.raw
# 获取文件系统信息
fsstat -o 2048 /cases/case-2024-001/images/evidence.dd | tee /cases/case-2024-001/ntfs/fs_info.txt
# 使用 MFTECmd(Eric Zimmerman)解析 MFT
MFTECmd.exe -f "C:\cases\ntfs\MFT" --csv "C:\cases\analysis\" --csvf mft_analysis.csv
# 使用 analyzeMFT(Python)解析
pip install analyzeMFT
analyzeMFT.py -f /cases/case-2024-001/ntfs/MFT \
-o /cases/case-2024-001/analysis/mft_analysis.csv \
-c
# 使用 Python 自定义 MFT 分析
python3 << 'PYEOF'
from mft import PyMft
import csv
mft = PyMft(open('/cases/case-2024-001/ntfs/MFT', 'rb').read())
deleted_files = []
suspicious_files = []
for entry in mft.entries():
if entry is None:
continue
filename = entry.get_filename()
if filename is None:
continue
is_deleted = not entry.is_active()
is_directory = entry.is_directory()
created = entry.get_created_timestamp()
modified = entry.get_modified_timestamp()
mft_modified = entry.get_mft_modified_timestamp()
size = entry.get_file_size()
# 标记已删除文件以供恢复
if is_deleted and not is_directory and size > 0:
deleted_files.append({
'filename': filename,
'size': size,
'created': str(created),
'modified': str(modified),
'entry_number': entry.entry_number
})
# 检测时间戳篡改(MFT 修改时间 != $SI 修改时间)
si_modified = entry.get_si_modified_timestamp()
fn_modified = entry.get_fn_modified_timestamp()
if si_modified and fn_modified:
if abs((si_modified - fn_modified).total_seconds()) > 86400: # >1 天差异
suspicious_files.append({
'filename': filename,
'si_modified': str(si_modified),
'fn_modified': str(fn_modified),
'delta': str(si_modified - fn_modified)
})
print(f"=== 已删除文件(可恢复元数据)===")
print(f"总计: {len(deleted_files)}")
for f in deleted_files[:20]:
print(f" [{f['modified']}] {f['filename']} ({f['size']} 字节)")
print(f"\n=== 潜在时间戳篡改 ===")
print(f"可疑文件总计: {len(suspicious_files)}")
for f in suspicious_files[:10]:
print(f" {f['filename']}: $SI={f['si_modified']}, $FN={f['fn_modified']} (差值: {f['delta']})")
PYEOF
# 从松弛空间提取字符串
strings -a /cases/case-2024-001/ntfs/slack_space.raw > /cases/case-2024-001/analysis/slack_strings.txt
# 在松弛空间中搜索特定模式
grep -iab "password\|secret\|confidential\|credit.card\|ssn" \
/cases/case-2024-001/ntfs/slack_space.raw > /cases/case-2024-001/analysis/slack_keywords.txt
# 分析单个文件的松弛空间
python3 << 'PYEOF'
import struct
# 文件松弛空间由以下部分组成:
# 1. RAM 松弛:文件末尾到下一扇区边界之间的字节(历史上填充 RAM 内容或零)
# 2. 驱动器松弛:文件最后扇区之后,簇中剩余的扇区
# 分析特定 MFT 条目的松弛空间
# 使用 Sleuth Kit 获取特定文件的松弛空间
import subprocess
# 获取文件详情
result = subprocess.run(
['istat', '-o', '2048', '/cases/case-2024-001/images/evidence.dd', '14523'],
capture_output=True, text=True
)
print(result.stdout)
# 输出显示数据运行 - 最后一个簇可能包含松弛数据
# 计算松弛大小:(已分配大小 - 文件大小) 字节
PYEOF
# 在松弛空间中搜索文件签名(嵌入文件)
foremost -t jpg,pdf,zip -i /cases/case-2024-001/ntfs/slack_space.raw \
-o /cases/case-2024-001/carved/slack_carved/
# 使用 bulk_extractor 在松弛空间中查找结构化数据
bulk_extractor -o /cases/case-2024-001/analysis/bulk_extract/ \
/cases/case-2024-001/ntfs/slack_space.raw
# 使用 MFTECmd 解析 USN 日志
MFTECmd.exe -f "C:\cases\ntfs\UsnJrnl_J" --csv "C:\cases\analysis\" --csvf usn_journal.csv
# Python USN 日志解析
pip install pyusn
python3 << 'PYEOF'
import struct
import csv
from datetime import datetime, timedelta
def parse_usn_record(data, offset):
"""解析单个 USN_RECORD_V2。"""
if offset + 8 > len(data):
return None, offset
record_len = struct.unpack_from('<I', data, offset)[0]
if record_len < 56 or record_len > 65536 or offset + record_len > len(data):
return None, offset + 8
major_ver = struct.unpack_from('<H', data, offset + 4)[0]
if major_ver != 2:
return None, offset + record_len
mft_ref = struct.unpack_from('<Q', data, offset + 8)[0] & 0xFFFFFFFFFFFF
parent_ref = struct.unpack_from('<Q', data, offset + 16)[0] & 0xFFFFFFFFFFFF
usn = struct.unpack_from('<Q', data, offset + 24)[0]
timestamp = struct.unpack_from('<Q', data, offset + 32)[0]
reason = struct.unpack_from('<I', data, offset + 40)[0]
source_info = struct.unpack_from('<I', data, offset + 44)[0]
security_id = struct.unpack_from('<I', data, offset + 48)[0]
file_attrs = struct.unpack_from('<I', data, offset + 52)[0]
filename_len = struct.unpack_from('<H', data, offset + 56)[0]
filename_off = struct.unpack_from('<H', data, offset + 58)[0]
name = data[offset + filename_off:offset + filename_off + filename_len].decode('utf-16-le', errors='ignore')
# 将 Windows FILETIME 转换为 datetime
ts = datetime(1601, 1, 1) + timedelta(microseconds=timestamp // 10)
# 解码原因标志
reasons = []
reason_flags = {
0x01: 'DATA_OVERWRITE', 0x02: 'DATA_EXTEND', 0x04: 'DATA_TRUNCATION',
0x10: 'NAMED_DATA_OVERWRITE', 0x20: 'NAMED_DATA_EXTEND',
0x100: 'FILE_CREATE', 0x200: 'FILE_DELETE', 0x400: 'EA_CHANGE',
0x800: 'SECURITY_CHANGE', 0x1000: 'RENAME_OLD_NAME', 0x2000: 'RENAME_NEW_NAME',
0x4000: 'INDEXABLE_CHANGE', 0x8000: 'BASIC_INFO_CHANGE',
0x10000: 'HARD_LINK_CHANGE', 0x20000: 'COMPRESSION_CHANGE',
0x40000: 'ENCRYPTION_CHANGE', 0x80000: 'OBJECT_ID_CHANGE',
0x100000: 'REPARSE_POINT_CHANGE', 0x200000: 'STREAM_CHANGE',
0x80000000: 'CLOSE'
}
for flag, desc in reason_flags.items():
if reason & flag:
reasons.append(desc)
record = {
'timestamp': ts.strftime('%Y-%m-%d %H:%M:%S'),
'filename': name,
'mft_entry': mft_ref,
'parent_entry': parent_ref,
'reasons': '|'.join(reasons),
'usn': usn
}
return record, offset + record_len
# 解析日志
with open('/cases/case-2024-001/ntfs/UsnJrnl_J', 'rb') as f:
data = f.read()
records = []
offset = 0
while offset < len(data) - 8:
record, offset = parse_usn_record(data, offset)
if record:
records.append(record)
else:
offset += 8 # 跳过零字节
# 过滤删除事件
deletions = [r for r in records if 'FILE_DELETE' in r['reasons']]
creations = [r for r in records if 'FILE_CREATE' in r['reasons']]
renames = [r for r in records if 'RENAME_NEW_NAME' in r['reasons']]
print(f"USN 记录总数: {len(records)}")
print(f"文件创建: {len(creations)}")
print(f"文件删除: {len(deletions)}")
print(f"文件重命名: {len(renames)}")
print("\n=== 最近删除 ===")
for r in deletions[-20:]:
print(f" [{r['timestamp']}] 已删除: {r['filename']} (MFT#{r['mft_entry']})")
# 将完整日志写入 CSV
with open('/cases/case-2024-001/analysis/usn_journal.csv', 'w', newline='') as f:
writer = csv.DictWriter(f, fieldnames=['timestamp', 'filename', 'mft_entry', 'parent_entry', 'reasons', 'usn'])
writer.writeheader()
writer.writerows(records)
PYEOF
# 列出镜像中所有备用数据流
find /mnt/evidence -exec getfattr -d {} \; 2>/dev/null | grep -i "ads\|zone\|stream"
# 使用 Sleuth Kit 查找 ADS
fls -r -o 2048 /cases/case-2024-001/images/evidence.dd | grep ":" | \
tee /cases/case-2024-001/analysis/ads_list.txt
# 提取特定 ADS 内容
# 格式:icat 镜像 inode:ads名称
icat -o 2048 /cases/case-2024-001/images/evidence.dd 14523:hidden_stream \
> /cases/case-2024-001/analysis/extracted_ads.bin
# 检查 Zone.Identifier 数据流(下载来源追踪)
fls -r -o 2048 /cases/case-2024-001/images/evidence.dd | grep "Zone.Identifier" | \
while read line; do
inode=$(echo "$line" | awk '{print $2}' | tr -d ':')
echo "=== $line ==="
icat -o 2048 /cases/case-2024-001/images/evidence.dd "${inode}:Zone.Identifier" 2>/dev/null
echo ""
done > /cases/case-2024-001/analysis/zone_identifiers.txt
# Zone.Identifier 内容揭示:
# [ZoneTransfer]
# ZoneId=3 (3 = Internet,表示文件已下载)
# ReferrerUrl=https://malicious-site.com/payload.exe
# HostUrl=https://cdn.malicious-site.com/payload.exe
| 概念 | 描述 |
|---|---|
| 文件松弛空间(File slack) | 文件末尾与簇边界之间包含残留数据的未使用空间 |
| RAM 松弛(RAM slack) | 从文件末尾到扇区边界的松弛空间部分(历史上填充 RAM 内容) |
| MFT($MFT) | 主文件表——每个文件都有条目的 NTFS 元数据数据库 |
| USN 日志($UsnJrnl) | 记录 NTFS 上所有文件/目录修改的变更日志 |
| 备用数据流(Alternate Data Streams) | NTFS 功能,允许每个文件有多个数据流(用于隐藏存储) |
| $STANDARD_INFORMATION | MFT 属性,包含用户态应用程序可修改的时间戳 |
| $FILE_NAME | MFT 属性,包含仅内核可修改的时间戳 |
| 时间戳篡改(Timestomping) | 修改文件时间戳以逃避检测的反取证技术 |
| 工具 | 用途 |
|---|---|
| MFTECmd | Eric Zimmerman 的 MFT 和 USN 日志解析器,支持 CSV 输出 |
| MFTExplorer | 用于 MFT 分析的交互式 GUI 工具 |
| analyzeMFT | 支持 CSV/JSON 输出的 Python MFT 解析器 |
| The Sleuth Kit | 文件系统取证工具集(fls、icat、blkls、istat) |
| bulk_extractor | 从原始数据(包括松弛空间)中提取特征 |
| NTFS Log Tracker | 用于解析 $LogFile 事务记录的工具 |
| streams.exe | 用于列出 NTFS 备用数据流的 Sysinternals 工具 |
| Plaso | 解析 MFT 和 USN 日志的超级时间线工具 |
场景 1:通过时间戳篡改检测反取证 将 MFT 条目中的 $STANDARD_INFORMATION 时间戳与 $FILE_NAME 时间戳进行比较,标记 $SI 时间戳早于 $FN 时间戳的文件(正常操作中不可能发生),将时间戳被篡改的文件识别为故意操纵的证据,与其他时间线证据相关联。
场景 2:备用数据流中的隐藏数据 扫描附加到文件上超出标准 Zone.Identifier 的 ADS,提取 ADS 内容进行分析,检查存储在 ADS 中的隐藏可执行文件或文档,将 ADS 创建与用户活动时间线相关联,记录调查结果作为证据。
场景 3:从 MFT 重建已删除文件 解析 MFT 中的非活动(已删除)条目,提取已删除文件的文件名、大小和时间戳,如果数据簇未被覆盖则使用 icat 恢复文件内容,建立已删除证据文件列表,与 USN 日志删除事件相关联。
场景 4:从 USN 日志重建文件活动 解析调查时段的 USN 变更日志,识别文件创建、修改、重命名和删除事件,重建文件操作序列,检测数据暂存证据(创建、复制、压缩、删除模式),识别反取证文件擦除。
文件系统制品分析:
卷: NTFS(分区 2,465 GB)
簇大小: 4096 字节
MFT 分析:
条目总数: 456,789
活动文件: 234,567
已删除条目: 12,345(8,901 个含可恢复元数据)
时间戳被篡改文件: 23(检测到 SI/FN 不匹配)
USN 日志:
已解析记录: 2,345,678
日期范围: 2024-01-01 至 2024-01-20
文件创建: 45,678
文件删除: 23,456
文件重命名: 12,345
备用数据流:
发现 ADS 总数: 1,234
Zone.Identifier: 890(已下载文件)
自定义/可疑 ADS: 5(检测到隐藏数据)
松弛空间:
总松弛空间: 12.3 GB
关键词命中: 45(密码、信用卡)
从松弛空间雕刻文件: 23
可疑发现:
- 23 个文件含被篡改的时间戳
- 5 个文件含隐藏 ADS 数据
- USN 显示 2024-01-18 存在大量删除(反取证)
- 松弛空间含残留邮件片段
报告: /cases/case-2024-001/analysis/