4-3.4) MFT Entry의 주요 속성2(인덱스1, resident/Nonresident)
Last updated
Last updated
아래 실습은 NTFS 가상디스크를 만들어서 분석하였습니다.
실습을 직접 따라 해보고 싶으신 경우 NTFS 분석 처음부터 따라오시는 것을 추천 드립니다. > 4-3) NTFS 기초 분석
드디어 NTFS의 루트디렉토리를 분석하러 오게 되었습니다. [5번 MFT Entry] 이미 우리는 분석 방법에 대해 확인하고 왔기 때문에 도구들의 힘을 빌려 빠르게 확인해보죠!
우선 5번 MFT Entry로 가봅시다. 5번 MFT Entry를 가기 위해선 먼저 MFT Entry는 1024 bytes로 2섹터를 사용합니다. $MFT 파일 처음부분(68,392섹터)에서 MFT Entry 번호 X MFT Entry 크기(1,024) 오프셋 만큼 이동하면 되지 않을까요? 1,024 X 5 = 5,120 오프셋
해당 영역에서 NTFS MFT File Record 템플릿 적용을 해봅시다. 첫 부분에서 우클릭 - Set Template Position 설정
자! 우선 우리는 루트로 알고 있는데 혹시 이 5번 MFT Entry 의 부모 File Address를 살펴봅시다. 자 여기서 살펴보고 싶은건 Sequence Number와 Parent Directory File record number 가 몇번인지 살펴봅시다. 모두 5번으로 자기 자신을 가리키고 있네요?? 즉, 이 이상 상위 디렉토리가 없다는 것이겠지요!
우선 루트를 찾아온 이유는 당연히 우리가 만들어둔 "test.txt" 파일을 찾아가기 위해서 입니다. 그래서 $DATA를 보고 싶은데 없네..? (Attribute id : 0x80 $DATA) Attribute 90, Attribute A0 속성이 보입니다. Attribute $90($INDEX_ROOT를 살펴봅시다.)
NTFS는 파일을 빠르게 검색하기 위해 인덱스 구조로 관리하고 있다고 하네요. Binary-Tree 구조라고 하는데 일단 분석을 해보고 나중에 정리하면서 어떤 모양인지 감잡아 봅시다. 결국 슬프게도 또 속성 분석을 해야한다. (좌절..) 먼저 Attribute 0x90(Index Root)를 살펴보면 Index Root의 Header가 위치합니다.
- Attribute Type : 30 00 00 00 -> 00 00 00 30 => 30 은 $FILE_NAME의 Type ID > Index Entry 가 담는 속성의 Type ID를 담고 있습니다. 디렉토리 인덱스라면 $FILE_NAME 속성의 타입 ID인 0x30 , 속성이 아니면 0x00이다. - Collation Rule(대조규칙) : 01 00 00 00 -> 00 00 00 01 (FileName) => 파일이름 대소문자 구분없이로 정렬 [Collation Rule 종류]
- Size of Index (bytes) : 00 10 00 00 -> 00 00 10 00 => 4,096 bytes - Size of Index (Cluster) : 01 => 1 클러스터 사용 (1클러스터가 8섹터로 4,096 bytes) 인덱스 루트의 헤더는 매우 간단하다. 디렉토리 인덱스로 파일이름을 대상으로 정렬하고, 인덱스 크기가 4,096 bytes, 1 클러스터인 것을 표시하고 있습니다.
Index Root의 Index Root 를 바로 이어서 Index Node Header가 위치합니다.
- Offset to First Index Entry : 10 00 00 00 -> 00 00 00 10 (16진수) => 16 (10진수) - Total Size of the Index Entries : 28 00 00 00 -> 00 00 00 28 => 40 - Allocated size of the Index Entries : 28 00 00 00 -> 00 00 00 28 => 40 - Flags : 01 00 00 00 -> 00 00 00 01 => Index Node 는 하위 Node가 있습니다. > 00 : Index Node는 하위 Node가 하나도 없다.
01 : Index Node는 하위 Node가 있음
Index Node Header 오프셋에서 16인 위치로 가면 Index Node가 위치하는데 해당 내용을 확인하여 봅시다.
- File Reference Address For Filenames : 00 00 00 00 00 00 00 00 / END Mark 마지막 Node - Length of This Index Entry : 18 00 - Length of $FILE_NAME : 00 00 - Flag : 03 00 00 00 -> 00 00 00 03 => 00 ~~~ 0000 0011 (2진수) > 비트 0 : 하위 노드 존재 > 비트 1 : 인덱스 노드가 마지막 노드임을 나타냅니다. - VCN of Child node in $INDEX_ALLOCATION (Optional) : 00 00 00 00 00 00 00 00 (사용x) 이렇게 빈 이유는 루트에 파일 수가 적으면 $INDEX_ROOT만으로도 충분하면 내용이 들어가지만, $INDEX_ALLOCATION에 인덱스 구조를 사용할 경우 이런 현상을 보이게 됩니다. (왜 분석한거야..? 슬프다)
$INDEX_ALLOCATION 속성에 대해서 알아봅시다. - INDEX_ALLOCATION 속성은 기본적으로 Non-Resident 으로 다른 클러스터에 해당 내용을 저장하고 있습니다. Non-Resident 헤더 구조는 $DATA 구조와 동일하고, 중요한 부분만 살펴보도록 합시다.
- Data runs Offset : 48 00 -> 00 48 => 72 offset (헤더에서 72오프셋에 runlist 위치) - Data Run: 11 01 24 => 클러스터 수 : 1 / 시작 클러스터 번호 : 24(16진수) => 36 클러스터 *36클러스터에 Index 노드 헤더, Index 노드 들이 저장되어 있을 것입니다. - $I30 : DataRun 앞에 표시 되며 디렉토리 인덱스를 나타냅니다.
36번 클러스터로 이동하여 봅시다. FTK Imager를 이용하여 클러스터 이동해서 섹터번호를 확인하여도 좋고 빠르게 직접 계산 해봅시다. 파티션 시작 위치 + 클러스터 번호 X 클러스터 당 섹터 수 > 128 + 36 X 8 = 416 섹터
몇몇 도구들은 $INDEX_ALLOCATION을 분석하여 바로 접근 가능하게 해줍니다. 해당 디렉토리 내 파일 수가 많으면 $INDEX_ALLOCATION($I30)이 생성되지만, 수가 매우 적으면, 속성내에서 모두 표시가 되어 생성되지 않습니다.
$INDEX_ALLOCATION을 이동하면 Index Record Header가 위치하고 이제부터 위에서 우리가 봤던 Index Record가 뒤에 이어서 나오게 됩니다. 아쉽게도 Active Disk Editor에서는 Index 부분에 대한 템플릿이 없어서 시각적으로 보기는 불편할 듯 하네요!
위 내용을 살펴봅시다. - Signature : 고정 값으로 INDX로 표시 - Fixup Array : 28 00 -> 00 28 => 40 offset - Fixup Array Count : 09 00 -> 00 09 (9개) X 2 > Fixup 섹터의 마지막 2byte과 바꾸는 것인데 안정성 확보를 위해 다시 등장했네요
19 00 이 클러스터에 해당하는 모든 8섹터 마지막 2 Bytes에 들어가고, 다음 2byte는 그 다음 섹터의 마지막 2byte와 교체되는 식으로 저장되게 됩니다.
- $LogFile Sequence Number : $LOG_FILE과 관련되어 추후 분석 진행 해봅시다. - Index Record 의 시작 VCN : 00 00 00 00 00 00 00 00 (할당 첫번째 클러스터 위치) *VCN(Virtual Cluster Number) > VCN 0 : 인덱스 레코드가 인덱스 할당의 첫 번째 클러스터에 위치합니다
> VCN 1 : 인덱스 레코드가 인덱스 할당의 두 번째 클러스터에 위치합니다. > VCN 100 : 인덱스 레코드가 인덱스 할당의 101번째 클러스터에 위치합니다.
Index Record Header 다음에 바로 Index Node Header가 위치합니다. 위에서 살펴본 것과 동일한 것으로 다시 한번 살펴봅시다.
- Offset to First Index Entry : 40 00 00 00 -> 00 00 00 40 (16진수) => 64 - Total Size of the Index Entries : 28 09 00 00 -> 00 00 09 28 => 2,344 bytes > 실제 사용 중인 Index Node Entry 전체 크기 - Allocated Size of the Index Entries : E8 0F 00 00 -> 00 00 0F E8 => 4,072 bytes > 할당된 클러스터 용량에서 루트 헤더부분을 Index Record Header 24bytes 제외한 영역 - Flag : 00 00 00 00
Index Node Header를 기준으로 64 오프셋을 이동 해봅시다.
이동한 오프셋 기준 Index Node Entry 가 연달아 위치하게 됩니다.
- File Reference Address for File Name : 04 00 00 00 00 00 04 00 (File Record Number + File Sequence Number) - Length of This Index Entry : 68 00 -> 00 68 => 104 bytes - Length of $FILE_NAME : 52 00 -> 00 52 => 82 bytes - Flag : 00 00 00 00 (하위 노드 없음 > VCN of Child node in $INDEX_ALLOCATION 없음) - $FILE_NAME 속성 82 bytes Active Disk Editor에서 드래그하면 하단에 범위 용량을 확인할 수 있습니다.
이 엔트리의 전체 크기 또한 확인이 가능하며 $FILE_NAME이 끝난 뒤에 일부분이 남은 영역인 것을 알 수 있습니다.
$FILE_NAME 부분을 먼저 살펴보고 나머지도 확인해 봅시다. 이미 우리가 살펴보았던 $FILE_NAME의 속성이 여기에 포함되는 것입니다.
- Parent Directory File Record Number(Parent Directory File Record Number + Sequence number) >> 05 00 00 00 00 00 00 05 00 >> 5번 MFT Entry - Creation Time : 00 00 00 00 00 00 00 00 - Modified Time : 00 00 00 00 00 00 00 00 - MFT Modified Time : 00 00 00 00 00 00 00 00 - Access Time : 00 00 00 00 00 00 00 00 - 파일 할당 크기 : 00 00 00 00 00 00 00 00 - 파일 실제 크기 : 00 00 00 00 00 00 00 00 - Flag : 00 00 00 00 - Reparse Value : 00 00 00 00 - 이름 길이 : 08 (16bytes) - 이름 형식 : 03 - WIN32 & DOS (대소문자 구분 안함) - 이름 : 24 00 41 00 74 00 74 00 72 00 44 00 65 00 66 00 => $AttrDef >> 기존의 $FILE_NAME 속성이 있는데 대부분 0이고, 부모 디렉토리 File Record Number, 파일 이름 관련이 들어 있는 것을 알 수 있습니다.
File Reference Address for File Name, Parent Directory File Record Number에 대해서 제대로 살펴봅시다. File Reference Address = File Record Number = MFT Entry Number를 가리키는 같은 의미입니다. 간단하게 MFT Entry Number를 가리키는 것이라고 보면 됩니다. Sequence Number(4Bytes) + File Record Number(6 Bytes) 를 합쳐서 표현합니다. - File Reference Address for File Name : 04 00 00 00 00 00 04 00 를 보면 > File Record Number : 04 00 00 00 00 00 -> 00 00 00 00 00 04 => 4번 > Sequence Number : 04 00 -> 00 04 만약 MFT Entry 의 Sequence Value가 변하면 해당하는 모든 Index node에 있는 File reference Address에 있는 Sequence number 2byte 부분도 같이 변하게 됩니다.
몇 가지 더 살펴보도록 합시다. 상세분석을 해봤으니 이제 다른 Index Node Entry를 살펴봅시다.
위와 같이 파일명과 해당 디렉토리의 파일명과 파일명에 해당하는 MFT Entry를 확인 할 수 있습니다.
이제 처음에 만들어둔 test.txt 파일을 한번 찾아보도록 하겠습니다.
일단 영문으로 저장했기 때문에 내리다 보일 것이고, 대략 $FileName 속성 위에 MFT Entry Number가 있을 것입니다. - File Entry Number : 27 00 00 00 00 00 01 00 -> 27(16진수) => 39번 엔트리
27번 엔트리를 가보도록 하죠. $MFT 에서 39번 MFT Entry로 오프셋 이동 해보도록 하겠습니다. MFT Entry 번호 X MFT 크기 = 39 X 1,024 = 39,936 오프셋
39번 MFT Entry로 이동 후 우클릭 - Set Template Position / Tempalte 는 NTFS MFT File Recod로 설정해서 다시 한번 MFT Entry 분석을 해볼 수 있습니다. 우선 파일명과 파일 내용 부분 먼저 살펴봅시다.
[시간 정보] $STANDARD_INFORMATION 에서 시간정보 확인 가능 + 읽기전용/숨김 속성 등등..
[Data 영역] $DATA 영역으로 실제 데이터를 봅시다.. 그런데... 디스크할당 크기가 0바이트 인 것을 알 수 있다.
Resident 와 Non-Resident 차이점 속성의 헤더를 보면 Non-Resident Flag 의 값으로 Resident 속성과 Non-Resident속성을 구분합니다. [Non-Resident Flag가 0일 경우] > header 이후 속성의 내용이 이어 집니다.
[Non-Resident Flag가 1일 경우] > 속성 Header 기준에서 Offset runlist 에 위치만큼 뒤로 이동하면 runlist가 존재하고, runlist가 나타내는 클러스터에 속성의 내용이 담기게 됩니다. (*runlist는 $DATA에서 클러스터 크기, 클러스터 시작 위치 계산 했던것과 동일한 방식입니다.)
일단 위에서 우리가 실습했던 것들 중에서 $MFT, $INDEX_ALLOCATION의 MFT Entry 에 속했던$DATA 속성은 새로운 클러스터 번호를 가리키던 runlist가 있었습니다. 이렇게 runlist를 통해 새로운 클러스터를 가리키고 있는 것이 Non-Resident 속성입니다. (속성에 거주하지 않고 외부에 있어서 Non-Resident, 거주하면 resident 속성인거 같네요!) 즉, resident 타입의 $DATA 속성을 가진 test.txt 파일의 내용은 MFT Entry에 DATA가 들어가도 될 만큼 용량이 작아서겠지요!?? 그렇다면 용량 얼마까지 resident일까요? 한번 테스트를 해보도록 합시다. MFT Entry의 전체 크기는 1,024 bytes 입니다. 한번 빈 영역 크기를 계산한 다음 채워보도록 합시다. 우선 우리가 보던 MFT Entry 끝에서부터 다음 섹터 끝까지 얼마나 남았는지 살펴보겠습니다. test가 저장된 공간 4 bytes + 패딩 데이터(4 bytes)test를 덮어쓰기 때문에) MFT Entry의 마지막 FF FF FF FF 뒤부터 다음 섹터 끝까지 용량 684 bytes [참고] 테스트를 해보시면 알겠지만 4bytes의 패딩 데이터 영역은 남겨둬야 합니다. 따라서 684 + 4 - 4 = 688, 총 688 bytes 가 resident의 최대 용량으로 보입니다. (그러나 항상 이 용량이 아니라 이번 test.txt 파일만 해당 됩니다.)
이전에 사용하였던 파이썬 명령어를 통해 688 bytes a로 가득찬 파일을 생성해보도록 하겠습니다. 아래의 명령어를 입력시 test.txt 파일에 a가 688개 채워진 파일로 덮어 쓰게 됩니다. > python -c "with open('test.txt', 'w') as file: file.write('a' * 688)"
이제 Active Disk Editor 또는 FTK Imager를 재기동하고 test.txt의 MFT Enty를 살펴보도록 합시다. [처음 부터 test.txt 파일에 접근하는 순서를 다시 한번 연습해봅시다!] 1. $MFT 파일 섹터 = 파티션 시작 위치 + (NTFS의 BR 에서 Start of MFT 번호 X 클러스터 당 섹터 수) > 128 + (8,533 X 8) = 68,392 섹터 2. 루트 디렉토리의 MFT Entry 는 5번이기에 5번 MFT Entry 이동 (MFT Entry 크기 1024) > $MFT 파일 내에서 5 X 1,024 = 오프셋으로 이동 3. 루트디렉토리 MFT Entry에서 $INDEX_ALLOCATION 의 DATA 속성으로 디렉토리내 파일의 인덱스 엔트리가 있는 위치로 이동 > runlist 계산 시 36 : 36번 클러스터에 루트 디렉토리의 파일들의 MFT Entry 위치 확인 가능 4. 36 번 클러스터 이동 (파티션 시작위치 + 클러스터 번호 X 클러스터 당 섹터 수) > 128 + 36 X 8 = 416 섹터 5. test.txt 파일의 Index node Entry를 찾고, test.txt가 몇 번 MFT Entry인지 확인 > 39번 6. 다시 $MFT에서 39 MFT Entry 위치 오프셋으로 이동 > 39 X 1024 = 39,936 오프셋 ($MFT 파일 처음에서 39,936 오프셋으로 이동) 또는 > 섹터로 계산하고 싶은 경우 39 X 2 = 78 > 68,392($MFT 파일 위치) + 78 섹터 = 68,470 섹터 >> 결국 파일의 MFT Entry 넘버를 찾기 위해 이런 여정을 거쳐와야 하네요! > MFT Entry에 가득 찬 것을 알 수 있습니다. 여기서 1Bytes만 늘리면 당연하게도 MFT Entry 에는 저장이 안되기 때문에 새로운 클러스터로 할당되게 되는 것이죠!
test.txt 파일의 용량을 조금만 더 늘려서 저장해보도록 합시다. 저는 맨 뒤에 12345만 추가해서 저장해보겠습니다. 디스크 할당 크기도 생겼네요!(1클러스터 크기)
속성 $80(DATA)을 살펴보고 runlist를 살펴보면 데이터의 시작 클러스터 위치를 알 수 있습니다. 즉, 속성이 Resident 에서 Non-resident 로 변경된 것입니다.
- Data Run : 21 01 95 07 (클러스터 수 1, 시작 클러스터 위치 95 07 -> 0795 => 1,941 )
1,941 클러스터로 이동해보도록 합시다. 파티션 시작 위치 + 클러스터 번호 X 클러스터 당 섹터 수 128 + (1,941 X 8) = 15,656 섹터 다음 섹터에 추가한 데이터를 확인할 수 있습니다.
이런 식으로 파일에 접근을 위해선 루트에서는 Index Allocation 을 통해서 MFT Entry Nubmer를 찾고, 해당 MFT Entry에서 Data 속성을 분석하면 해당 속성에 있거나 또는 클러스터를 가리키는 DataRunlist 로 해당 파일의 클러스터 번호로 파일에 접근하고 관리하게 됩니다.
자 그런데 시 파일의 용량을 줄이면 다시 DATA 속성이 Non-resident 에서 resident로 돌아올까요? 다시 용량을 줄여서 저장해보니.. 여전히 디스크 할당 크기가 있는걸로 보니 Non-Resident 그대로인듯 합니다. 정말 그런지 MFT Entry에서 여러분이 직접 확인해보시기 바랍니다.