반응형

MFT 엔트리 구조에서 Attributes에 대한 내용을 다룹니다.

꽤 내용이 길수도 있습니다. 본 포스팅은 다음과 같은 내용을 다룹니다.
NTFS MFT Attributes
Resident, Non-resident
$STANDARD_INFORMATION, $FILE_NAME, $BITMAP

NTFS구조

속성을 알아보기 전에 이전에 배웠던 것을 간단하게 짚고 넘어갑시다. 기존 NTFS 구조는 아래 사진과 같이 구성되어 있습니다.

VBR에서는 Boot Sector와 NTLDF & Boot strap으로 구성되어 있었고, FAT와 마찬가지로 BPB라는 구조를 살펴보았습니다. 더 나아가 MFT를 살펴봤는데, $MFT도 NTFS에서 하나의 파일로 다뤄진다고 하였습니다. 0번 제일 처음을 사용하고 있으며 fixup array라는 구조도 살펴봤지요. 무엇보다, Signature, Sequene Number, Flags가 중요한 필드라고 설명했습니다. 이제 MFT Entry 구조에서 Attributes(속성)을 살펴볼 차례입니다.

MFT Entry 구조

MFT엔트리는 NTFS의 각 파일마다 하나씩 존재한다. 각 파일의 메타정보는 MFT 엔트리 내에 다양한 속성으로 표현된다. MFT 엔트리 크기는 1024바이트로 고정되어 있다.

MFT 엔트리 헤더, Fixup Array 다음에 속성은 End of marker가 나타날 때까지 연속적으로 이어진다. 속성은 크게 속성 헤더와 내용으로 나뉜다.

위 속성중 0x10, 0x30, 0x90, 0xA0은 폴더가 가지는 속성이고, 0x10, 0x30, 0x80은 파일이 가지는 속성이다.

위의 속성들은 각 파일의 특성에 따라 MFT 엔트리 헤더, Fixup 배열 이후에 연속적으로 온다고 했다. 일반적인 파일의 경우에는 아래 3개의 속성이 온다. Fixup 배열 이후 $STANDARD_INFORMATION, $FILE_NAME, $DATA가 순차적으로 오는 것을 볼 수 있다.

여기서 속성 크기에 따라 Resident와 Non-Resident로 나뉜다. 크기가 작은 Resident는 MFT안에 있으며, 크기가 큰 Non-Resident는 MFT밖에 있다. 별도로 클러스터를 할당해서 링크만 한다. 이것을 클러스터 런이라고 부른다. $Data 속성의 경우 파일크기가 600 바이트 미만이면 Resident, 600넘어가면 Non-Resident속성으로 된다.

Resident, Non-resident, 클러스터 런에 관한 글을 참고하자.
[파일 시스템]NTFS - 4편(Resident, Cluster Run) (tistory.com)

각 속성은 속성의 메타정보 표현을 위해 속성헤더를 가지게 되는데, 속성 헤더는 공용헤더와 속성 형식헤더로 구성되어 있습니다. 다음은 Common Header (공용헤더) 입니다.

공용헤더

- Attribute type identifier : 속성 타입 식별 값
- Length of attribute : 속성 헤더를 포함한 속성 전체 길이
- Non resident flag : Non resident 속성인지 여부(0x01값이면 Non-resident 속성이다)
- Lenght of name : 자신의 속성 이름 길이
- Offset to name : 속성 이름이 저장된 곳의 시작 위치
- Flags : 속성의 상태 표현
(0x0001 : 압축된 속성, 0x4000 : 암호화된 속성, 0x8000 : Sparse 속성)
- Attribute identifier : 고유한 식별자로 같은 속성이 여러 개일 경우 구별을 위해 사용

다음은 Resident Attribute 헤더입니다. 공용 속성 헤더(Common Attribute Header)는 바로 위에 있습니다.

Resident Attribute Header

- Size of content : 헤더 뒤에 오는 속성 내용의 크기
- Offset to content : 속성 내용이 시작하는 곳의 위치
- Indexed flag : 1값을 가진다면 인덱스된 속성($FILE_NAME 속성은 1로 설정)
- Attribute Name : 속성 이름이 존재할 경우. 없다면 속성 내용

다음은 Non-resident Attribute 헤더입니다.

Start VCN of the runlist : 속성 내용이 담긴 런리스트의 시작 VCN
- VCN : 특정 파일의 첫 번째 클러스터부터 순차적으로 부여한 번호
End VCN of the runlist : 속성 내용이 담긴 런리스트(runlist)의 끝 VCN
Offset to runlist : 속성 내부의 런리스트 시작 위치
Compression unit size : 압축 속성일 경우 압축 단위
Allocated size of attribute content : 속성 내용에 할당된 클러스터크기(클러스터 배수)
Real size of attribute content : 속성 내용의 실제 크기
Initialized size of attribute content : 속성 내용의 초기 크기
- NTFS에 존재하는 초기화된 크기의 의미는 파일이 할당될 때, 파일이 더 커질 것을 예상하고 추가적으로 공간을 더 할당하는 경우가 있다. 이 경우 순수한 파일 데이터의 크기는 초기화된 크기이고, 추가적인 공간까지 포함한 크기가 논리적인 크기이다. 대부분 초기화된 크기와 논리적 크기가 동일하다.
Attribute name : 속성 이름이 존재할 경우, 없다면 속성 내용
추가적인 것은 속성 다음에 온다.

