Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags more
Archives
Today
Total
관리 메뉴

Piki's Play

Chapter 13. NTFS 데이터 구조 본문

포렌식/파일시스템 (파일시스템 포렌식분석)

Chapter 13. NTFS 데이터 구조

Pikigod 2020. 3. 11. 22:37

- 이 장은 NTFS 3번째 장으로서 데이터 구조체에 대해 설명한다. 앞에서는 NTFS의 기본 개념과 어떻게 분석해야 하는지 설명했다. 

- 먼저 기본 요소들의 데이터 구조체와 특정 속성들과 인덱스 타입들을 자세히 분석하고, 마지막으로 메타데이터 파일들을 다룬다. 

- NTFS에 대한 공식 명세는 없다.

 

 

13.1 기본 개념

- 이 절에서는 NTFS의 기본 데이터 구조체의 개념을 기술한다. 첫 번째 하위 절에서는 데이터 구조체의 신뢰성을 제공하는 구조체의 설계특징을 분석하고, 다음은 MFT엔트리와 속성 헤더의 데이터 구조체에 대해 설명한다.

 

 

Fixup 값

- NTFS에는 신뢰성을 향상 시키기위해 사용하는 저장기술이 있다. NTFS는 한 섹터가 넘는 길이를 갖는 데이터 구조체와 fixup 결합한다. fixup값과 큰 데이터 구조체의 마지막 두 바이트는 데이터 구조체가 디스크에 써질 때 시그니처 값으로 교체된다. 

- 그 시그니처 값을 이용해 모든 섹터의 시그니처가 같은지 확인하고 무결성을 검증하는데 사용한다. fixup은 오직 데이터 구조체에만 사용하고, 파일 내용을 포함하는 섹터가 없는 경우에는 사용되지 않는다는 것을 유념하자. 

- fixup을 사용하는 데이터 구조체는 현재의 16비트 시그니처 값을 식별하는 '헤더필드'와 원본 값들을 담고 있는 '배열'이 있다. 데이터 구조체가 디스크에 써질 때 그 시그니처 값은 1씩 증가한다. 각 섹터의 마지막 2바이트는 배열로 복사되고, 시그니처 값은 각 섹터의 마지막 2바이트에 써진다. 데이터 구조체를 읽을 때, 운영체제는 각 섹터 마지막 2바이트가 시그니처 값과 동일한지 검증하고, 배열에 원본값으로 교체한다. 

 

원래의 값들이 있는 데이터 구조체와 각 섹터의 마지막 2바이트가 fixup으로 적용된 모습

- 위 그림은 실제 값을 갖는 데이터 구조체와 디스크에 써진 버전을 보여준다. 두 번째 구조체에 섹터 마지막 2바이트가 0x0001로 변경된다. 

- 운영체제는 fixup을 손상된 섹터와 데이터 구조체를 탐지하는데 사용한다. 여러개의 섹터로 구성된 데이터 구조체 중 한 섹터만 쓰면, fixup은 시그니처와 다르게 되고 운영체제는 데이터가 손상되었다고 보고한다. 예제 파일시스템을 자세히 분석하려면 먼저 시그니처 값들을 교체해야 한다. 

 

 

MFT엔트리 (파일레코드)

- MFT (Master File Table)은 NTFS의 핵심이고, 파일과 디렉토리에 대한 엔트리를 갖는다. MFT 엔트리는 크기가 일정하고 단지 몇개의 필드들만 저장한다. 보통 엔트리 크기는 1024바이트이고, 이 크기는 부트섹터에서 저장한다. 각 MFT 엔트리는 Fixup 값들을 사용하므로 윈도우 NTFS의 데이터 구조체는 Fixup 값으로 교체된 각 섹터의 마지막에 두 바이트(시그니처)가 있다.

 

MFT 엔트리 데이터 구조체 필드는 아래와 같다

