分析 Slack 空間與檔案系統成品 是做什麼的?
使用時機
- 搜尋檔案系統 Slack 空間中的隱藏或殘留資料時
- 分析 NTFS 主檔案表 (MFT) 項目以取得已刪除檔案的中繼資料時
- 從 USN 變更日誌重建檔案操作時
- 偵測用於隱藏資料或惡意軟體的替代資料串流 (ADS) 時
- 在需要超越標準檔案復原的深入鑑識分析中
先決條件
- 具有 NTFS 檔案系統的鑑識磁碟映像檔
- The Sleuth Kit (TSK) 工具:istat, icat, fls, blkls, blkstat
- MFTECmd (Eric Zimmerman) 用於 MFT 解析
- MFTExplorer 用於互動式 MFT 分析
- 了解 NTFS 結構 (MFT, $UsnJrnl, $LogFile, ADS)
- 使用 analyzeMFT 或 mft 程式庫的 Python 環境,用於自動化解析
工作流程
步驟 1:識別並擷取 NTFS 檔案系統成品
# 確認分割區配置
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
# 從磁碟區擷取所有 Slack 空間
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
步驟 2:分析主檔案表 (MFT)
# 使用 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
步驟 3:分析 Slack 空間中的隱藏資料
# 在 Slack 空間中搜尋字串
strings -a /cases/case-2024-001/ntfs/slack_space.raw > /cases/case-2024-001/analysis/slack_strings.txt
# 在 Slack 空間中搜尋特定模式
grep -iab "password\|secret\|confidential\|credit.card\|ssn" \
/cases/case-2024-001/ntfs/slack_space.raw > /cases/case-2024-001/analysis/slack_keywords.txt
# 分析個別檔案的 Slack
python3 << 'PYEOF'
import struct
# 檔案 Slack 包含:
# 1. RAM slack:檔案結尾到下一個磁區邊界之間的位元組 (填充 RAM 內容或零)
# 2. Drive slack:最後一個檔案磁區之後,叢集中剩餘的磁區
# 分析特定 MFT 項目的 Slack
# 使用 Sleuth Kit 取得特定檔案的 Slack
import subprocess
# 取得檔案詳細資訊
result = subprocess.run(
['istat', '-o', '2048', '/cases/case-2024-001/images/evidence.dd', '14523'],
capture_output=True, text=True
)
print(result.stdout)
# 輸出顯示資料執行區塊 - 最後一個叢集可能包含 Slack 資料
# 計算 Slack 大小:(配置大小 - 檔案大小) 位元組
PYEOF
# 在 Slack 空間中搜尋檔案簽名 (內嵌檔案)
foremost -t jpg,pdf,zip -i /cases/case-2024-001/ntfs/slack_space.raw \
-o /cases/case-2024-001/carved/slack_carved/
# 使用 bulk_extractor 在 Slack 中尋找結構化資料
bulk_extractor -o /cases/case-2024-001/analysis/bulk_extract/ \
/cases/case-2024-001/ntfs/slack_space.raw
步驟 4:解析 USN 變更日誌
# 使用 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
步驟 5:偵測與分析替代資料串流
# 列出映像檔中所有替代資料串流
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_name
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 = 網際網路,表示檔案已下載)
# ReferrerUrl=https://惡意網站.com/payload.exe
# HostUrl=https://cdn.惡意網站.com/payload.exe
關鍵概念
| 概念 | 描述 |
|---|---|
| 檔案 Slack | 檔案結尾與叢集邊界之間未使用的空間,包含殘留資料 |
| RAM Slack | 從檔案結尾到磁區邊界的 Slack 部分 (歷史上是 RAM 內容填充) |
| MFT ($MFT) | 主檔案表 - NTFS 中繼資料資料庫,包含每個檔案的項目 |
| USN 日誌 ($UsnJrnl) | 變更日誌,記錄 NTFS 上所有檔案/目錄的修改 |
| 替代資料串流 | NTFS 功能,允許每個檔案有多個資料串流 (隱藏儲存) |
| $STANDARD_INFORMATION | MFT 屬性,時間戳可由使用者模式應用程式修改 |
| $FILE_NAME | MFT 屬性,時間戳僅可由核心修改 |
| 時間戳偽造 | 一種反鑑識技術,修改檔案時間戳以避免偵測 |
工具與系統
| 工具 | 用途 |
|---|---|
| MFTECmd | Eric Zimmerman 的 MFT 與 USN 日誌解析器,輸出為 CSV |
| MFTExplorer | 用於 MFT 分析的互動式 GUI 工具 |
| analyzeMFT | Python MFT 解析器,輸出為 CSV/JSON |
| The Sleuth Kit | 檔案系統鑑識工具組 (fls, icat, blkls, istat) |
| bulk_extractor | 從原始資料中提取特徵,包括 Slack 空間 |
| NTFS Log Tracker | 用於解析 $LogFile 交易記錄的工具 |
| streams.exe | Sysinternals 工具,用於列出 NTFS 替代資料串流 |
| 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 (偵測到隱藏資料)
Slack 空間:
Slack 總量:12.3 GB
關鍵字命中次數:45 (密碼、信用卡)
從 Slack 空間還原的檔案:23 個
可疑發現:
- 23 個檔案具有偽造的時間戳
- 5 個檔案帶有包含資料的隱藏 ADS
- USN 顯示於 2024-01-18 發生大量刪除 (反鑑識)
…
[因翻譯而截斷]