$STANDARD_INFORMATION

각 속성을 Hex Editor로 열어서 분석을 해볼건데, 그 전에 속성을 들여다 볼 필요가 있습니다. 모든 파일에 존재하는 기본 속성으로 식별 값은 0x10이다. 속성 중 식별 값이 가장 낮기 때문에 속성 구조에서 가장 처음에 위치한다. 윈도우 NT에서는 48바이트 크기를 가지며 2000이상부터는 72바이트로 증가하였다.

$STANDARD_INFORMATION

- Windows 64-bit Timststamp : Created, Modified, MFT Modified, Last Accesed Time 총 4개
- Flag : 아래 표 참고
- Maximum number of version : 파일에서 최대로 허용하는 버전 값, 0일 경우 해당 기능이 비활성화
- Version number : 파일의 버전 번호, 버전 최대값이 0일 경우 이 값도 0
- Class ID : 인덱스된 클래스 ID
- Owner ID : 파일 소유자의 ID 값으로 $Quota 파일에서 인덱스로 사용
- Security ID : $Secure 파일의 인덱스로 사용되고 파일 접근 제어 적용시 사용
- Quota Charged : 사용자 할당량 중 해당 파일이 할당된 크기(보통 파일 크기와 동일)
- USN : 파일의 USN 값으로 $UsnJrnl에서 인덱스로 사용

$STANDARD_INFORMATION Flag

$STANDARD_INFORMATION 속성의 많은 항목 중에서 보통 0으로 셋팅되는 경우는 비활성화되어 있음을 의미한다. 디지털 포렌식적으로 의미 있는 정보는 시간 정보 4가지와, Flag, Owner ID, Security ID, USN가 중요. $STANDARD_INFORMATION의 정보를 들여다 보도록 하자. 다음은 MFT 엔트리 구조입니다.

MFT Entry 구조

테스트 NTFS 파일 시스템을 Hex Editor로 열어보았다. Fixup array 이후에 첫 번째 빨간색 0x00000001 값을 가지는 $STANDAR_INFORMATION이 옵니다. 그 다음에 주황색 네모칸을 보면 같은 값이 4개가 반복이 됩니다. (0x01D6C6C5 F53B4F53) 이 값은 $STANDARD_INFORMATION 속성의 4가지 시간정보 입니다.

그리고 이후에 나오는 0x00000003 값을 가지는 $FILE_NAME 속성에도 동일하게 (0x01D6C65C F53B4F53) 값이 4번 반복됩니다. 그리고 나서 $MFT가 유니코드로 적혀 있는 것도 확인이 됩니다.

정확한 분석을 위해서 각 속성의 헤더와 내용의 포맷을 이해해야 합니다. 근데 속성의 4가지 시간정보인데 왜 시간정보가 다 같은 값을 가지고 있을까요? 시간 정보는 MFT수정, 생성, 수정, 접근 시간을 나타냅니다. 저번에도 다뤘던 것 같지만, $MFT는 매우 빈번하게 접근 및 수정이 이루어지기 때문에 시간 정보를 갱신하지 않는 것 같습니다. 속도 차원에서도 많이 느려지겠지요. 한 번 시간을 우리가 보기 편한 시간으로 바꿔보도록 합시다.

- 생성, 수정, MFT수정, 접근 시간 : 0x01D6C6C5F53B4F53(2020년 11월 29일 23시 36분 45초, UTC +9)- Flag : 0x0006(숨긴파일 + 시스템파일)- Maximum number of version : 0x00000000 (사용 X)- Version number : 0x00000000 (사용 X)- Class ID : 0x00000000 (사용 X)- Owner ID : 0x00000000 (사용 X)- Security ID : 0x00000100- Quota Charged : 0x00000000 00000000- USN : 0x00000000 00000000

자세히 보면 $STANDARD_INFORMATION, $FILR_NAME 각각 속성에 4개의 시간 총 8개 시간이 동일합니다. 이는 파일 시스템을 포맷한 시점이라고 보시면 됩니다.

$ATTRIBUTE_LIST

$ATTRIBUTE_LIST의 크기는 가변적이라 Resident, Non-resident 둘 다 될 수 있다. $ATTRIBUTE_LIST는 파일이나 디렉토리의 속성 크기가 너무 크거나 많아지면 MFT 엔트리에 담을 수 없다고 했었다. 다 담을 수 없는 경우 MFT 엔트리에 나눠 저장하게 된다. 이 때 $ATTRIBUTE_LIST속성의 내용을 통해 어떤 속성이 어느 MFT 엔트리에 담겨있는지 알 수 있다.

속성 식별 겂은 0x20(32)로 필수 속성은 아니다. 목적은 무엇일까? 속성의 빠른 접근을 위해 존재한다.

$ATTRIBUTE_LIST

Attibute type : 속성타입
Lenght of entry : 엔트리 길이
Length of Name : 이름 길이
Offset to Name : 이름 시작 위치. 항상 0x1A값이 있음.
Start VCN of attribute : 속성 시작 VCN
File Reference Address : 속성 위치의 파일 참조 주소
Attribute ID : 속성 ID
Attribute name : 속성 이름이 존재할 경우 속성 이름

