NTFS 파일 시스템 Non-Resident 파일 삭제에 대한 $LogFile 레코드 정보 기반 파일 복구
분석 개요
$LogFile은 NTFS 파일 시스템의 메타 데이터 시스템 파일 중 하나로 파일에 대한 트랜잭션(파일 생성,삭제,수정 등) 로그를 기록하며 데이터 복구를 위해 각 레코드는 Redo 와 Undo 데이터를 기록한다.
Windows 운영체제 및 NTFS 파일 시스템을 사용하고 있는 PC에서 $DATA 속성 값이 Non-Resident(비 거주형)형식으로 저장된 파일을 삭제하였을 때 남게 되는 $LogFile 트랜잭션 로그 정보 기반으로 파일을 복구하는 방식에 대해 분석한 내용을 정리하였다.
(본 포스트에서는 $LogFile에 대한 상세 구조 보다는 주요 레코드 정보를 다루기 때문에 자세한 구조는 https://flatcap.github.io/linux-ntfs/ntfs/files/logfile.html를 참고해주시면 도움이 될 것 같습니다.)
분석 도구
- NTFS Log Tracker v1.9
- HxD v2.5.0.0
- Exterro FTK Imager v4.7.3.81
테스트용 “NTFS_TEST_FILE.pdf” 파일 생성 및 삭제
[그림] NTFS_TEST_FILE.pdf 문서 파일 내용
| 파일명 |
NTFS_TEST_FILE.pdf |
| 전체 경로 |
C:\Users\codecure\Downloads\NTFS_TEST_FILE.pdf |
| 실제 크기 |
20,611 Bytes |
| 할당 크기 |
24,576 Bytes (6 Clusters, 81,216 Start Cluster Num) |
| MFT 레코드 번호 |
2,984 |
| 생성 일시 |
2026-09-03 13:46:36(UTC) |
| 마지막 수정 일시 |
2026-09-03 13:46:37(UTC) |
| 마지막 접근 일시 |
2026-09-03 14:02:04(UTC) |
[표] NTFS_TEST_FILE.pdf 파일 정보
분석을 진행하기 위해 20,611 Bytes 크기의 NTFS_TEST_FILE.pdf 파일을 생성하였다.
[그림] NTFS_TEST_FILE.pdf 파일 완전 삭제
[그림] NTFS Log Tracker 도구를 사용하여 삭제된 “NTFS_TEST_FILE.pdf” 파일 로그 조회 결과($LogFile)
“NTFS_TEST_FILE.pdf” 파일을 Shift+Delete 키를 통한 파일 완전 삭제 및 PC 종료 후 추출한 $LogFile에서 NTFS Log Tracker 도구를 사용하여 해당 파일 명을 조회한 결과 “File Deletion” 이벤트의 DeallocateFileRecordSegment레코드 LSN 1,110,407,404 가 확인되었다.
NTFS Log Tracker 도구를 통해 삭제된 파일 명을 조회하였을 때 해당 파일의 삭제 로그는 확인되지만 삭제된 파일 데이터가 저장되어 있었던 클러스터 위치 정보는 확인되지 않는다.
해당 파일 삭제 트랜잭션 로그를 자세히 확인해보기 위해 $LogFile을 HxD로 열어 NTFS Log Tracker에서 조회된 LSN 1,110,407,404 레코드를 찾아가 보았다.
[그림] LSN 1,110,407,404 레코드
$LogFile 레코드의 첫 8 bytes 값은 레코드 LSN 번호 값이 들어가기 때문에 $LogFile에서 LSN 1,110,407,404 (0x422f78ec) 값을 검색하여 해당 레코드 영역을 찾을 수 있었다.
해당 LSN 1,110,407,404 레코드 부분에 이전 레코드 LSN 번호가 존재하며 다음 레코드 또한 존재하는 것으로 확인되어 해당 삭제 트랜잭션에 대하여 연결된 레코드를 따라가면서 분석을 진행하였다.
“NTFS_TEST_FILE.pdf” Non-Resident(비 거주형) 파일 완전 삭제에 대한 $LogFile 파일에 남는 트랜잭션 레코드 패턴은 다음과 같이 확인되었다.
| 순서 |
Redo(Opcode) |
Undo(Opcode) |
세부 내용 |
| 1 |
ClearBitsInNonResidentBitMap(0x16) |
SetBitsInNonResidentBitMap(0x15) |
$Bitmap 영역에서 할당되었던 클러스터에 해당하는 비트맵 값을 0으로 수정 (클러스터 비 할당) |
| 2 |
DeleteIndexEntryFromAllocationBuffer(0x0F) |
AddIndexEntryToAllocationBuffer(0x0E) |
부모 디렉터리 내부 Index Entry 삭제 |
| 3 |
DeleteIndexEntryFromAllocationBuffer(0x0F) |
AddIndexEntryToAllocationBuffer(0x0E) |
부모 디렉터리 내부 Index Entry 삭제 |
| 4 |
DeleteIndexEntryFromAllocationBuffer(0x0F) |
AddIndexEntryToAllocationBuffer(0x0E) |
부모 디렉터리 내부 Index Entry 삭제 |
| 5 |
DeallocateFileRecordSegment(0x03) |
InitializeFileRecordSegment(0x02) |
파일 레코드 세그먼트 해제 |
| 6 |
ClearBitsInNonResidentBitMap(0x16) |
SetBitsInNonResidentBitMap(0x15) |
$MFT의 $BITMAP 속성값에서 사용하였던 MFT Entry 번호의 비트맵 값을 0으로 수정 (MFT Entry 비할당) |
| 7 |
SetNewAttributeSizes(0x0B) |
SetNewAttributeSizes(0x0B) |
$UsnJrnl:$J 파일의 $DATA 속성 크기 수정 |
| 8 |
UpdateNonResidentAttributeValue(0x08) |
None(0x00) |
$Usnjrnl:$J 저널에 파일 삭제 로그 생성 |
| 9 |
SetNewAttributeSizes(0x0B) |
SetNewAttributeSizes(0x0B) |
$UsnJrnl:$J 파일의 $DATA 속성 크기 수정 |
| 10 |
ForgetTransaction(0x1B) |
CompensationLogRecord(0x01) |
|
[표] “NTFS_TEST_FILE.pdf” Non-Resident(비 거주형) 파일 완전 삭제에 대한 $LogFile 파일에 남는 트랜잭션 레코드 패턴
“NTFS_TEST_FILE.pdf” 파일 삭제에 대한 트랜잭션 레코드는 총 10개로 확인되었다. 파일 복구 측면에서 자세히 봐야 하는 부분은 1,6번째 ClearBitsInNonResidentBitMap(0x16)/SetBitsInNonResidentBitMap(0x15) 레코드 부분이다. 해당 레코드의 Redo/Undo 데이터 부분에서 삭제된 파일이 저장되어 있었던 클러스터 위치 및 MFT Entry 번호를 계산할 수 있기 때문이다.
$Bitmap 파일의 클러스터 비트맵 비 할당 작업 레코드(0x16,0x15)
[그림] “NTFS_TEST_FILE.pdf” 파일 삭제 트랜잭션의 첫번째 0x16/0x15 레코드
“NTFS_TEST_FILE.pdf” 파일 삭제 트랜잭션의 첫번째 레코드는 NTFS Log Tracker 도구에서 확인된 1,110,407,404 LSN 레코드에 기록되어 있었던 이전 레코드 LSN을 역추적하여 이전 레코드 값이 없는 첫번째 레코드 1,110,407,319 (0x422F7897) LSN을 확인할 수 있었다.
해당 레코드는 ClearBitsInNonResidentBitMap(0x16)/SetBitsInNonResidentBitMap(0x15) 레코드로 확인되었으며 Opcode 이름을 통해서도 비트맵을 수정하는 레코드임을 확인할 수 있었다.
[그림] 수정 사항이 적용되는 VCN 값과 LCN 값
비트맵 값이 수정되는 위치 LCN 값은 785,921(0x0BFE01)이며 상대 주소인 VCN 값은 2(0x02)로 확인되었다.
[그림] MFT Entry 6번 $Bitmap 메타 데이터 파일의 $DATA 클러스터 런 리스트
MFT Entry 6번 $Bitmap 메타 데이터 파일의 $DATA 속성 클러스터 런 리스트 구조를 보면 32 FA 01 FF FD 0B 인 것으로 $Bitmap 파일은 785,919(0x0BFDFF)번 클러스터를 시작으로 연속적인 506(0x1FA)개의 클러스터에 저장되어 있는 것을 확인할 수 있었다.
ClearBitsInNonResidentBitMap(0x16)/SetBitsInNonResidentBitMap(0x15) 레코드에서 확인된 비트맵 수정 대상 LCN 785,921 클러스터는 $Bitmap 파일이 저장되어 있는 785,919 ~ 786,424 클러스터 영역이며 해당 $Bitmap 시작 클러스터 기준 2번째 클러스터임을 확인할 수 있다. 또한 0x16/0x15 레코드에 기록된 Redo/Undo 반영 대상 VCN은 $Bitmap 시작 클러스터 기준 VCN임을 확인할 수 있었다.
[그림] 0x16/0x15 레코드의 Redo/Undo 데이터에 기록되어 있는 비 할당 비트맵 위치 정보
해당 ClearBitsInNonResidentBitMap(0x16)/SetBitsInNonResidentBitMap(0x15) 레코드의 경우 Redo/Undo 데이터는 동일한 위치에 8 bytes 값을 나타내고 있다. 첫 4 bytes 값은 수정 대상 LCN값 기준 비트 오프셋 값으로 비 할당되는 비트맵 시작 위치 값을 나타낸다. 나머지 4bytes 값은 비 할당 비트맵 시작 위치 기준 연속되는 비 할당 비트맵 수를 나타낸다.
비 할당으로 수정하는 비트맵 시작 위치는 785,921(LCN)클러스터 기준 15,680번째 비트를 나타내는 것으로 Byte 오프셋으로 변환하면 785,921(LCN)클러스터 내부 15,680 / 8 = 1,960번째 바이트를 가리킨다.
[그림] $Bitmap 785,921(LCN)클러스터 내부 1,960번째 바이트 비트맵
비트맵은 비트 0과 1값을 이용하여 클러스터 할당 및 비 할당 여부 상태를 관리한다. 비 할당으로 수정된 비트맵 값을 보면 C0인 것으로 이를 비트로 나타내면 1100 0000 이다. 1 byte 비트값에서 클러스터 순서는 하위 비트부터 이며 1100 0000 임으로 총 6개 클러스터 영역이 연속적으로 비 할당 상태임을 확인할 수 있다.
$Bitmap 메타 데이터 파일은 파일 시스템 전체 클러스터 할당 정보를 비트 단위로 기록된 파일로 $Bitmap의 비트맵 시작 위치부터 비 할당으로 수정한 비트맵의 위치를 계산하여 삭제된 파일이 저장되어 있었던 클러스터 번호를 알아낼 수 있다.
[Redo/Undo 데이터 적용 대상 VCN] * ([클러스터 크기] * 8) + [비 할당 비트맵 시작 위치] = 삭제된 Non-Resident 파일의 클러스터 시작 위치
하나의 클러스터 크기는 4,096 Bytes임으로 $Bitmap의 클러스터 1개당 4,096 * 8 = 32,768개 클러스터에 대한 비트맵을 나타내고 있다. 위 레코드에서 Redo/Undo 데이터 적용 대상 VCN은 2이며 해당 클러스터 위치 기준 15,680 번째 임으로 2 * 32,768 + 15,680 = 81,216 번째 클러스터부터 연속적인 총 6개 클러스터가 삭제된 “NTFS_TEST_FILE.pdf” 파일이 저장되어 있는 클러스터임을 알아낼 수 있다.
[그림] 81,216 클러스터에 남아 있는 삭제된 “NTFS_TEST_FILE.pdf” 파일 데이터
[그림] 정상적으로 복구된 “NTFS_TEST_FILE.pdf” 파일
81,216번째 클러스터를 확인한 결과 삭제된 “NTFS_TEST_FILE.pdf” 파일의 데이터가 온전히 남아 있었으며 81,216 ~ 81,221번 연속적인 6개 클러스터를 추출하여 정상적으로 삭제된 pdf 파일을 복구할 수 있었다.
$MFT 파일의 $BITMAP 속성 비트맵 비할당 작업 레코드(0x16,0x15)
[그림] 파일 삭제 트랜잭션 6번째 레코드 비트맵 수정 0x16/0x15 레코드
Non-Resident(비 거주형) 파일 삭제 트랜잭션의 6번째 비트맵 수정 ClearBitsInNonResidentBitMap(0x16)/SetBitsInNonResidentBitMap(0x15) 레코드의 비트맵 수정 대상 LCN은 786,431(0xBFFFF) 클러스터 이며 VCN 값은 0(0x00)으로 확인되었다.
[그림] $MFT 파일의 $BITMAP(0xB0) 속성의 클러스터 런 리스트
ClearBitsInNonResidentBitMap(0x16)/SetBitsInNonResidentBitMap(0x15) 레코드의 비트맵 수정 대상 LCN 786,431(0xBFFFF)위치는 $MFT 파일의 $BITMAP(0xB0) 속성의 첫번째 클러스터 런 리스트 에서 확인된 MFT Entry 할당 정보가 기록되는 비트맵 클러스터 영역임을 확인할 수 있었다.
[그림] 0x16/0x15 레코드의 Redo/Undo 데이터에 기록되어 있는 비 할당 비트맵 위치 정보
비 할당으로 수정하는 비트맵 시작 위치는 786,431(LCN)클러스터 기준 2,984번째 비트를 나타내는 것으로 Byte 오프셋으로 변환하면 785,921(LCN)클러스터 내부 2,984 / 8 = 373번째 바이트를 가리킨다.
[그림] MFT Entry 비트맵의 785,921(LCN)클러스터 내부 373번째 바이트 비트맵
$MFT 파일의 $BITMAP 속성 값 785,921(LCN)클러스터 내부 373번째 바이트 비트맵을 확인하니 FF인 것으로 삭제되어 비 할당된 MFT Entry가 다른 파일의 MFT Entry로 재 할당 된 상태로 확인되었다.
$Bitmap 메타 데이터 파일의 비트맵 비 할당 Redo/Undo 레코드 데이터를 계산하여 삭제된 파일이 저장되어 있었던 클러스터 위치를 알아낼 수 있었던 것과 동일한 방법으로 MFT Entry의 비트맵 비 할당 Redo/Undo 레코드 데이터를 계산하여 삭제된 “NTFS_TEST_FILE.pdf” 파일의 MFT Entry 번호를 알아낼 수 있다.
[Redo/Undo 데이터 적용 대상 VCN] * ([클러스터 크기] * 8) + [비 할당 비트맵 시작 위치] = MFT Entry 번호
따라서 계산을 하게 되면 “NTFS_TEST_FILE.pdf” 파일에 대한 메타 데이터는 2,984번 MFT Entry에 할당되어 저장되어 있었음을 알아낼 수 있다.
[그림] “NTFS_TEST_FILE.pdf” 파일의 MFT Entry ($MFT 파일 기준 Offset 0x2EA000)
“NTFS_TEST_FILE.pdf” 파일 삭제 이전 $MFT 파일 기준 Offset 0x2EA000(2,984 * 1,024)위치에 해당 파일의 MFT Entry가 2,984번째로 저장되어 있었던 것으로 ClearBitsInNonResidentBitMap(0x16)/SetBitsInNonResidentBitMap(0x15)레코드 데이터를 통해 계산된 정보와 동일한 것을 확인할 수 있었다.
분석 결론
[그림] “NTFS_TEST_FILE.pdf” 파일 삭제 후 다른 파일로 재 할당된 MFT Entry
$MFT 파일의 $BITMAP 속성에서 확인했던 바와 같이 “NTFS_TEST_FILE.pdf” 파일의 MFT Entry는 삭제 이후 다른 파일로 재 할당되어 “NTFS_TEST_FILE.pdf” 파일의 MFT Entry 번호를 알아도 해당 MFT Entry의 $DATA 속성에서 “NTFS_TEST_FILE.pdf” 파일이 저장되어 있었던 클러스터 위치를 찾아 파일을 직접적으로 복구할 수 없는 상황이다.
[그림] FTK Imager 도구를 통해 복구되지 않는 “NTFS_TEST_FILE.pdf” 파일
FTK Imager 도구의 경우 MFT Entry와 해당 파일의 클러스터 영역이 온전한 경우에는 삭제된 파일이 존재하였던 경로에 "X" 아이콘으로 삭제된 파일 식별 및 복구가 가능하지만 위와 같이 삭제된 파일의 클러스터가 재 할당 되지 않고 온전히 남아있지만 MFT Entry가 다른 파일로 재 할당된 경우에는 직접적으로 복구를 지원하지 않는 것으로 확인되었다.
| 레코드 순서 |
Redo(Opcode) |
Undo(Opcode) |
확인 가능 정보 |
| 1 |
ClearBitsInNonResidentBitMap(0x16) |
SetBitsInNonResidentBitMap(0x15) |
삭제된 파일의 클러스터 주소 |
| 2 |
DeleteIndexEntryFromAllocationBuffer(0x0F) |
AddIndexEntryToAllocationBuffer(0x0E) |
부모 디렉터리 참조 주소, 삭제된 파일 명 |
| 3 |
DeleteIndexEntryFromAllocationBuffer(0x0F) |
AddIndexEntryToAllocationBuffer(0x0E) |
부모 디렉터리 참조 주소, 삭제된 파일 명 |
| 4 |
DeleteIndexEntryFromAllocationBuffer(0x0F) |
AddIndexEntryToAllocationBuffer(0x0E) |
부모 디렉터리 참조 주소, 삭제된 파일 명 |
| 6 |
ClearBitsInNonResidentBitMap(0x16) |
SetBitsInNonResidentBitMap(0x15) |
삭제된 파일의 MFT Entry 번호 |
| 8 |
UpdateNonResidentAttributeValue(0x08) |
None(0x00) |
$UsnJrnl:$J 파일에 기록된 파일 삭제 저널 로그 |
[표] $LogFile의 Non-Resident(비 거주형) 파일 삭제 트랜잭션 레코드에서 확인가능한 주요 정보
하지만 앞서 확인했던 것과 같이 $LogFile에 남아있는 파일 삭제 트랜잭션 레코드 중 $Bitmap에 대한 첫번째 ClearBitsInNonResidentBitMap(0x16)/SetBitsInNonResidentBitMap(0x15) 레코드에 기록되어 있는 클러스터 비트맵 위치 정보를 기반으로 삭제된 파일이 할당되었던 클러스터 영역을 알아낼 수 있기 때문에 해당 클러스터 영역이 재 할당 되지 않고 비 할당 영역으로 남아 있다면 파일 복구를 수행할 수 있다.
참고 문헌