- 표준 시그니처 값은 'FILE'이지만 chkdsk가 오류를 발견하면 일부엔트리에는 'BAAD'가 된다. 다음 두 필드는 fixup 값들을 위한것이고, 그 배열은 일반적으로 바이트 42이후에 저장된다. 그 오프셋 값들은 엔트리 시작에서 상대적이다.

- LSN은 12장 "응용프로그램 범주"에서 설명했던 파일시스템 저널(또는 로그)에서 사용한다. 메타데이터가 업데이트 될 때 파일시스템에서 로그 레코드들이 만들어지고 이것들은 손상된 파일시스템을 신속하게 복구하는 기능을 한다.

- 순서값은 엔트리가 할당되거나 비할당될 때 증가하고, 운영체제에 의해서 결정된다. 링크카운트는 얼마나 많은 디렉토리들이 이 MFT 엔트리를 위한 엔트리를 가지고 있는지 보여준다. 만약 하드링크가 파일에서 생성되었다면 이 번호는 각 링크에 하나씩 증가한다.

- 엔트리 시작에서 상대적인 오프셋 값을 사용하는 파일의 첫 속성을 찾을 수 있다. 다른 모든 속성은 첫 속성 뒤에 오고 마지막 속성뒤에 파일 끝 표시자 0xffffffff가 온다. 파일에 한개 이상의 MFT엔트리가 필요하면 추가적인 MFT엔트리는 기준 엔트리의 파일 참조를 갖는다.

- 플래그 필드에는 두개의 값이 있다. 0x01비트는 엔트리가 사용중일때 설정되고 0x02는 디렉토리의 엔트리일때 설정된다. 

 

- 미가공 MFT의 엔트리를 분석해보자. 테이블을 보기위해 TSK icat을 이용하여 엔트리 0인 $MFT 파일에 대한 $DATA 속성을 확인해보자. MFT엔트리 주소 다음에 오는 속성타입 ID를 더해서 TSK에 어떤 속성이던지 지정할 수 있다. 이경우에 $DATA 속성은 128타입을 갖는다.

- 이 결과는 리틀엔디안 순서이고 따라서 적절하게 순서를 변환해야 한다. 첫 줄에서 'FILE' 시그니처를 확인 할 수 있고, 바이트 4~5는 fixup 배열이 MFT엔트리 내에 48바이트(0x0030)로 위치해 있다는 것을 나타낸다. 바이트 6~7은 배열이 3개 값을 갖는다는 것을 표시한다. 바이트 16~17은 이런 MFT 엔트리 순서 값이 1인것을 보여주는데, 이는 이 엔트리가 처음 사용됐다는 것을 의미한다. 바이트 20~21은 첫 속성이 바이트 오프셋 56(0x0038)에 위치하는 것을 알려준다.

- 바이트 22~23의 플래그들은 이 엔트리가 사용중이라는 것을 보여준다. 바이트 32~39에 있는 기준 엔트리 값들은 0이고 바이트 40~41은 다음속성 ID가 6에 할당된다는 것을 보여준다. 따라서 속성들이 ID 1에서 5에 있다는 것을 예상할 수 있다. 

- Fixup 배열은 바이트 48에서 시작하고 첫 두바이트는 0x0058 시그니처 값을 갖는다. 다음 두 바이트 값은 시그니처 값을 교체하는데 사용되는 원본 값들이다. 각 섹터의 마지막 두 바이트 510,511과 1022,1023은 각 시그니처가 0x0058인 것을 확인 할 수 있다. 그 엔트리를 처리하기 위해 이 값들을 fixup 배열 값인 0x0000으로 교체한다. fixup 값 뒤로 첫번째 속성이 바이트 56에서 시작한다. 이러한 파일의 속성들은 파일 마지막을 마지막 표시자 0xffffffff를 이용해 바이트 504에서 끝낸다. 

 

- 만약 TSK로 MFT를 보기 원한다면 icat과 함께 dd를 사용해서 정확한 위치로 건너뛸 수 있다. 블록크기를 각 MFT엔트리 크기인 1024로 설정해서 이 방법을 사용할 수 있다. 예를들어 엔트리 1234를 보기위해 다음과 같이 사용한다.

 