$FILE_NAME

$STANDARD_INFORMATION의 속성 값은 0x10(16)을 쓰고, $FILE_NAME은 0x30(48)을 사용합니다. 더불어 유니코드(UTF-16)로 인코딩되어 있으며, NTFS에서 빠른 탐색을 위해 만들어 둔 인덱스 구조에도 존재합니다. $STANDARD_INFORMATION과 함께 모든 파일에 기본적으로 존재하는 속성입니다. FILE_NAME=파일 이름이지만 이름외에 부가정보를 더 가지고 있습니다. $FILE_NAME 속성은 해당 MFT 엔트리 뿐만 아니라 인덱스 구조에도 생성이 되며, 이름이 변경될 때 두 속성이 모두 변경이 되지만, 단순히 파일의 속성이 변경될 때에는 인덱스 구조의 $FILE_NAME만 변경됩니다. 항상 Resident 형태도 존재하고 최소 68바이트의 크기를 가집니다.

$FILE_NAME 시간정보를 보면 알겠지만, $STANDARD_INFORMATION 4개의 시간정보 다음에 $FILE_NAME의 시간정보가 옵니다. 총 8개의 시간정보를 볼 수 있는데, 두 개의 차이점은 행위에 따라 차이가 있지만 보통은 이름 변경이면 $FILE_NAME 시간이 변경될 것이고, 나머지는  $STDINFO의 시간 정보가 갱신된다고 알고 있으면 됩니다.

MFT Entry 구조

- File Reference address parent directory(부모 디렉토리 파일 참조 주소) : 따로 포스팅 예정
- Allocated size of file : 해당 파일이 할당된 크기(클러스터 배수)
- Real size of file : 파일의 실제 크기
- Flags : $STANDARD_INFORMATION 속성중 Flag와 약간 차이가 있음. 거의 동일
- Reparse value : 해당 속성의 Reparse Point(해당 파일의 마운트, 심볼링 링크 정보 등을 저장)
- Lenght of Name ; 이름 길이
- Namespace : 이름 표현 형식(POSIX, DOS, Win32&Dos). 아래 표 참고
- File Name : 유니코드(UTF-16)로 인코딩된 파일 이름

Namespace 형식

NTFS는 파일 이름을 저장할 때 DOS형식에 맞지 않으면 $FILE_NAME 속성을 2개 저장하여 파일 이름을 2개로 표현한다. 즉, 원래 파일 이름과 DOS형식의 이름을 추가로 저장한다. 분석할 때 MFT 엔트리 내에 $FILE_NAME 속성이 하나 이상 존재한다면 파일 이름이 DOS형식에 맞지 않다는 것이다.

한 번 예시를 들어서 필드 값을 찾아보자. 파일 이름은 cluster_run.txt 라고 예시를 만들었다. 이 예시는 파일 이름을 변경하지 않고, 파일의 내용을 변경하였다. 파일 이름이 수정 되지 않고, 내용만 수정되었으니 $STANDARD_INFORMATION의 시간 정보만 바뀐 것을 알 수 있다.

File Reference address of parent directory :
- 순서번호 : 0x0005
- MFT 엔트리 주소 : 0x000000000006
Created Time : C5 D7 C3 CF E4 E1 D6 01 (2021년 1월 4일 월요일 00시 26분 30초 UTC+9)
Modified Time : 2A 81 EC D5 E4 E1 D6 01 (2021년 1월 4일 00시 26분 41초 UTC +9)
MFT modified Time : 2A 81 EC D5 E4 E1 D6 01 (2021년 1월 4일 00시 26분 41초 UTC +9)
Last Accessed Time : 2A 81 EC D5 E4 E1 D6 01 (2021년 1월 4일 00시 26분 41초 UTC +9)
Allocated size of file : 0x00 (0 바이트)
Real size of file : 0x00
Flags : 0x0x00000020 ( 일반 파일 )
Reparse value : 0x00000000
Lenght of name : 0x0F
Namespace : 0x00
File Name : cluster_run.txt

한 가지 예시를 더 보자. 지금은 파일만 바뀌어 $STANDARD_INFORMATION의 시간정보만 바뀌고 $FILE_NAME의 시간 정보는 그대로이다. 파일 이름을 바꾸면 정말 $FILE_NAME의 시간정보가 바뀌게 될까? 아래 사진을 보면 $FIME_NAME의 시간도 바뀐 것을 알 수 있다.

$BITMAP

$MFT 파일의 MFT 엔트리의 항목 중 $BITMAP은 $MFT 파일에 연속되어 있는 1024바이트의 MFT 엔트리 중 현재 사용중인 엔트리(1)와 사용하지 않는 엔트리(0)를 비트맵으로 표현한 것이다.

속성 식별 값 0xB0(176)을 가지는 속성으로 할당 정보를 표현해준다. 할당 정보를 가지는 데이터는 $MFT, $INDEX_ALLOCATION이다. 그러면 Non-resident 속성인 $BITMAP을 한 번 Hex Editor로 구경해보자.

공통된 헤더

