슬랙 공간 및 파일 시스템 아티팩트 분석은(는) 무엇을 하나요?
사용 시기
- 파일 시스템 슬랙 공간에서 숨겨지거나 잔여 데이터를 검색할 때
- 삭제된 파일 메타데이터에 대한 NTFS 마스터 파일 테이블(MFT) 엔트리를 분석할 때
- USN 변경 저널에서 파일 작업을 재구성할 때
- 데이터나 멀웨어를 숨기는 데 사용되는 대체 데이터 스트림(ADS)을 탐지할 때
- 표준 파일 복구를 넘어선 심층 포렌식 분석이 필요할 때
전제 조건
- NTFS 파일 시스템이 있는 포렌식 디스크 이미지
- Sleuth Kit(TSK) 도구: istat, icat, fls, blkls, blkstat
- MFT 파싱을 위한 MFTECmd (Eric Zimmerman)
- 대화형 MFT 분석을 위한 MFTExplorer
- 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
# 볼륨에서 모든 슬랙 공간 추출
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로 MFT 파싱 (Eric Zimmerman)
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
# 사용자 정의 MFT 분석 (Python)
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단계: 슬랙 공간에서 숨겨진 데이터 분석
# 슬랙 공간에서 문자열 검색
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 내용 또는 0으로 채워짐)
# 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
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 # 0 건너뛰기
# 삭제 이벤트 필터링
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단계: 대체 데이터 스트림(ADS) 탐지 및 분석
# 이미지에서 모든 대체 데이터 스트림 나열
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 = 인터넷, 파일이 다운로드되었음을 나타냄)
# ReferrerUrl=https://malicious-site.com/payload.exe
# HostUrl=https://cdn.malicious-site.com/payload.exe
핵심 개념
| 개념 | 설명 |
|---|---|
| 파일 슬랙 | 파일 끝과 클러스터 경계 사이의 사용되지 않은 공간으로 잔여 데이터 포함 |
| RAM 슬랙 | 파일 끝에서 섹터 경계까지의 슬랙 부분 (역사적으로 RAM으로 채워짐) |
| MFT ($MFT) | 마스터 파일 테이블 - 모든 파일에 대한 엔트리가 있는 NTFS 메타데이터 데이터베이스 |
| USN 저널 ($UsnJrnl) | NTFS에서 모든 파일/디렉터리 수정을 기록하는 변경 저널 |
| 대체 데이터 스트림 | 파일당 여러 데이터 스트림을 허용하는 NTFS 기능 (숨겨진 저장소) |
| $STANDARD_INFORMATION | 사용자 모드 응용 프로그램에서 수정 가능한 타임스탬프가 있는 MFT 속성 |
| $FILE_NAME | 커널에서만 수정 가능한 타임스탬프가 있는 MFT 속성 |
| 타임스탬프 변조 | 탐지를 피하기 위해 파일 타임스탬프를 수정하는 반포렌식 기술 |
도구 및 시스템
| 도구 | 목적 |
|---|---|
| MFTECmd | CSV 출력이 있는 Eric Zimmerman MFT 및 USN 저널 파서 |
| 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개
- 데이터를 포함한 숨겨진 ADS가 있는 파일 5개
- USN은 2024-01-18에 대량 삭제를 보여줌 (반포렌식)
…
[번역을 위해 잘림]