#icat -f ntfs ntfs1.dd 0 | dd bs=1024 skip=1234 count=1 | xxd

 

 

 

▶ 속성헤더

- MFT 엔트리는 속성들로 채워지고, 각 속성은 같은 헤더 데이터 구조체를 갖는다. 다음 그림은 전형적인 파일에 헤더위치의 도식을 보여준다. 

 

MFT 엔트리 헤더와 각 속성 예

 

- 거주와 비거주 속성의 데이터 구조체는 약간 다른데 비거주 속성에는 run정보가 있다.

첫 16바이트는 두 경우가 같다. 비거주는 뒤에 run 정보가 있다.

- 이러한 값들은 타입, 크기, 이름 위치 등의 속성에 대한 기본적인 정보를 제공한다. 크기는 MFT엔트리 다음 속성을 찾는데 사용되고 속성 다음에 0xffffffff가 존재하면 그것이 마지막이라고 인식할 수도 있다. 속성이 비거주일때 플래그는 1이다. 그 플래그 값은 속성이 압축되었는지(0x0001), 암호화되었는지(0x4000) 또는 Sparse되었는지(0x8000)를 구분한다.

속성 식별자는 이 MFT에서 속성에 대한 고유 번호이고, 이름의 오프셋은 속성 시작에서 상대적이다. 

 

거주 속성에는 아래 표와 같은 필드들이 있다.

거주 속성의 데이터 구조체

-> 위 값들은 스트림으로 불리는 속성 내용의 크기와 위치를 알려준다. 이 MFT 엔트리를 상세히 분석했을 때 속성이 바이트 56에서 시작한다는 것을 알았다. 속성 헤더 오프셋을 더욱 쉽게 결정할 수 있도록 그 예쩨에서 사용된 속성을 가져와 그 결과에서 오프셋 수를 다시 설정해야 한다.

 

-> 위 결과 첫 4바이트에서 $STANDARD_INFORMATION 속성 값 16(0x10)을 확인할 수 있다. 바이트 4~7은 크기가 96바이트(0x60)라는 것을 표시한다. 바이트 8은 이것이 거주 속성(0x00)이라는 것을, 바이트 9는 이것의 이름이 없다는 것을(0x00) 나타낸다.

플래그 아이디 값들은 바이트 12~13, 14~15에서 0으로 설정되어있다. 바이트 16~19는 속성 길이가 72바이트이며 20~21은 속성 시작이 24바이트(0x18)라는 것을 표시한다. 정상적인 검사(Sanity Check)는 그 24바이트 오프셋과 72바이트 속성 길이가 96바이트 전체와 같다는 것을 보여준다.

 

※ 비거주 속성들은 클러스터 runs의 임의의 숫자를 설명해야하기 때문에 다른 데이터 구조체를 갖는다.

(삐뚤빼뚤)

-> VCN은 8장 "파일시스템 분석"에서 정의했던 논리적 파일 주소에 대한 다른 이름이다. VCN시작과 끝 숫자들은 여러 MFT엔트리들이 한개의 속성을 설명할 필요가 있을 때 사용된다. 예를들어 $DATA속성이 매우 단편화 되어있고, 그것의 run이 MFT에 적합하지 않을 때 그것의 두번째 엔트리는 첫번째 엔트리의 마지막 VCN 다음 주소와 동일한 시작 VCN을 갖는 $DATA속성을 포함한다. "$ATTRIBUTE_LIST" 절에서 이 예제를 포함한다. 

 

- 데이터 runlist에서 오프셋은 속성 시작에서 상대적이다. Runlist 형식은 매우 효율적이지만 조금 혼란스러울 수 있다. 길이가 유동적이지만 길이가 최소 1바이트여야만 한다. 데이터구조체 첫 바이트는 상위 4비트와 하위 4비트로 구성된다. ('니블'로 알려짐) 4개의 최하위 비트들은 헤더 바이트 다음에 오는 run길이 필드의 바이트 수를 저장한다. 최상위 4비트들은 길이 필드 다음에 오는 run오프셋 필드에 있는 바이트 수를 저장한다. 

 