속성 타입 식별자 : 0x000000B0(176) -> $BITMAP
속성 길이 : 0x00000005 (80) 현재 초록색 범위의 크기
Non-resident 플래그 : 0x01
속성 이름 길이 : 0x00
속성 이름 시작 위치 : 0x0040
상태 플래그 : 0x0x0000
속성 식별자 : 0x0005

Non-resident 헤더

런리스트 시작 VCN : 0x00000000 00000000
런리스트 끝 VCN : 0x00000000 00000001
런리스트 시작 위치 : 0x0004
압축 단위 크기 : 0x0000
속성 내용 할당 크기 : 0x00000000 00002000 (8192)
속성 내용 실제 크기 : 0x00000000 00001008 (4104)
속성 내용 초기화된 크기 : 0x00000000 00001008 (4104)
속성 이름 : 존재하지 않음

$MFT의 전체 MFT 엔트리를 비트맵으로 표현하기 위해서 4104바이트를 사용했다. 0으로 표기된 MFT엔트리를 모으면 현재 사용되지 않고 삭제된 MFT엔트리 이거나 할당 대기중인 엔트리이다.

그리고 런 리스트 끝 VCN이 "1"값을 가지는 것으로 보아 $BITMAP 속성을 표현하기 위해 2개의 클러스터를 사용하고 있다는 것을 확인할 수 있다. 또한 할당 크기가 8192바이트로 확인이 된다.

$DATA

처음에 설명했듯이 MFT엔트리에는 기본적으로 3개의 속성이 있다고 하였다. 그 중 하나인 $DATA는 파일의 데이터를 저장하는 속성으로 파일 데이터가 약 700바이트 보다 크다면 Non-resident 속성으로 저장된다. 그 외에는 MFT 엔트리 내에 존재하게 된다.

MFT 엔트리의 크기는 1024바이트 인 것을 알고 있다. MFT 엔트리 헤더, $STANDARD_INFORMATION, $FILE_NAME을 제외하면 약 700바이트가 남는다. 때문에 설명하는 사람들마다 600바이트, 680바이트라고 하는 분도 있으니 이 값은 가변적이라고 알아둬야 한다. 즉, 파일 데이터가 별도의 클러스터를 할당받아 저장되는 것이 아니라 MFT 엔트리 내부에 저장된 다는 것을 알아둬야 한다. 이는 만약 파일이 삭제될 경우 삭제된 파일의 데이터를 복구하기 어려운 문제도 있다. 데이터 영역은 할당 정책에 따라 데이터 쓰기 작업이 수행되는데, 방금 삭제된 파일이 덮어 쓰여질 가능성은 적지만 $MFT 내부는 순차적으로 MFT엔트리를 할당받기 때문에 연속적인 MFT 엔트리 공간에서 특정 MFT엔트리가 삭제되었을 경우 새로운 파일에 의해 해당 엔트리가 덮어써질 가능성이 적지 않다.

속성 식별값 0x80(180)을 가지는 속성으로 파일 데이터를 저장한다. 속성 헤더 이후에 바로 속성 내용인 파일 데이터 스트림이 위치하게 된다. 데이터 크기에 따라 Resident 또는 Non-resident로 존재한다. 대체 데이터 스트림(ADS, Alternate, Data Stream)을 통해 2개 이상의 데이터 스트림을 표현한다. 한 번 예시를 알아보자

$DATA가 Resident인 경우

공통된 헤더를 보면 0x80의 $DATA를 알림과 4바이트 뒤에 크기가 나오며, 8바이트 시작에서는 1이면 Non-resident, 0이면 Resident 속성인 것을 알 수 있다. 지금은 값이 0이므로 Resident속성이다.

$DATA가 Non-resident인 경우

$DATA인 0x80과 8번째 Non-resident flag가 1로 적혀있으므로 Non-resident임을 알 수 있다. Non-resident 경우에는 따로 클러스터로 할당 받는 것을 알 수 있다.
Resident, Non-resident 정보는 여기서 보자 [파일 시스템]NTFS - 4편(Resident, Cluster Run) (tistory.com)

한 번 직접 헤더를 보면서 값을 찾아보는 것도 좋을 것이다.

$DATA, LCN, VCN

NTFS에서 LCN(Logical Cluster Number)과 VCN(Virtual Cluster Number)라는 주소방식을 사용한다. LCN은 볼륨의 첫 번째 클러스터부터 순차적으로 지정되어있는 주소를 나타내고, 볼륨상 중복되는 주소가 존재하지 않는 고유 주소를 가진다. VCN은 파일의 첫 번째 클러스터부터 순차적으로 지정되어 있는 주소를 나타내며, 어떤 파일이든 첫 번째 클러스터 VCN값은 0이 되며, 순서대로 하나씩 VCN 값이 증가하게 된다. 또한, LCN처럼 고유번호가 아니라 동일 파일 내에서는 중복된 값을 가질 수 있다. 동일 파일 내에서만 다르면 된다.

즉, LCN은 볼륨의 첫 번째 클러스터를 순차적으로, 고유 주소를 가지는 것. VCN은 동일 파일 내에서만 고유하면 된다.

LCN, VCN 매핑

 

