Step 243. 디스크 포렌식: Autopsy, 삭제 파일 복구 — 지워진 것은 지워지지 않았다
Level 3 — Forensics 트랙 | 난이도 ★★★★☆ | 예상 소요 시간 5시간
전제: Step 239(시그니처, 치빙), Step 242(메모리 포렌식의 원리)를 마쳤다.
⚠️ 이 챕터의 실습은 내 랩·합법 플랫폼 전용입니다. 허가 없는 시스템에 적용하면 범죄입니다.
- 준비물: 파이썬 3(실측: 3.12.14), WSL의
file명령. - 주의: Autopsy는 이 환경에 없어 "화면 예시"로 소개합니다. 대신 삭제의 실체 — "지운다 = 표시만 바꾼다" — 를 파이썬으로 직접 만든 FAT12 디스크 이미지에서 완전 실측합니다. 마운트도 시스템 변경도 없이, 이미지 파일을 바이트로 읽기만 합니다.
휴지통을 비우면 파일은 어디로 갈까요? 정답은 — 아무데도 안 갑니다. 파일시스템은 "이 자리 써도 됨"이라는 표시만 바꾸고 데이터는 그대로 둡니다. 그래서 덮어쓰이기 전까지 복구가 가능하고, 범인이 지운 파일이 법정 증거가 됩니다. 오늘은 이 원리를 장난감 디스크 이미지를 직접 만들어 증명하고, 실전 도구 Autopsy가 보여 주는 화면이 정확히 이 원리의 시각화임을 확인합니다.
1. 학습 목표
이 챕터를 끝내면 다음을 할 수 있습니다:
- 파일시스템의 삭제가 "할당 해제"이지 "데이터 소거"가 아님을 구조로 설명한다
- FAT의 디렉터리 엔트리와 FAT 체인이 파일 위치를 기록하는 방식을 안다
- 삭제된 엔트리(0xE5)를 스캔해 파일을 바이트 단위로 복구한다
- 슬랙 공간에 옛 데이터의 잔해가 남는 이유를 설명한다
- Autopsy의 삭제 파일 뷰·키워드 검색·타임라인이 각각 무엇을 보여 주는지 안다
2. 배경 지식 — 오늘의 도구와 개념
오늘의 도구 한눈에 보기
| 구분 | 내용 |
|---|---|
| 언어·환경 | 파이썬 3 (실측: 3.12.14) + WSL file 5.45 — 이미지는 순수 파이썬으로 생성·해석 |
| 오늘의 코드 | 디렉터리 엔트리 파싱(struct.unpack), 0xE5 스캔, 클러스터 체인 추적 |
| 필요한 개념 | 클러스터, 디렉터리 엔트리, FAT 체인, 슬랙 공간, 미할당 영역 |
| 오늘의 산출물 | FAT12 이미지 + 삭제 시뮬레이션 + 복구된 파일(원본과 바이트 일치) |
2-1. 파일시스템의 장부 — 파일은 어디에 기록되는가
디스크에서 파일 하나는 세 곳에 걸쳐 기록됩니다. ① 디렉터리 엔트리: 이름, 시작 위치, 크기, 시각을 적은 장부의 한 줄. ② 할당표(FAT의 FAT 체인, NTFS의 $BITMAP): 각 구역(클러스터)이 비었는지 쓰였는지, 다음 구역은 어디인지. ③ 데이터 영역: 실제 내용.
파일을 열 때 운영체제는 ①에서 시작 위치와 크기를 읽고, ②를 따라가며 ③을 모읍니다. 이 장부 구조를 아는 것이 삭제와 복구를 이해하는 전부입니다.
2-2. 삭제의 실체 — 표시 두 개만 바뀐다
FAT에서 파일을 지우면 일어나는 일은 정확히 두 가지입니다:
- 디렉터리 엔트리의 첫 바이트를
0xE5로 바꾼다 — "이 줄은 비었다" - 할당표에서 그 파일의 클러스터들을 0으로 바꾼다 — "이 구역들은 써도 된다"
데이터 영역(③)은 아무도 손대지 않습니다. 지우기에 데이터까지 덮어쓰면 수 GB 파일도 삭제에 몇 분이 걸릴 테니, 효율을 위해 태어난 설계입니다. NTFS도 본질은 같습니다 ($MFT 엔트리의 "사용 중" 비트를 내리고 $BITMAP을 해제). 삭제는 소각이 아니라 "버려도 됨" 꼬리표 붙이기입니다. 꼬리표가 붙은 채 실물이 남아 있는 동안 — 새 데이터가 그 자리를 덮어쓰기 전까지 — 복구가 가능합니다.
2-3. 슬랙 공간 — 파일 끝과 클러스터 끝 사이
파일시스템은 클러스터(여기서는 512바이트) 단위로만 공간을 줍니다. 700바이트 파일은 클러스터 2개(1024바이트)를 받고, 뒤의 324바이트는 파일 내용이 아닌 채로 딸려 옵니다 — 이것이 슬랙 공간(slack space)입니다. 그 자리에 예전 파일의 데이터가 남아 있다면, 파일을 새로 써도 파일 끝 이후는 건드리지 않으므로 잔해가 살아남습니다. 포렌식에서 "파일에는 없는데 디스크에는 있는" 정보가 나오는 대표 경로입니다.
2-4. Autopsy — 이 원리들의 시각화 (화면 예시)
Autopsy(Sleuth Kit의 GUI)는 디스크 이미지를 뜯어 이 구조들을 화면으로 보여 줍니다. 이 환경에는 없어 화면 예시로 봅니다 (실제 실행하지 않았습니다):
# 화면 예시 — Autopsy 분석 흐름
1. New Case → Add Data Source → 디스크 이미지(.img/.E01) 선택
2. 왼쪽 트리: Views → Deleted Files ← 0xE5 엔트리·미사용 $MFT를 모아 보여 줌
3. 파일 클릭 → 하단에 내용 미리보기 → Extract로 복구
4. Keyword Search: "password" 등 전체 이미지 검색 (미할당 영역·슬랙 포함 옵션)
5. Timeline: 파일 생성/수정/삭제 시각을 시간축에 배열 → 사용자 행동 재구성
Deleted Files 뷰는 2-2의 0xE5 줄들을 모은 것이고, 미할당 영역 검색은 "꼬리표만 떼인" 데이터를 직접 뒤지는 것입니다. 오늘 파이썬으로 할 일이 이 화면들의 원리입니다.
3. 따라 하기
3-1. 장난감 디스크 만들기 — FAT12 이미지 직접 조립
실제 FAT12 규격대로 1.44MB 이미지를 바이트로 조립합니다. 부트 섹터(BPB), FAT 두 벌, 루트 디렉터리, 데이터 영역 순입니다 (전체 스크립트는 tmp_test/step243_fat.py; 핵심만 봅니다. 이 챕터의 출력은 2026-09-09 실측입니다):
import struct
SECTOR = 512
img = bytearray(2880 * SECTOR) # 1.44MB 플로피 규격
# 부트 섹터: bytes/sector, sectors/cluster, FAT 개수, root entries ...
img[510:512] = b"\x55\xaa" # 부트 시그니처
# ... FAT 초기화, SECRET.TXT(700바이트)를 클러스터 2~3에 기록,
# 디렉터리 엔트리에 (이름, 시작 클러스터 2, 크기 700) 등록,
# FAT 체인 2 → 3 → EOF(0xFFF) 설정
open("s243_floppy.img", "wb").write(bytes(img))
① 이미지 생성: s243_floppy.img (1.44MB, FAT12)
SECRET.TXT 700바이트 = 클러스터 2~3, FAT 체인 2→3→EOF
이 이미지가 진짜인지는 Step 239의 도구가 증명해 줍니다:
file s243_floppy.img
s243_floppy.img: DOS/MBR boot sector, ..., OEM-ID "KIMIFAT ", root entries 224,
sectors 2880 (volumes <=32 MB), sectors/FAT 9, sectors/track 18, ..., FAT (12 bit ...)
(2026-09-09 실측.) file이 이 바이트 덩어리를 FAT12 파일시스템으로 인식했습니다 — 우리가 규격대로 조립했다는 증거이자, 파일시스템도 결국 "약속된 자리에 약속된 바이트"라는 확인입니다.
3-2. 삭제의 실체 재현 — 두 개의 표시만 바꾼다
이제 이 이미지에서 SECRET.TXT를 "삭제"합니다. 2-2의 절차 그대로:
ent = bytearray(img[ROOT0:ROOT0+32])
ent[0] = 0xE5 # 디렉터리 엔트리 첫 바이트 → '비었음'
img[ROOT0:ROOT0+32] = ent
fat_set(2, 0); fat_set(3, 0) # FAT 체인 해제 → '써도 됨'
open("s243_deleted.img", "wb").write(bytes(img))
② 삭제 시뮬레이션: 디렉터리 첫 바이트→0xE5, FAT 체인→0 (데이터 영역은 손대지 않음)
(2026-09-09 실측.)
읽는 법: 바뀐 바이트는 정확히 1바이트(엔트리 첫 글자) + FAT 엔트리 2개뿐입니다. 데이터 영역의 700바이트는 한 바이트도 안 바뀌었습니다. 이것이 "삭제"의 전부입니다. 파일 관리자에서는 사라졌지만, 디스크에는 그대로입니다.
3-3. 복구 — 0xE5 엔트리를 찾아 체인을 되짚기
복구 도구가 하는 일을 직접 합니다. 루트 디렉터리를 훑어 0xE5로 시작하는 (삭제된) 엔트리를 찾고, 거기 남아 있는 시작 클러스터와 크기로 데이터를 읽습니다:
d = open("s243_deleted.img", "rb").read()
for i in range(224): # 루트 엔트리 224개 전수 조사
e = d[ROOT0 + i*32 : ROOT0 + (i+1)*32]
if e[0] == 0xE5 and e[11] == 0x20 and (e[26] | e[27]):
name = "?" + e[1:8].decode().strip() + "." + e[8:11].decode().strip()
clus, = struct.unpack("<H", e[26:28]) # 시작 클러스터는 살아 있다
size, = struct.unpack("<I", e[28:32]) # 크기도 살아 있다
print(f"발견: {name} 시작 클러스터 {clus}, 크기 {size}바이트")
data = d[DATA0 : DATA0 + ((size + 511)//512)*512][:size]
open("s243_recovered.txt", "wb").write(data)
print("원본과 일치:", data == SECRET)
③ 복구 시작 — 루트 디렉터리에서 0xE5 엔트리 스캔:
발견: ?ECRET.TXT 시작 클러스터 2, 크기 700바이트
복구 저장: s243_recovered.txt — 첫 줄: DH{d3l3t3_1s_n0t_3r4s3}
원본과 일치: True
(2026-09-09 실측.)
읽는 법: 세 가지를 보세요. ① 파일명 첫 글자가 ? — 첫 바이트가 0xE5로 덮였기 때문입니다. 실제 복구 도구가 보여 주는 ?ECRET.TXT 같은 이름이 바로 이것입니다. ② 시작 클러스터와 크기는 엔트리에 그대로 남아 있어서 복구의 실마리가 됩니다. ③ 복구된 파일이 원본과 바이트 단위로 일치(True) — "삭제는 소거가 아니다"의 완전한 증명입니다. 단, FAT 체인이 해제됐기에 두 번째 클러스터 이후의 위치는 "연속이었을 것"이라는 추정에 의존합니다 — 파일이 조각나 있었다면 여기서부터 복구가 어려워집니다.
3-4. 슬랙 공간의 잔해
이번엔 파일 끝 이후를 봅니다. 700바이트 파일의 마지막 클러스터 뒤쪽 324바이트 — 슬랙 공간입니다:
slack = d[DATA0 + len(SECRET) : DATA0 + 2*SECTOR]
print(slack.split(b"\x00")[0].decode(errors="replace"))
④ 슬랙 공간 확인 — 클러스터 3의 파일 끝(700) 이후 구역:
잔해 발견: OLD_PASSWORD=hunter2 (예전 파일의 잔해)
(2026-09-09 실측 — 미리 심어 둔 옛 데이터가 파일 끝 이후에 그대로 남아 있었습니다.)
읽는 법: 현재 파일 어디에도 없는 문자열이 디스크에는 있습니다. Autopsy의 키워드 검색에서 "미할당·슬랙 포함" 옵션을 켜는 이유가 이 자리입니다. 사건 현장에서 지워진 비밀번호, 덮어쓴 문서의 이전 판이 이런 틈에서 나옵니다.
3-5. 현실 보정 — 복구가 안 되는 경우들
오늘의 실험이 깨끗하게 성공한 것은 우리가 삭제 직후 아무것도 안 썼기 때문입니다. 현실의 제약을 정리합니다:
- 덮어쓰기: 해제된 클러스터에 새 데이터가 기록되면 그 부분은 끝입니다. 그래서 복구 도구들은 "삭제를 알았으면 그 드라이브를 즉시 쓰지 마라"고 합니다.
- 단편화: FAT 체인이 해제되면 다음 클러스터 위치를 잃습니다. 연속 저장이 아니었다면 헤더·푸터 치빙(Step 239)으로 돌아갑니다.
- SSD의 TRIM: 현대 SSD는 삭제 시 컨트롤러가 실제 셀을 비워 버립니다. "삭제 ≠ 소거"는 HDD 시대의 원리가 SSD에서는 반쯤 깨진다는 것 — 포렌식의 현실도 변하고 있습니다.
- 완전 삭제(secure wipe):
shred, cipher /w 같은 도구는 데이터 영역을 실제로 덮어씁니다. 삭제와 소거는 다른 동사입니다.
4. 미션과 연습문제
미션 — 두 파일짜리 사건 이미지 제작과 해부
- 3-1을 확장해 파일 두 개(
NOTE.TXT,KEY.TXT— 서로 다른 클러스터)를 기록한 이미지를 만든다 - 둘 중 하나만 "삭제"한다 (0xE5 + 체인 해제)
- 복구 스크립트를 실행해 삭제된 것만 골라 복구하고, 살아 있는 파일은 디렉터리에서 정상 열거되는 것을 함께 보인다
- (도전) 삭제된 파일의 두 번째 클러스터 위에 새 파일의 데이터를 일부 덮어써서, 복구 결과가 어떻게 망가지는지 관찰하고 기록한다
연습문제
문제 1. FAT에서 파일 삭제 시 바뀌는 것 두 가지와, 바뀌지 않는 것 하나를 말하세요.
문제 2. 복구된 파일 이름이 ?ECRET.TXT로 나오는 이유를 디렉터리 엔트리 구조로 설명하세요.
문제 3. 700바이트 파일에 2클러스터(1024바이트)가 할당됐습니다. 슬랙 공간은 몇 바이트이며, 왜 그곳에 옛 데이터가 남아 있을 수 있나요?
문제 4. 같은 "삭제"인데 SSD(TRIM)에서는 복구가 어렵고 HDD에서는 되는 이유를, 그리고 shred 같은 완전 삭제 도구가 하는 일을 각각 설명하세요.
5. 모범 답안과 완료 기준
미션 모범 답안
핵심 구조 — 두 파일의 엔트리와 체인을 나란히 등록합니다:
# NOTE.TXT → 클러스터 2, KEY.TXT → 클러스터 4 (간격을 두면 추적이 쉽다)
write_file(img, b"NOTE TXT", 2, note_data)
write_file(img, b"KEY TXT", 4, key_data)
# KEY.TXT만 삭제: 엔트리[0]=0xE5, fat_set(4, 0)
복구 스크립트는 0xE5 엔트리만 골라내고(KEY.TXT), 정상 엔트리(NOTE.TXT)는 첫 바이트가 0xE5가 아니므로 "살아 있는 파일" 목록에 나옵니다 — 이 두 목록이 나뉘는 것 자체가 Autopsy의 일반 파일 뷰와 Deleted Files 뷰의 원리입니다.
4번 도전의 예상 결과: 덮어쓴 부분부터 새 데이터가 보이고, 파일 내용이 뒤섞입니다. 원본과 일치가 False가 되는 그 지점이 "복구 한계선"이며, 치빙(Step 239)으로 헤더 부분이라도 건지는 다음 단계의 실마리가 됩니다.
검증하는 법: ① 살아 있는 파일과 삭제된 파일이 별도 목록으로 열거되는가. ② 복구 파일이 원본과 일치하는가. ③ 도전 과제에서 어느 오프셋부터 망가졌는지 기록했는가.
연습문제 해답
문제 1 해답. 바뀌는 것: ① 디렉터리 엔트리의 첫 바이트가 0xE5로, ② FAT 체인이 0으로(할당 해제). 바뀌지 않는 것: 데이터 영역의 실제 내용 — 그래서 3-3에서 원본과 바이트 일치로 복구됐습니다.
문제 2 해답. FAT의 삭제 표시가 엔트리 첫 바이트를 0xE5로 덮어쓰는 방식인데, 그 첫 바이트가 파일명의 첫 글자 자리이기 때문입니다. 나머지 7글자와 확장자는 살아 있어서 ? + ECRET.TXT로 보입니다. 복구 도구가 이름 첫 글자를 물어보는 이유가 이것입니다.
문제 3 해답. 1024 − 700 = 324바이트입니다. 파일시스템은 클러스터 단위로만 할당하고, 파일 쓰기는 파일 길이까지만 데이터를 채웁니다. 파일 끝 이후 클러스터 끝까지는 이전에 그 자리를 쓰던 데이터가 지워지지 않고 남습니다 — 아무도 그 구역을 덮어쓰지 않았으므로요.
문제 4 해답. HDD는 삭제가 장부 표시 변경이라 데이터가 자기장으로 남지만, SSD는 TRIM 명령이 내려가면 컨트롤러가 해당 블록의 셀을 실제로 비워 읽어도 0이 돌아옵니다. 완전 삭제 도구는 운영체제의 삭제를 쓰지 않고 데이터 영역을 난수·0으로 직접 덮어써서(종종 여러 번) 복구의 전제 자체를 없앱니다. "삭제(장부)"와 "소거(덮어쓰기)"는 다른 동사입니다.
완료 기준 체크리스트
- [ ] 파일이 디렉터리 엔트리·할당표·데이터 영역 세 곳에 기록됨을 설명할 수 있다
- [ ] 삭제 = 0xE5 + 체인 해제이며 데이터는 남는다는 것을 실험으로 확인했다
- [ ] 0xE5 엔트리 스캔으로 삭제 파일을 복구하고 원본과 대조했다
- [ ] 슬랙 공간의 잔해를 직접 읽어 냈다
- [ ] 복구가 실패하는 세 조건(덮어쓰기, 단편화, TRIM)을 말할 수 있다
- [ ] Autopsy의 Deleted Files·키워드 검색·타임라인의 원리를 안다
- [ ] 미션: 두 파일 사건 이미지의 제작·삭제·복구를 완료했다
6. 흔한 실수와 해결
벽 1. 복구했는데 파일이 깨져 있다
증상: 추출은 됐는데 내용이 뒤섞였습니다.
원인: 할당 해제된 클러스터 일부가 새 데이터로 덮어쓰였거나, 파일이 단편화돼 있어 "연속" 추정이 틀린 것입니다.
해결: 헤더(시그니처)가 살아 있으면 Step 239의 치빙으로 살릴 부분을 가려냅니다. "부분 복구도 증거다" — 첫 줄만 남아도 그것이 존재했다는 사실은 증명됩니다.
벽 2. 0xE5 엔트리를 찾았는데 시작 클러스터가 0이다
증상: 엔트리는 있는데 위치 정보가 비어 있습니다.
원인: 일부 운영체제·도구는 삭제 시 위치 필드까지 지우거나, 엔트리가 재활용된 것입니다.
해결: 미할당 영역 전체를 시그니처로 스캔하는 치빙으로 전환합니다. 엔트리 기반 복구가 안 되면 내용 기반 복구로 — 두 층의 도구가 있는 이유입니다.
벽 3. FAT12 엔트리 계산이 어긋난다
증상: 직접 짠 fat_get/fat_set이 이상한 값을 줍니다.
원인: FAT12는 12비트 엔트리라 두 엔트리가 3바이트를 나눠 씁니다. 짝수/홀수 클러스터의 비트 배치가 다릅니다.
해결: 짝수는 하위 12비트, 홀수는 상위 12비트 — 실습 코드의 분기가 그 처리입니다. 계산이 의심스러우면 클러스터 2에 3을 쓰고 즉시 읽어 왕복 검증하세요.
벽 4. 삭제 직후인데도 복구가 안 된다 (SSD)
증상: USB·SSD에서 지운 파일이 0으로만 읽힙니다.
원인: TRIM — 컨트롤러가 블록을 실제로 비웠습니다. OS의 삭제가 아니라 펌웨어가 소거한 것입니다.
해결: 복구 불가가 맞는 결론입니다. 포렌식 보고서에는 "TRIM으로 소거됨"이 정당한 관찰 결과입니다. 장치 종류(HDD/SSD)와 TRIM 활성 여부를 먼저 기록하는 것이 실전 절차입니다.
벽 5. Autopsy가 이미지를 못 연다
증상 (화면 예시): Add Data Source에서 이미지를 인식하지 못합니다.
원인: 이미지가 물리 디스크 통째가 아니라 파티션 하나만 덤프된 것이거나, 손상된 덤프일 수 있습니다.
해결: 파티션 이미지도 대부분 열립니다(파티션으로 추가). 그래도 안 되면 file 이미지로 정체부터 확인하세요 — Step 239의 첫 수가 여기서도 똑같이 적용됩니다.
7. 정리
오늘의 개념
| 개념 | 한 줄 설명 |
|---|---|
| 장부 구조 | 파일 = 디렉터리 엔트리 + 할당표 + 데이터 영역, 세 곳의 기록 |
| 삭제의 실체 | 0xE5 한 바이트 + 체인 해제 — 데이터는 그대로 |
| 0xE5 스캔 | 삭제 엔트리를 찾아 시작 클러스터·크기로 복구하는 원리 |
| 슬랙 공간 | 파일 끝과 클러스터 끝 사이 — 옛 데이터의 잔해 보관소 |
| 복구의 한계 | 덮어쓰기·단편화·TRIM — 세 가지가 복구를 막는다 |
| 삭제 vs 소거 | 장부 표시 변경 vs 데이터 덮어쓰기 — 다른 동사 |
오늘의 명령어·코드
| 명령·코드 | 하는 일 |
|---|---|
file 이미지 |
이미지의 파일시스템 판별 (FAT12 인식 확인) |
엔트리[0] = 0xE5 |
FAT식 삭제 — "이 줄 비었음" |
e[26:28], e[28:32] 언팩 |
삭제 엔트리에서 시작 클러스터·크기 읽기 |
| 0xE5 스캔 루프 | 삭제 파일 목록 복원 (Deleted Files 뷰의 원리) |
| Autopsy (화면 예시) | 이미지 탐색, 삭제 파일 복구, 키워드 검색, 타임라인 |
명령어보다 중요한 감각
오늘의 핵심 문장 — "지운다"는 컴퓨터에서 존재하지 않는 동사에 가깝다. 표시를 바꿀 뿐이고, 덮어쓰일 때까지 데이터는 거기 있습니다. 이 사실은 두 방향으로 작용합니다. 분석가에게는 희망(지워진 증거가 복구된다)이고, 사용자에게는 경고(중요한 파일은 삭제가 아니라 소거를 해야 한다)입니다.
그리고 이 챕터로 Forensics 트랙의 세 축 — 파일(239), 메모리(242), 디스크(243) — 가 갖춰졌습니다. 셋을 관통하는 하나의 원리를 보셨을 겁니다: 모든 기록은 장부와 실물로 나뉘고, 둘의 어긋남에 증거가 숨는다. 파일의 확장자와 시그니처, 프로세스 명부와 메모리 흔적, 디렉터리 엔트리와 데이터 영역 — 포렌식은 이 틈을 읽는 기술입니다.
전부 체크되면 Step 243 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.