Run의 첫 바이트는 길이필드가 1바이트이고, 오프셋 필드는 2바이트라는 것을 보여준다.

 

- 값은 클러스터 크기의 유닛에 있고, 오프셋 필드는 이전 오프셋에서 상대적인 부호있는 값(Signed Value)이다. 예를들어 속성에서 첫 run의 오프셋은 파일시스템 시작에서 상대적이고, 두번째 run 오프셋은 이전 오프셋에서 상대적이다. 

음수의 최상위 비트가 1로 설정되고 만약 그 값을 변환하기위해 계산기에 값을 입력한다면 32또는 64비트 수를 만드는데 필요한 만큼 1을 더해야 한다. 만약 값이 0xf1이라면 0xfffffff1을 넣어야한다. (?!)

 

- 비거주 속성을 알아보기 위해서 이전에 조사했던 엔트리로 돌아와 $DATA 속성에 대해 상세하게 짚어보자. 그 속성의 내용은 아래와 같고, 오프셋 값들은 속성의 시작에서 상대적이다.

- 첫번째 4개 바이트에는 속성 값 128(0x80)이 저장되어있고, 두번째 4바이트는 전체 크기가 96바이트(0x60)라는 것을 보여준다. 바이트 8은 비거주 속성임을 나타내는 1이며, 바이트 9는 속성 이름의 길이가 0임을 나타내는 값으로 0이다. 따라서 이것은 디폴트 $DATA 속성이고 ADS는 아니다. 바이트 12~13에는 0이 저장되어있는데, 플래그들이 암호화나 압축되지 않았음을 나타낸다. 

- 비거주에 대한 정보는 바이트 16에서 시작하고, 바이트 16~23은 이 run 세트의 시작 VCN이 0인것을 보여준다. run세트에 대한 마지막 VCN이 바이트 24~31에 있고 그것들은 8431(0x20ef)로 설정되어있다. 바이트 32~33은 runlist의 시작에서 오프셋이 64바이트 (0x0040)인 것을 나타낸다. 바이트 40~47, 48~55, 56~63은 할당되는데 실제적이고 초기화된 공간을 위한 것이고, 그것들은 8634468바이트(0x0083c000)의 값으로 설정되어있다.

 

64바이트에서 다음과 같이 마지막으로 runlist를 얻을 수 있다.

-> 첫 바이트는 다른 필드들의 크기를 나타내는 상, 하위 각 4비트로 구성되어있다. 바이트 64의 하위 4비트는 run길이를 위한 필드에 2개의 바이트들이 있다는 것을 보여주고, 상위 4비트는 오프셋 필드에 3개의 바이트들이 있다는 것을 보여준다. Run길이를 위해 7872클러스터(0x1ec0) 가 있는 바이트 65~66을 조사해야 한다. 다음 3개 바이트, 바이트 67~69는 클러스터 342709(0x053ab5)인 오프셋을 위해 사용된다. 즉, 첫 run은 클러스터 342709에서 시작해서 뒤로 7872클러스터 길이만큼 위치한다.

-> 다음 run에 대한 뎅터 구조체는 이전 바이트 70이후부터 시작한다. run길이 필드는 1바이트이고 오프셋 필드는 2바이트임을 알 수 있다. 그 길이 값은 112(0x70)이고, 바이트 71에 있다. 오프셋 같은 바이트 72~73에서 7963(0x1f1b) 값이 저장되어있다. 그 오프셋은 부호있는 값이고, 이전 오프셋에서 상대적이다. 따라서 342709+7963 인 350672클러스터에서 시작하고, 112클러스터들을 위해서 확장한다.


p.s 내용자체가 복잡해서 다시 공부해봐야겠다!

개념보다는 구조체를 보여주는 느낌이라 다 그림보고 해석할 수 있는 부분이라 밑줄을 많이 안쳤다!