위 그림을 보면 파일의 0-2번 클러스터 정보를 가지며 VCN 0-2번과 LCN 1300-1302이 대응된다. 두 번째 클러스터 런을 보면 VCN은 3-6번 클러스터 정보를 가지고 있고 LCN은 2301-2304 클러스터와 대응된다.

$DATA ADS(Alternate Data Stream)

하나의 파일이나 디렉토리에 데이터 속성이 여러개 올 수 있으며 기본적으로 존재하는 $DATA 속성 외에 추가적으로 존재하는 속성이 ADS이다. ADS속성은 파일 크기에 포함되지 않는다.

ADS속성은 반드시 속성 이름이 있어야 하며 기본적으로 존재하는 $DATA 속성은 이름이 없어도 상관 없다. ADS는 영역식별자 및 Thumbs.dg:encryptable에 활용된다. 다음은 ADS 생성하는 방법이다. ADS는 용량에 포함되지 않는다. 즉 다른 볼륨에 저장이 되며 삭제하는 방법은 ADS삭제 도구를 이용해야 한다.

 

참고
NTFS – $DATA 속성 | FORENSIC-PROOF (forensic-proof.com)
NTFS – $ATTRIBUTE_LIST 속성 | FORENSIC-PROOF (forensic-proof.com)

반응형
반응형

NTFS 구조

NTFS의 기본 구조는 아래와 같습니다. VBR, MFT. Data Area로 이루어져 있는 것을 다시 한 번 상기시켜보도록 합시다.

1편에서 VBR에 대해서 다뤘으며, VBR은 Boot Sector와 NTLDR로 이루어져 있었습니다. 또한, 최종적으로 NTFS복원까지도 해봤습니다. VBR은 단지 부팅을 위한 영역이다. 오늘 2편에서 다룰 내용은 MFT입니다. VBR다음에 MFT가 바로 붙어있는 사진이 있지만 사실상 물리적으로 떨어져 있습니다. 또한 MFT는 VBR이후 모든 볼륨 영역 아무곳에나 위치할 수 있습니다.

MFT란

MFT 엔트리 0번은 $MFT파일을 가리킨다. $MFT파일은 MFT영역 자체의 정보를 가지고 있다. MFT파일의 메타 정보를 유지하고 있는 엔트리이다. $MFT엔트리 정보를 읽으면 전체 MFT가 할당하고 있는 클러스터 정보를 얻을 수 있다. 즉, NTFS에 접근할 때 부트 섹터의 정보를 이용하여 $MFT엔트리 정보를 얻게된다면 전체 MFT영역의 레이아웃을 알아낼 수 있다. 다시말해서 NTFS내에 존재하는 파일 및 디렉토리의 메타 정보를 얻을 수 있다. 메타 정보는 파일 이름, 생성 시간, 수정시간, 크기 등을 말한다. $MFT도 하나의 파일이며, 시간 정보, 용량이 표시된다. MFT 엔트리를 살펴보자. 아래 사진을 보면 0번은 $MFT로 시작하게 된다.

MFT레코드는 총 1024바이트이며, 포맷만 해도 생성되는 파일들도 있다. 또한 앞에 15개는 예약되어 있으며 모두 '$'를 맨 앞에 두고있다. $LogFile에서는 R,WWW를 하게 되면 하나의 트랜잭션이 생성된다. 하지만 중간에 PC가 off가 된다면 Check Disk가 뜨게 된다. 이를 롤백이라고 하고, 저널링이라고도 한다. 레지스트리 또한 저널링을 사용한다. $UsnJrnl은 $LogFile과 합쳐서 파일 시스템 로그라고 부른다.

디지털 포렌식 관점

용량이 커지면 커질수록 이미징 하는데 시간이 너무 오래 걸리게 된다. 그렇다면 복제나 이미징이 끝날 때까지 시간을 허비하고 있을 수는 없다. 일반적으로 신고부터 영장발부까지 48시간 내에 이루어져야 한다고 한다. 읽고, 쓰는 작업이 느리면 하루가 넘어갈 수도 있는데 그러면 48시간의 절반을 허비하고 그 남은 시간동안 증거를 찾아내야만 한다. 따라서, 이럴 경우 전체 데이터를 복제 및 이미징을 하기 보다는 MFT 영역만을 이미징하여 분석하는 방안이 필요하다. MFT 영역은 파일시스템에 존재하는 모든 파일 및 폴더에 대한 메타 정보를 가지고 있다. 메타 정보는 파일 이름, 숨긴 파일, 암호화, 시간 속성 등 의심되는 것들을 확인할 수 있다.

또한, MFT는 조각나있다고 한다. 처음 사용자가 파일을 몇 개 사용할지 모르기 때문이다. MFT구조 크기는 생성 파일, 폴더의 개수의 따라 차이가 난다. 실제로 물리적으로 2~3개로 조각나있다고 한다. 그러면 MFT 엔트리 구조에 대해서 알아보도록 하자.

MFT 엔트리 구조

48바이트 크기의 MFT Entry Header 다음으로 Fixup, Attribute가 위치한다. 가운데에 위치한 속성은 파일이 일반 파일, 링크 파일, 비트맵 파일 등 파일 종류에 따라서 MFT엔트리에 존재하는 속성들이 다르다. Attribute안에도 여러 세부적인 정보로 나뉘어진다. 그 다음으로 속성의 끝을 알려주는 표시가 온다. 그 다음으로는 MFT에서는 사용하지 않는 영역이 배치되어 있다.

MFT Entry Header

MFT 엔트리 헤더는 MFT 엔트리의 앞 48바이트의 정보를 나타낸다. 중요한 값은 좀 더 진한 녹색으로 표시를 했다.

참고로 MFT 엔트리를 모아둔 파일을 $MFT라고 했습니다. $MFT역시 하나의 파일이므로 $MFT파일의 내용 중 일부가 아닌 $MFT파일 자체가 MFT 엔트리입니다.

그러면 실습 USB를 가지고 MFT엔트리 헤더를 구경해봅시다. 모든 색상을 색칠하지 않고 구간별로만 나눠서 보기 편하게 했습니다.

  • Signature : "FILE"
  • Offset to fixup array : 0x0030
  • Number of entries in fixup array : 0x0003
  • $LogFile Sequence Number(LSN) : 0x00000000 00081563
  • Sequence Number : 0x0001
    부모 식별을 위해 기록한다. 특정 행위시마다 +1증가. MFT헤더에 기록.
  • Link Count : 0x0001
  • Offset to first attribute : 0x0038
  • Flags : 0x0001
  • Used size of MFT entry : 0x000001B0
  • Allocated sie of MFT entry : 0x00000400 (1024바이트)
  • File reference to base record : 0x00000000 00000000
  • Next attribute id : 0x0007
  • Align to 4B boundary : 0x0000
  • Number of this MFT  Entry : 0x00000000

Flag

Flag는 레코드가 사용 중인지 삭제된 것인지에 대한 여부를 표시하기도 한다. 사용중인 디렉토리라면 0x03, 지워지면 0x00을 나타낸다. 즉, Flag가 0이거나 2면 지워진 파일이나 폴더를 나타낸다.

Fixup Array

Fixup이라는 것은 그 자체로 "고치다" 라는 의미를 가진다. 파일시스템에서 "고친다"라는 것은 신뢰성을 높이기 위함이라고 볼 수 있다. MFT 엔트리는 1,024바이트 총 2섹터를 사용하는데, NTFS를 구성하는 데이터가 하나 이상의 섹터를 사용할 경우 섹터의 마지막 2바이트 값을 별도로 지정하고, 해당 위치에는 Fixup값 2바이트가 들어간다. 만약 섹터의 내용이 비정상적으로 변경될 때, 데이터 해석하기 전 등 오류를 사전에 찾아낼 수 있다.

위 예시를 보면 Fixup array가 위치한 곳은 0x30이라고 나타내고 있다. 0x30 위치에는 0x014A라는 값이 들어있다. 그리고 Number of entries in fixup array값이 0x0003이다. 이 값은 기본적으로 3이다. 3*2(총 6바이트)가 Fixup값에 의해 대체된 값을 저장하는 배열이 되는데, 이 3이라는 것은 MFT 엔트리가 2개의 섹터를 사용하므로 MFT 엔트리 내에 존재하는 섹터의 마지막 2바이트와 추가적으로 하나를 더 예비해 둔 것이다. MFT 엔트리를 더 많이 사용한다면 그 숫자도 증가할 것이다. 

보호된 fixup record를 읽기 전 과정

1. Update Sequence Number에 하나를 추가한다(0x0000은 건너뛰어야 함)
2. 각 섹터에 대해 마지막 2바이트를 Update Sequence Array에 복사한다.
3. 각 섹터 끝에 Update Sequence Number번호를 기록한다.
4. 마지막으로 디스크에 기록한다.

디스크에서 record 읽는 과정

1. magic number가 정확한지 확인해야 한다.
2. Update Sequence Number를 읽는다.
3. 모든 섹터의 마지막 2바이트와 비교한다.
4. 정확한 위치에 Update Sequence Array에 내용을 복사한다.

읽을 때 하나라도 실패하면 배드 섹터 및 디스크 손상, 드라이버 결함이 있을 수 있다.

MFT Record 이외에 Fixup Array를 사용하는 구조는 다음과 같다.

- 폴더와 인덱스의 INDX Record
- $LogFile의 RDRD Record
- $LogFile의 RSTR Record

3편에서는 NTFS 속성에 대해 알아보도록 하겠습니다.

필요한 정보
(파일 참조 주소)

 

참고
Fixup - Concept - NTFS Documentation (flatcap.org)
NTFS – MFT 엔트리 구조 (MFT Entry Structure) | FORENSIC-PROOF (forensic-proof.com)

반응형
반응형

이번 글에서 다루는 내용
NTFS 구조
NTFS 부트섹터
MFT 찾아가기
VBR 복원

NTFS 구조

모든 데이터는 파일 형태로 관리되며, VBR의 위치는 고정되어 있다. MFT의 시작은 일반적으로 VBR이후지만, 크기가 커지면서 데이터 영역에 추가로 할당되기도 한다. 참고로, NTFS는 N번 클러스터 * SPC하면 섹터위치로 이동한다.
MBR ~ MBR slack : ROM BIOS가 사용한다(섹터 단위).
클러스터는 OS 포맷할 때 쓴 것.

VBR(Volume Boot Record)

NTFS VBR의 구조는 다음과 같습니다. NTFS는 클러스터 단위를 사용한다고 했었습니다. VBR도 할당될 때 기본 클러스터 크기만큼(512) 할당이 됩니다. 보통 4K이므로 8개 섹터를 할당 받습니다.

Boot Sector

다음으로 Hex Editor로 열어본 NTFS 파일 시스템 입니다.

Jump Instruction : 첫 3바이트의 Jump Instruction은 NTFS "EB 52 90"이며, FAT12/16은 "EB 3C 90", FAT32는 "EB 58 90"입니다. FAT1편에 나와있습니다([파일 시스템]FAT32 - 1편(Reserved Area) (tistory.com)) BootStrap부분은 부트 섹터가 부족할 때 사용됩니다.

OEM ID(제조사 식별 값) : NTFS라고 표시. 가상 VMDK VBR도 나오곤 한다. 값이 바뀌면 마운트가 되지 않으니 수정하지 말 것. 문자열 NTFS검색으로도 찾아도 된다(하지만 무조건 부트섹터에 있는 것만은 아님). 정확도를 높이고자 하면 Jump Instruction 포함 11바이트 검색하면 정확도는 높아진다.

BIOS Parameter Block(BPB)

보통 BPB면 Bytes Per Sector부터 시작하지만 FAT에서도 그랬듯이 Jump Instruction부터 다 포함시켰다. 위 사진에는 봐야할 곳만 색칠을 해놓았다. Total Sectors는 4바이트에서 8바이트로 늘어났다. $MFT는 NTFS에서 제일 중요하다. 중요하다보니 $MFTMirr로 백업본을 두지 않았나 싶다. 모든 파일 및 폴더 정보를 가지고 있기 때문이다. 이 구조만 알면 모든 파일 테이블의 정보를 다 알 수 있다. 또한, BPB에서 중요하게 봐야될 정보는 Start Cluster for MFT 필드이다.

Bytes Per Secotr(BPS) : 보통 512
Sector Per Cluster : 보통 8(1 클러스터당 8섹터)
Reserced Sectors : NTFS는 없음(NTFS는 항상 파티션 맨 앞에 부트 섹터가 존재)
Media Description : 0xF8(고정식 디스크), 나머지 값은 플로피디스크 구분
Total Sectors : 해당 볼륨이 가지는 총 섹터 수
Start Cluster for $MFT : $MFT의 LBA 주소
Start Cluster for $MFTMirr : $MFTMirr의 LBA주소
Clusters Per MFT Record : MFT Record 크기, MFT Record는 MFT레코드의 묶음이다.
Cluster Per Index Buffer : 폴더 구조에서 사용되는 인덱스 버퍼의 크기
Volume Serial Number : 볼륨 시리얼 번호(포맷 때 마다 변경).

MFT Record : NTFS의 모든 파일은 반드시 하나의 MFT 레코드를 가진다. MFT Record로 자기 자신의 메타 정보를 표현한다.

그렇다면 이제 MFT 시작위치를 따라가볼까요? FAT편을 보고 오신 분은 물리디스크로 열어서 실습을 했었는데요, 이번에는 논리디스크로 열어서 실습하도록 하겠습니다.

MFT 클러스터 시작은 0x0C0000 클러스터부터 시작이군요. 섹터단위가 아니라서 한 번 더 계산을 해주어야 합니다. 1클러스터는 8섹터라고 했었죠? 0x0C0000 * 8을 해주면 0x600000(6,291,456)섹터가 됩니다. 만약 바이트로 넘어가고 싶다면 8섹터는 4096(0x1000)바이트이므로 0x0C0000 * 0x1000 = 0xC0000000(3,221,225,472) 값으로 이동하시면 됩니다. 아래 표를 참고해주세요. 원하시는 단위로 계산하시고 이동해주시면 됩니다. 물론 논리로 열었을 때 입니다. 물리로 여셨으면 기존 VBR위치를 더해주셔야 합니다.

위와 같이 계산하고 이동하시면 결과는 아래와 같습니다. 이 구조만 살아있다면 100% 복구가 가능합니다. 이 구조가 없다면 손상된 것이죠. 손상안된 부분만 복구가 가능하고, 나머지는 카빙해야 합니다. $MFT는 MFT 레코드의 연속이라고 했습니다. MFT 레코드의 시그니처는 FILE 이다. FILE을 시작으로 1024바이트(2섹터) 만큼 점프하면 FILE이 또 나온다. 이들을 묶은 것을 $MFT라고 한다.

좌(첫 FILE), 우(1024바이트 후)

그렇다면 MFTMirr는 어디에 있을까? BPB에서 MFT다음 8바이트가 MFTMirr 위치였다. MFTMirr의 값은 0x02이므로 섹터로 계산하면 8을 곱해주어야 한다. 즉 16섹터로 이동하면 된다.

MFTMirr는 MFT수 만큼 다 백업하지 않는다. MFT 위치가 0x0C0000으로 거의 고정되는 것처럼 MFTMirr도 특별한 이유가 없으면 2번째 클러스터에 위치한다. MFTMirr도 1024바이트 만큼 뒤에 반복적으로 4개가 나온다.

MFTMirr는 $MFT의 가장 앞쪽의 하나의 클러스터만 백업한다(4096바이트 - 4K). 제일 중요한 것은 MFT의 첫 레코드이기 때문이다.

NTFS VBR 복원

VBR-BS가 손상된 경우

백업 BS 존재 하는 경우

VBR-BS가 손상되었을 경우 볼륨 끝 백업 BS를 활용해준다. 백업 BS는 볼륨 마지막 섹터에 있으니 찾아가면 된다. 그러면 찾아가는 방법을 안내하고자 한다. 파티션 테이블 구조에서 마지막 4바이트가 크기인 것을 기억해야 한다.([Digital Forensic] MBR이란? (tistory.com)) 필자의 DOTAKY99의 파티션 테이블은 아래와 같다.

다시 설명하자면, VBR 위치에서 LBA 총 섹터 수 더하고 -1을 해주면 백업 BS가 나온다. 복사하여 그대로 붙여넣어주면 복구가 된다. 하지만 망치려고 하는 나쁜 사람들이 백업이 있다는 정보를 모르진 않을 것이다.

결론적으로 VBR BS 백업을 찾았으면 그대로 덮어써주면 복원이 된다. 중요한 부분은 백업 BS가 없을 때 이다.

백업 BS가 존재하지 않는 경우

이럴 때는 볼륨 내 BS를 검색해보는 방법을 가져야 한다. 사이즈가 크면 클수록 시간이 오래걸린다. 검색은 Jump Instruction부터 OEM ID를 포함시켜 검색하여 준다(EB 52 90 4E 54 46 53 20). 또는 직접 값을 필드에 하나하나 넣어주는 수동으로 복원하는 방법도 있다.

FAT32복원 글을 보신 분은 아시겠지만, NTFS도 마찬가지로 모든 필드를 다 채워줄 필요가 없습니다. 핵심만 잘 수동으로 입력해주면 FKT에서 마운트가 됩니다. 복구할 필드는 다음과 같습니다.

OEM ID : NTFS입니다. FAT32는 MSDOS5.0이었고, exFAT은 "EXFAT"입니다!
Bytes Per Sector : 보통 512(0x200) 입니다.
Secotors Per Cluster : 2GB이상 볼륨의 기본 클러스터 크기는 4096-byte입니다.
Total Sectors : 각 볼륨 구간을 식별하여 할당해주어야 합니다(BS탐색).
Start Cluster for $MFT : VBR이후 "FILE" 시그니처 검색하여 $MFT위치를 알아내야 합니다.
Start Cluster for $MFTMirr : $MFT와 같은 방법으로 찾아주시면 됩니다.Clusters(Bytes) Per File Record : 항상 F6(-10) 2의 10승 = 1024-byte입니다. FF가 -1로 표시 되므로 F6은 -10이 됩니다!

STEP 0 - VBR 위치 찾기

파티션 테이블에 의하면 128번 섹터에 위치하고 있다고 합니다. 백업 BS를 찾으려고 했지만 VBR위치 + 해당 파티션 크기 -1을 해도 백업본은 보이지 않습니다..!

다음은 NTFS라고 적혀 있어야 하는 곳이 말끔하게 비워져 있습니다. 이제부터 하나하나 채워나가 봅시다.

STEP 1 - OEM ID, Bytes Per Sector, Sector Per Cluster 채우기

OEM ID는 0x03~0x0A까지 채웠으며, BPS는 512바이트(0x200) 값으로 채웠고, SPC는 4096바이트 즉, 클러스터당 8섹터를 잡아먹고 있기 때문에 0x08로 채웠습니다. Reserved 는 안쓰니 당연히 0입니다.

STEP 2 - Start Cluster for $MFT, $MFTMirr 위치 찾기, Clusters Per MFT Record(F6)

$MFTMirr는 보통 2번 클러스터에 있다고 했습니다. 즉, 0x02 * 8섹터를 해주면 16섹터가 나오고, 기존 128섹터 위치에 더해주면 144섹터가 나옵니다. 이렇게 $MFTMirr도 확인해주실 수 있습니다. 한 번더 검색을 해봅시다.

MFT Mirr 위치

$MFTMirr의 첫 레코드는 $MFT 첫 부분과 같을 것이라 생각하여 아래 해당 값을 검색하여 329168섹터를 찾아냈습니다. 기존 BS위치를 빼줘야 하므로 329,168 - 128 = 329,040이 나오며 여기서 나누기 8을 해주면 클러스터 값이 됩니다. 즉, Start Cluster for $MFT는 0xA0AA값이 나오게 됩니다.

NTFS 복원 과정

마지막으로 Clusters Per MFT Record 값인 F6을 넣어주면 다음과 같이 마무리가 됩니다.

STEP 3 - Total Sectors

 

990,865 - 128 (VBR만 계산)을 계산하면 0x0F1E11 값이 나옵니다.

그러면 최종적으로 마지막 결과값은 아래와 같이 쓰여지게 됩니다.

마지막 2바이트 시그니처 55AA도 적어주는거 잊지마세요!

NTFS 2편은 MBR에 대해서 다루겠습니다!

반응형

+ Recent posts