내용
1. 예약된 영역
1)예약된 영역
- FAT 파일 시스템에서 가장 앞쪽에 위치하고 있는 영역으로 미래를 대비해서 비워놓은 영역이다.
- FAT 16의 경우 크기가 보통 '1'이며, FAT32의 경우 '32' 값을 가진다.
- 주된 사용 목적은 부트 레코드 저장과 주요 항목 백업이며 FAT16과 FAT 32 모두 예약된 영역 첫 번째 섹터에 반드시 부트 레코드를 담고 있어야 한다.(볼륨의 첫번째 섹터 이기도 하다.)
- FAT32의 경우 예약된 영역 2번째 섹터에 FSInfo 구조체를 저장하며, 6번째 섹터에 부트 레코드를 백업해 놓고 있다. 만약 첫 번째 섹터에 부트 레코드가 망가지면 백업해 놓은 부트 레코드를 이용해서 망가진 정보를 복구 할 수 있다.
[예약된 영역의 섹터 사용 위치 및 용도]
파일시스템 |
예약된 영역 크기 |
사용 용도 |
FAT16 |
보통 1 섹터 |
- 예약된 영역의 첫 번째 섹터에 부트 레코드 저장 - 나머지 섹터들은 사용되지 않지만 '0'으로 초기화 시킴 |
FAT32 |
보통 32섹터 |
- 예약된 영역의 첫 번째 섹터에 부트 레코드 저장 - 예약된 영역의 두 번째 섹터에 FSInfo 구조체를 저장함 - 예약된 영역의 6 번째 섹터에 부트레코드 사본 저장 - 나머지 섹터들은 사용되지 않지만 '0'으로 초기화 |
2)FileSystem Information 구조체 분석
[FSInfo 영역 출력 화면]
[FSInfor 항목]
이름 |
Lead Signature |
||||
위치 (Offset) |
0~3 |
크기 (Size) |
4 Byte |
일반적인 값 (Value) |
항상 0x41615252 |
설명 |
FSInfo 섹터라는 것을 표시해주는 항목으로 항상 값이 일정하다. 0x41615252(aARR) 위 그림에선 Little endian의 의해 거꾸로 표시 된 것을 알 수 있다. |
이름 |
Reserved1 |
||||
위치 (Offset) |
4~483 |
크기 (Size) |
479 Byte |
일반적인 값 (Value) |
0 |
설명 |
미래를 위해 예약된 영역으로, 섹터의 대부분을 차지하며 전부 0으로 채워져 있고, 사용되지 않는다. |
이름 |
Struct Signature |
||||
위치 (Offset) |
484~487 |
크기 (Size) |
4 Byte |
일반적인 값 (Value) |
항상 0x61417272 |
설명 |
FSInfo 섹터라는 것을 표시해주는 또 하나의 항목으로 항상 값이 일정하다. 0x61417272(aArr) 위 그림에선 Little Endian의 의해 거꾸로 표시 된 것을 알 수 있다. |
이름 |
Free Cluster Count |
||||
위치 (Offset) |
488~491 |
크기 (Size) |
4 Byte |
일반적인 값 (Value) |
가변적 |
설명 |
볼륨상 존재하는 빈 클러스터의 수를 저장. 이항 목이 0xFFFFFFFF 라면 빈 클러스터의 수를 알 수 없는 상태 이므로 계산 되어야 한다. 정확한 값은 아니므로 참고만 하는 것이 좋다. |
이름 |
Next Free Cluster |
||||
위치 (Offset) |
492~495 |
크기 (Size) |
4 Byte |
일반적인 값 (Value) |
가변적 |
설명 |
해당 볼륨에서 보다 빨리 빈 클러스터가 있는 곳을 알아내기 위해 항목으로 마지막으로 할당된 클러스터의 값이 저장된다. 빈 클러스터를 찾기 위해 FAT 영역을 처음부터 뒤질 필요 없이 여기에 적혀 있는 클러스터의 뒤쪽부터 찾으면 된다. 0xFFFFFFFF라면 이 항목에 아직 아무 것도 갱신된 사항이 없다는 의미로 어쩔 수 없이 처음 클러스터부터 찾기 시작해야 한다. 이 항목 역시 반드시 정확한 값이 아닐 수 있으므로 참고로만 이용하는 것이 좋다. |
이름 |
Reserved 2 |
||||
위치 (Offset) |
496~507 |
크기 (Size) |
12 Byte |
일반적인 값 (Value) |
0 |
설명 |
미래를 위해 예약된 영역으로, 섹터의 대부분을 차지하며 전부 0으로 채워져 있고, 사용되지 않는다. |
이름 |
Trail Signature |
||||
위치 (Offset) |
508~511 |
크기 (Size) |
4 Byte |
일반적인 값 (Value) |
0xAA550000 |
설명 |
FSInfo 섹터라는 것을 표시해 주는 또 하나의 항목으로 언제나 값은 0xAA550000으로 일정 해야 한다. 위 그림에선 Little Endian에 의해 거꾸로 된 것을 알 수 있다. |
2.FAT 영역
1)FAT 영역 분석
- FAT 파일시스템에서 매우 중요한 정보를 담고 있는 영역으로 주로 파일(or 디렉토리)의 할당을 관리하는 것이 목적이다.
- FAT 영역은 특별한 구조체가 존재하는 것이 아니라 클러스터의 상태 값을 담고 있는 공간이 연속되는 단순한 형태이다.
[FAT32 파일시스템의 FAT1 영역]
- FAT32 파일시스템에서 FAT #1번 영역으로 #2는 #1의 복사본 이므로 동일한 값을 가진다.
- 각각의 4Byte를 'FAT Entry'라고 불리며 정보들이 담기게 된다.
[FAT32 파일시스템의 FAT 영역 항목]
- 0번 Entry와 1번 Entry를 제외한 나머지는 모두 클러스터를 나타내고 있다.
0번과 1번 클러스터에 해당하는 Entry가 없는 것을 알 수 있다. 실제로 클러스터 0번과 1번은 존재하지 않는다. FAT 파일시스템이 FAT 영역의 0번과 1번 Entry를 다른 용도로 사용하면서 생긴 현상이다. |
2) FAT 클러스터 연결 방식
- FAT 영역은 단일 링크드 리스트(Linked List)로 표현하고 있으며, 각각의 FAT Entry들은 자신의 다음 클러스터(Next Cluster) 값을 담고 있다.
- 위 그림을 보고 한번 알아보자.
Cluster 2의 위치한 값을 보게 되면 Little Endian으로 되어 있기 때문에 0x00000009 인걸 알 수 있다. 즉 Cluster 9를 나타낸다.
Cluster 9를 보게 되면 0x0000000A (10진수로10) 을 나타내어 Cluster 10을 나타내고 있다.
Cluster 10을 보게 되면 0x0000000B (10진수로 11)을 나타내어 Cluster 11을 나타내고 있다.
Cluster 11을 보게 되면 0x00000011 (10진수로 17)을 나타내어 Cluster 17을 나타내고 있다.
Cluster 17을 보게 되면 0x0FFFFFFF을 나타내며 이 것은 해당 파일(또는 디렉토리)의 링크드 리스트 끝을 표시 하고 있는 것으로 EOC(End Of Cluster) 값이라고 한다.
아래 같은 색상에 Linked List를 한번 파악 해보자.
- 루트 디렉토리의 시작 클러스터의 위치는 FAT 32의 경우 부트 레코드의 Root Cluster 항목에 있고, FAT 16의 경우 무조건 FAT 영역 바로 뒤에 오는 것은 정해져 있지만, 다른 일반 파일이나 디렉토리의 경우 시작 클러스터는 Directory Entry를 통해서 알 수 있게 된다.
3)FAT Entry의 상태 값
- 위에서 알아본 것을 확인 하자면 FAT Entry에 값은 자신의 다음 클러스터 번호이고 자신이 마지막 클러스터라면 0x0FFFFFFF가 들어가는 것을 알게 되었지만 실제로 Entry에 들어가는 상태 값을 알아 보자.
FAT16 |
FAT32 |
설명 |
0x0000 |
0x?0000000 |
비어있는 클러스터(Free Cluster), 누구에게도 사용되지 않고 연결되길 대기하고 있는 클러스터 |
0x0001 |
0x?0000001 |
예약된 클러스터(Reserved Cluster), 아직 특별 의미는 없으나 앞으로 사용할 값으로 예약됨 |
0x0002 ~ 0xEEEF |
0x?00000002 ~ 0x?FFFFFEF |
사용하고 있는 클러스터(Allocated Cluster), 누군가에게 연결되어 내용을 담고 있는 클러스터, FAT Entry에 담긴 값은 자신의 다음 클러스터 번호이다. |
0xFFF0 ~ 0xFFF6 |
0x?FFFFFF0 ~ 0x?FFFFFF6 |
예약된 클러스터(Reserved Cluster), 아직 특별한 의미는 없으나 앞으로 사용할 값으로 예약됨 |
0xFFF7 |
0x?FFFFFF7 |
불량 클러스터(Bad Cluster), 이 클러스터에 속해 있는 한 개 이상의 섹터에 불량 섹터(Bad Sector)가 발생하여 더 이상 데이터를 저장할 능력이 없는 클러스터, 이러한 불량 클러스터는 사용하지 말아야 한다. |
0xFFF8 ~ 0xFFFF |
0x?FFFFFF8 ~ 0x?FFFFFFF |
파일의 마지막 클러스터(EOC), 이 클러스터가 저장한 내용은 데이터의 끝으로 더 이상 뒤에 연결된 클러스터가 없다. |
- 0번 클러스터와 1번 클러스터는 존재 하지 않기 때문에 비어 있거나 예약된 클러스터로 사용될 수 있다.
[FAT32 파일 시스템 FAT Entry 형태]
- FAT32의 FAT Entry 경우 32bit이지만 실제 상위 4bit(Reserved)는 사용되지 않고 어떤 값이 와도 무시 된다. (0x30000000 이나 0x0000000 이나 전부 비어있는 클러스터를 의미)
- Reserved 영역의 경우 어떤 값이 와도 무시되기 때문에 주로 0으로 채운다. 따라서 FAT32가 표현할 수 있는 클러스터의 개수는 228개가 되는 것이다.
- Windows의 경우 FAT Entry의 상위 4비트는 건들지 않는다. 만약 0x30000000으로 비어있는 클러스터로 저장을 했다면 0x3을 보존하여 불량 클러스터가 의미하는 0x?FFFFFF7을 쓰면 0x3FFFFFF7이라는 갑이 저장된다. 상위 4비트의 값을 바꾸는 경우는 볼륨을 포맷하는 경우뿐이다.
4) FAT32와 FAT16의 FAT영역 차이
- FAT 16의 경우 Entry 크기가 16bit(2byte)이고, FAT32는 (4Byte)이다.
- 구조는 동일하나 표현할 수 있는 클러스터 수가 다르며 FAT32가 더 많은 클러스터 수를 표현할 수 있기 때문에 대용량 하드에 적용 가능하다.
- FAT Entry 0번의 경우 Media Type를 저장하는 용도로 사용되고 있는 것을 알 수 있었다.
[FAT32 파일 시스템의 FAT Entry 0번의 형태]
- 위 그림처럼 FAT Entry 0번은 하위 8bit를 Boot Record의 Media 값과 동일하게 적어주고 나머지는 0로 채워주게 된다.
- Boot Record에 저장된 Media 항목의 값이 고정식 디스크를 뜻하는 0xF8 이라면 FAT 32의 0번 Entry값은 0x?FFFFFF8 이 될 것이고 FAT 16의 0번 Entry는 0xFFF8이 될 것이다.
[FAT Entry 1번 사용비트]
값 |
FAT 16 |
FAT32 |
Clean Shutdown Bit Mask |
0x8000 |
0x8000000 |
Hard Error Bit Mask |
0x4000 |
0x4000000 |
- Clean Shutdown Bit Mask : FAT Entry 1번의 최상위 비트로 이 비트 값이 0이라면 OS가 볼륨을 탑재했던 마지막 순간에 볼륨을 올바르게 분리 하지 않았다는 것을 나타낸다. 이 비트의 값이 1이라면 정상이다.
- Hard Error Bit Mask : 이 비트는 FAT Entry 1번의 최상위 2번째 비트로, 이 비트 값이 0이면 OS가 마지막 탑재 시 볼륨상에서 I/O 에러가 발생한 것이다. 이것의 경우 볼륨상 몇몇 섹터가 불량이 되었다는 것을 나타낸다. 이 비트의 값이 1이라면 정상이다.
- FAT Entry 0번과 FAT Entry 1번에 저장되는 Media Type와 Volume State는 꼭 구현되지 않아도 파일시스템의 구현에 지장이 없다.
3. 데이터 영역
1) 데이터 영역
- 데이터들이 저장되는 영역으로 볼륨에서 대부분을 차지하고 있으며 실제 이 영역을 관리하기 위한 보조적인 영역이다.
[Data 영역 구조]
- 다른 영역과 달리 효율성을 위해 논리적인 영역 분할 단위인 클러스터로 접근 된다.
- 클러스터에는 파일이나 디렉토리의 내용을 담을 수 있으며, 데이터의 크기가 클 경우 여러 클러스터에 나누어서 담을 수 있다.
- 그러나 빈 공간이 남는다고 해서 클러스터 하나에 여러 데이터의 내용을 같이 넣을 수는 없다.
- Data 영역에는 Directory Entry 불리는 구조체를 담고 있는 영역과 파일 이라는 2개의 형태의 데이터가 저장된다.
- Directory는 담고 있는 내용이 Directory Entry 라는 것만 빼고 파일과 형태나 접근 방법이 동일하다.
- Directory Entry에 저장되는 내용은 자신에게 속한 파일들과 자신의 하위 디렉토리에 대한 정보들이 있다.
2) 원하는 파일 찾기
[루트 디렉토리의 Entry]
\DOC\A.TXT 파일의 내용을 찾을 경우
첫번째, 루트디렉토리가 담고 있는 Directory Entry를 하나씩 조사해서 'DOC'라는 디렉토리가 있는지 알아낸다.
두번째, Directory Entry가 DOC라는 디렉토리 정보와 Cluster 정보를 담고 있다.
[DOC 디렉토리 Entry]
세번째, DOC 디렉토리의 Directory Entry를 조사하면 A.TXT 파일의 클러스터를 찾을 수 있다.
네번째, A.TXT의 클러스터(0x4)를 읽어서 화면에 출력하면 되는 것이다.
3) Directory Entry 항목
[루트 디렉토리 출력 화면]
- 32Byte 단위로 Directory Entry 하나가 구성되어 이다. 즉 1섹터당 Directory Entry를 16개 담을 수 있게 된다.
[Directory Entry 항목]
이름 |
Name |
||||
위치 (Offset) |
0~7 |
크기 (Size) |
8 Byte |
일반적인 값 (Value) |
파일명 |
설명 |
파일 이름 또는 디렉토리 이름을 적는 항목으로 최대 8자리까지 적을 수 있으며, 대문자만 넣을 수 있다. 만약 이름이 남는 경우 0x20이라는 값으로 채운다. Null(0x0)으로 채우는 것이 아니다. |
[Name[0]의 값에 대한 의미]
Name[0]의 값 |
설명 |
0xE5 |
해당 디렉토리 엔트리가 담고 있는 데이터는 삭제 된 데이터라는 것을 의미하며, FAT 파일시스템에서는 파일이나 디렉토리가 삭제되면 해당 디렉토리 엔트리를 초기화 하는 것이 아니라 Name 항목의 첫 번째 바이트를 0xE5로 바꾼다. 즉 파일이 삭제 되어도 복구가 가능하다. |
0x00 |
해당 디렉토리 엔트리가 비어있고, 뒤에 오는 디렉토리 엔트리들도 전부 비어있다는 의미로 디렉토리 엔트리를 검색하다가 0x00을 만나면 더 이상 검색하지 않아도 된다. |
0x05 |
이 바이트에 대한 실제 파일 이름 문자는 0xE5이다. 삭제된 파일데이터를 의미하는 0xE5는 일본문자(간지)의 집한 선두 바이트 값으로 일본어를 파일명으로 하면 삭제된 데이터로 인식을 해버리는데 0x05라는 것을 일본어 문자 선두 바이트 0xE5로 표현하고 있다. |
- Name의 첫 번째 바이트에는 0x20(스페이스)가 올 수 없으며, 0x05를 제외한 0x20보다 작은 값은 불가능 하다.
이름 |
Extender |
||||
위치 (Offset) |
8~10 |
크기 (Size) |
3 Byte |
일반적인 값 (Value) |
확장자 |
설명 |
확장자를 넣는 항목으로 최대 3자리까지 적을 수 있으며 반드시 대문자만 가능하다. 확장자가 2자리 이하 일 경우 0x20이라는 값으로 채워 넣어야 한다. 디렉토리의 경우 확장자가 없기 때문에 0x20값으로 전부 채우게 된다. |
이름 |
Attribute |
||||
위치 (Offset) |
11 |
크기 (Size) |
1 Byte |
일반적인 값 (Value) |
속성 항목 확인 |
설명 |
Directory Entry가 어떤 용도 인지 결정하는 항목으로 해당 Directory Entry가 담고 있는 것이 파일인지 디렉토리인지는 바로 이 항목에 저장된 값을 통해 결정된다. |
[속성 항목 값]
속성 값 |
속성 이름 |
설명 |
0x01 |
Read Only |
읽기 전용 파일. 이 속성이 걸려 있는 파일은 쓰기를 막도록 코드를 짜야 한다. |
0x02 |
Hidden |
보통의 사용자로부터 파일을 보여 주지 않는다. |
0x04 |
System |
OS에서 사용하는 파일 |
0x08 |
Volume Label |
이 파일의 이름이 볼륨레이블이 된다. 이 속성은 반드시 루트에 있어야 하며, 1개만 존재해야 하고, 이 속성을 가진 파일은 FstClusHi와 FstClusLow 값이 '0' 이어야 한다.(볼륨 레이블에는 클러스터를 할당할 필요가 없다. |
0x10 |
Directory |
서브 디렉토리를 의미한다. |
0x20 |
Archive |
일반적인 파일을 의미한다. |
0xF0 |
Long File Name Entry |
해당 Entry는 Directory Entry가 아니라 Long File Entry 이다. |
이름 |
NT Resource |
||||
위치 (Offset) |
12 |
크기 (Size) |
1 Byte |
일반적인 값 (Value) |
0x0 |
설명 |
NT 계열의 Windows OS가 사용하기 위해 예약한 공간으로 0으로 값 설정을 한다. |
이름 |
Create Time Tenth |
||||
위치 (Offset) |
13 |
크기 (Size) |
1 Byte |
일반적인 값 (Value) |
0~199 |
설명 |
파일이 생성된 시각을 1/10초 단위로 기록한 항목. Create Time 항목과 관련이 있다. |
이름 |
Create Time |
||||
위치 (Offset) |
14~15 |
크기 (Size) |
2 Byte |
일반적인 값 (Value) |
가변적 |
설명 |
파일 생성 시간. 표현 형식에 대해서 아래 항목 참고. |
[Create Time 항목]
[Create Time 항목 내용]
속성 값 |
유효 범위 |
설명 |
Seconds |
0 ~ 29 |
초를 기록하며 값이 1 증가하면 초는 2 증가 한다. 즉 값이 4면 8초, 값이 22면 44초를 나타낸다. |
Minutes |
0 ~ 59 |
분을 기록 |
Hours |
0 ~ 23 |
시를 기록 |
- 00:00:00 부터 23:59:58 까지 유효한 값의 범위로 사용 가능.
이름 |
Create Date |
||||
위치 (Offset) |
16 ~ 17 |
크기 (Size) |
2 Byte |
일반적인 값 (Value) |
가변적 |
설명 |
파일 생성 날짜. 표현 형식 |
[Create Date 항목 구조]
속성 값 |
유효 범위 |
설명 |
Day |
1 ~ 31 |
날 기록 |
Month |
1 ~ 12 |
달 기록 |
Year |
0 ~ 127 |
년도 기록. 기준 년도 1980년에 저장된 값에다 더하여 출력함. 값이 30이라면 1980 + 30 = 2010년이라는 의미로 1980~2107년까지 표현 가능 |
이름 |
Last Access Date |
||||
위치 (Offset) |
18 ~ 19 |
크기 (Size) |
2 Byte |
일반적인 값 (Value) |
가변적 |
설명 |
이 항목은 이 파일에 읽기/쓰기 작업을 했던 마지막 날짜를 기록한다. 최근 접근 날짜만 있고 최근 접근 시간에 대한 항목은 없다. 마지막 작업이 쓰기였다면 이 항목의 날짜와 Write Date 날짜가 일치해야 하며, Create Date 형식과 동일하다. |
이름 |
First Cluster High 2Bytes |
||||
위치 (Offset) |
20 ~ 21 |
크기 (Size) |
2 Byte |
일반적인 값 (Value) |
가변적 |
설명 |
이 항목은 이 파일의 첫 번째 클러스터 번호의 상위 2Byte를 담고 있으며, FAT16에서는 클러스터 번호가 2Byte로 이루어 져있기 때문에 이 항목이 필요가 없다. FAT 16에서는 항상 0이다. |
이름 |
Write Time |
||||
위치 (Offset) |
22 ~ 23 |
크기 (Size) |
2 Byte |
일반적인 값 (Value) |
가변적 |
설명 |
가장 최근에 이 파일을 수정한 시간. 파일 최초 생성도 쓰기로 간주하며 이 항목 역시 Create Time 형식과 동일하다. |
이름 |
Write Date |
||||
위치 (Offset) |
24 ~ 25 |
크기 (Size) |
2 Byte |
일반적인 값 (Value) |
가변적 |
설명 |
가장 최근에 이 파일을 수정한 날짜. 파일 최초 시간도 쓰기로 간주하며 이 항목의 형식 역시 Create Date 형식과 동일하다. |
이름 |
First Cluster Low 2Byte |
||||
위치 (Offset) |
26 ~ 27 |
크기 (Size) |
2 Byte |
일반적인 값 (Value) |
가변적 |
설명 |
이 항목은 첫 번째 클러스터 번호의 하위 2Byte를 담고 있으며, FAT 16에서는 클러스터 번호의 크기가 2Byte이므로 굳이 First Cluster High를 조사하지 않아도 이 항목만으로도 충분하다. |
이름 |
File Size |
||||
위치 (Offset) |
28 ~ 31 |
크기 (Size) |
4 Byte |
일반적인 값 (Value) |
가변적 |
설명 |
이 항목은 해당 파일의 크기를 나타내며, 표현 단위는 Byte로 표현할 수 있는 최대 크기는 4GB이다. 파일 하나가 4GB를 넘을 수 없다. |
[FTK Imager]를 이용한 파일 덤프하여 간단한 분석을 해보았다.
디렉토리의 경우 File Size가 0인 것을 알 수 있고. 삭제된 파일, 일반 파일의 경우 Name[0]에서 차이를 보이는 것을 알 수있다.
4) 파일명 문자 제한
- FAT 파일시스템에서 파일명(확장자 포함) 또는 디렉토리명으로 허용되는 문자에는 제한이 있는데 긴파일명(LFNs)과 짧은 파일명의 허용 제한 범위가 다르다,
- Short File Name(짧은 파일명) 문자 제한 범위를 알아보자
영어 대문자 A~Z(반드시 대문자여야 한다.) 해당 OS가 지원하는 언어의 문자여야 한다. 아라비아 숫자 0~9 특수 문자 $%'=_@~!(){}^#& 가능 |
[Short File Name]
파일명 |
Name + Extender(11byte) |
||||||||||
FOO.BAR |
F |
O |
O |
B |
A |
R |
|||||
FILEDATA.DOC |
F |
I |
L |
E |
D |
A |
T |
A |
D |
O |
C |
foo |
F |
O |
O |
||||||||
foo.bar |
F |
O |
O |
B |
A |
R |
|||||
Pickle.A |
P |
I |
C |
K |
L |
E |
A |
||||
.BIG |
잘못된 표현으로 Name[0]에 0x20이 올 수 없다. |
||||||||||
HELLO!.JPG |
잘못된 표현으로 '!'는 허용 안 된다. |
4. Long File Names
1) Long File Names
LFNs의 주요 특징을 알아보자.
유니코드(UTF-16)방식으로 인코딩되어 다국어 지원이 가능 하다. 해당 OS가 지원하는 언어의 문자여야 한다. 아라비아 숫자 0~9 특수 문자 $%'=_@~!(){}^#& 가능 |
- LFNS를 설계하면서 호환성은 얻었지만 Long File Name Entry의 구조가 어색하고 불편하게 되어 버렸다.
- 유니코드를 지원하면서 여러 나라에서 사용될 수 있었다.
Long File Name Entry 하나에는 최대 13자의 문자열을 저장할 수 있다. 따라서 파일명을 255자로 한다면 LFNs는 Entry를 무려 20개나 차지하게 되고, FAT16에서는 루트 디렉토리의 Entry는 보통 512로 제한되기 때문에 FAT 16에서 LFNs를 사용하게 되면 루트 디렉토리에 많은 수의 파일을 저장할 수 없게 된다. 파일을 하위 디렉토리에 저장하거나, 볼륨을 포맷할 때 Entry 개수를 많이 잡게 되면 해결이 될 수 있다. |
2) Long File Name Entry 항목 분석
[Long File Name Entry가 담긴 섹터 화면]
"FileSystem Forensic.txt"이란 파일을 저장한 화면 이다.
이름 |
Order |
||||
위치 (Offset) |
0 |
크기 (Size) |
1 Byte |
일반적인 값 (Value) |
가변적 |
설명 |
Long File Name Entry가 정렬된 순번을 기록 하는 항목. 이 항목의 6번째 비트(0x40)가 1이라면 해당 파일명을 구성하는 마지막 Entry를 의미한다. |
이름 |
Name1 |
||||
위치 (Offset) |
1~10 |
크기 (Size) |
10 Byte |
일반적인 값 (Value) |
가변적 |
설명 |
해당 Entry에 저장할 문자열 중 1~5번째 문자열을 여기에 저장하게 된다. |
이름 |
Attribute |
||||
위치 (Offset) |
11 |
크기 (Size) |
1 Byte |
일반적인 값 (Value) |
0x0F |
설명 |
가장 최근에 이 파일을 수정한 날짜. 파일 최초 시간도 쓰기로 간주하며 이 항목의 형식 역시 Create Time 형식과 동일하다. |
이름 |
Type |
||||
위치 (Offset) |
12 |
크기 (Size) |
1 Byte |
일반적인 값 (Value) |
일반적으로 '0' |
설명 |
이 항목의 값이 '0'이라면 Long File Name의 항목 중 하나라는 의미로, 다른 값들은 미래를 위해 예약 되어 있다. |
이름 |
Check Sum |
||||
위치 (Offset) |
13 |
크기 (Size) |
1 Byte |
일반적인 값 (Value) |
일반적으로 '0' |
설명 |
이 항목은 Short Directory Entry 항목에 들어가는 11자의 문자열에 대한 Checksum을 저장한다. |
- Check Sum은 Long File Name Entry의 Checksum이라고 생각하기 쉽지만 Windows는 이 항목에 Short Directory Entry의 11자의 문자열에 대한 Checksum을 구해서 저장한다.
[저장 함수]
unsigned char ChkSum (char *buf){ int i; unsigned char Sum=0;
for(i=0;i<11;i++) { //우측으로 회전하면서 더하기 Sum = ((Sum & 1) ? 0x80 : 0) + (Sum >> 1) + buf[i]; } return sum; } |
이름 |
Name2 |
||||
위치 (Offset) |
14~25 |
크기 (Size) |
12 Byte |
일반적인 값 (Value) |
가변적 |
설명 |
해당 Entry에 저장할 문자열 중 6~11번째 문자열을 저장함 |
이름 |
First Cluster Low |
||||
위치 (Offset) |
26~27 |
크기 (Size) |
2 Byte |
일반적인 값 (Value) |
반드시 '0' |
설명 |
이 항목은 Long Directory Entry에서는 의미 없는 값이지만, 기존의 FAT 코드와의 호환성을 위해 반드시 0으로 해야한다. |
이름 |
Name3 |
||||
위치 (Offset) |
28~31 |
크기 (Size) |
4 Byte |
일반적인 값 (Value) |
가변적 |
설명 |
해당 Entry에 저장할 문자열 중 12~13번째 문자열을 저장함 |
3) LFNs 파일명 인코딩(Encoding)
- 위에서 보면 Long Directory Entry 한 개당 13개의 문자열을 저장할 수 있는데 저장형태가 2바이트당 1개의 문자열을 알 수 있다. 이는 LFNs가 유니코드(UTF-16)으로 하기 때문이다.
4) Short Directory Entry와 Long Directory Entry 관계
- Long Directory Entry는 그 자체만으로는 파일명만 저장하는 기능뿐이다. 파일 관리하는데 있어서 독립적으로 이용될 수 없고, 반드시 대응하는 Short Directory Entry가 있어야 한다.
- Short Directory Entry와 Long Directory Entry가 저장되는 형태는 다음과 같다.
[Directory Entry의 순서]
Directory Entry 순서 |
Order 항목 값 |
N번째 Long File Name Entry(마지막 Entry) |
LAST_LONG_ENTRY(0x40) | N |
… |
… |
2 번째 Long File Name Entry |
0x02 |
1 번째 Long File Name Entry |
0x01 |
위의 Long File Name Entry에 대응 하는 Short Directory Name Entry |
(해당 사항 없음) |
- 마지막에 Short Directory Entry가 위치하고, 위로 Long Directory Entry가 시작 되어 맨 위쪽에 마지막 문자열의 Long File Name Entry가 위치한다.
- 마지막 Long File Name Entry에는 마지막이라는 것을 알려주기 위해Order 항목에 해당 Entry 번호와 0x40번 비트를 OR한다. Windows는 LNF의 파일명을 최대 255로 제한하고 있다.
- 1개의 Long File Name Entry는 13개의 문자열을 저장할 수 있기 때문에 최대 20개의 Long File Name Entry가 필요하다는 것을 알 수 있으며 20번째 Entry는 Hex 값으로 0x14이기 때문에 0x40까진 될 수 없다 따라서 40번째 Long File Name Entry는 생기지 않을 것이다. 255를 꽉 채울 경우 하더라도 '0x40 | 0x14 = 0x54'가 되는 것이다.
5) Long File Name Entry
- LFNs에서는 파일명과 확장자 사이에 들어가는 점(.)까지도 문자열로 처리하고 있다. 그리고 LFNs에서 마지막 문자열까지 저장 완료 했다면 다음에 오는 공간에 0x0000을 저장하여 문자열이 끝났음을 알려주며, 그 이후 남는 공간은 전부 0xFFFF로 처리하면 된다.
- 단 문자열이 Long File Name Entry와 정확히 맞아 떨어지면 0x0000을 넣기 위해 Long File Name Entry를 하나 더 사용하지 않고 그냥 0x0000을 넣지 않는다.
[ "The quick brown.fox" Long File Name Entry예제 ]
- LFNs에서 마지막 문자열까지 저장이 완료되면 다음에 오는 공간에 0x0000을 저장하여 문자열이 끝났음을 알리며 그 후에 남는 공간은 0xFFFF로 처리된다.
- 만약 Long File Name Entry와 정확히 맞아 떨어지면 0x0000을 넣기 위해 Long File Name Entry를 추가하여 사용하지 않는다.
- "FAT32 File system docs.txt" 라는 26개로 Long File Name Entry 한 개당 13개씩 문자열 정확히 2개의 Entry로 떨어지는 문자열 뒤에 0x0000이 없게 된다.
6) LFNs 파일명 문자 허용 범위
[Long File Names에서 허용되는 문자 범위]
유니코드(UTF-16)방식으로 인코딩되어 다국어 지원이 가능 하다. 아라비아 숫자 0~9 Short File Name에서 허용되는 특수 문자 (특수 문자 $%'=_@~!(){}^#& 가능) Long File Names에서 추가적으로 더 허용되는 특수 문자 (특수 문자 + , ; = [ ])
허용되지 않는 문자 : \ / : * ? " < > | |
7) Short File Name 변환 규칙
- Long File Name은 반드시 자신과 대응하는 Directory Entry를 가져야 한다.
- Directory Entry에는 Long File Name의 별칭(Alias)을 가진 Short File Name이 있어야 하며, Short File Name을 생성하는 규칙은 엄격하지 않기 때문에 마음대로 지정하여도 상관 없다. 단 허용하지 않는 문자 범위를 넘어서면 안 된다.
[Windows가 Long File Name의 별칭을 생성하는 과정]
1_영어 소문자를 전부 대문자로 변환
2_Short File Name의 문자로 허용되지 않는 문자들은 전부 '_'로 변환한다.
3_문자열 사이에 모든 Space(0x20)을 제거 한다.
4_문자열 사이에 있는 모든 ' .'을 제거 한다.
5_위의 공식대로 얻어진 문자열에서 앞의 6자리만 얻어낸다.
6_마지막 2개 문자열은 ~를 넣은 후 숫자를 적는다. 숫자는 1부터 시작한다.
7_만약 이렇게 생성한 파일명이 존재한다면 숫자를 증가시킨다.
8_확장자는 앞에서 3자리만 넣어준다.
[Long File Name의 별칭 생성]
파일명 |
Name + Extender(11byte) |
||||||||||
test doc1.txt |
T |
E |
S |
T |
D |
O |
~ |
1 |
T |
X |
T |
test docs.txt |
T |
E |
S |
T |
D |
O |
~ |
1 |
T |
X |
T |
man.songs |
M |
A |
N |
~ |
1 |
S |
O |
N |
|||
my.name.txt |
M |
Y |
N |
A |
M |
E |
~ |
1 |
T |
X |
T |
File System.jpg |
F |
I |
L |
E |
S |
T |
~ |
1 |
J |
P |
G |
5. Data 영역 실습
먼저 FAT 16으로 포맷을 한 뒤 안에 파일 여러 개를 넣어 놓도록 하자.
Visual Studio 2012를 관리자 권한으로 실행한다.
관리자권한으로 실행
프로젝트 속성 – 구성 속성 – 일반 -> 프로젝트 기본값 – 문자집합 : 멀티 바이트 문자 집합 사용
#include <stdio.h>
#include <Windows.h>
#include <stdlib.h>
#pragma pack(1)
#define U8 unsigned char
#define S8 char
#define U16 unsigned short
#define U32 unsigned int
#define U64 unsigned __int64
typedef struct _DIR_struct{ //Directory Entry 구조체
U8 Name[11];
U8 Attr;
U8 NTres;
U8 CrtTimeTenth;
U16 CrtTime;
U16 CrtDate;
U16 LstAccDate;
U16 FstClusHi;
U16 WriteTime;
U16 WriteDate;
U16 FstClusLow;
U32 FileSize;
}DirEntry;
typedef struct _FAT16_BPB_struct{ //FAT16 부트 레코드 선언
//공통 영역
U8 JmpBoot[3];
U8 OEMName[8];
U16 BytsPerSec;
U8 SecPerClus;
U16 RsvdSecCnt;
U8 NumFATs;
U16 RootEntCnt;
U16 TotSec16;
U8 Media;
U16 FATs16;
U16 SecPerTrk;
U16 NumHeads;
U32 HiddSec;
U32 TotSec32;
//FAT 16영역
U8 DirveNumber;
U8 Reserved1;
U8 BootSignal;
U32 VolumeID;
U8 VolumeLabel[11];
U8 FileSysType[8];
U8 BootCodeArea[448];
U16 Signature;
}FAT16_BPB;
typedef struct _LONG_DIR_struct{ //Long File Name Entry 구조체
U8 order;
U8 Name1[10];
U8 Attr;
U8 Type;
U8 chksum;
U8 Name2[12];
U16 FstClusLo;
U8 Name3[4];
}LongDirEntry;
#pragma pack()
typedef struct _VOL_struct{ //볼륨의 전반적인 정보를 담는 구조체
U32 Drive;
U32 VolBeginSec;
U32 FirstDataSec;
U32 RootDirSec;
U32 RootEntCnt;
U32 RootDirSecCnt;
U32 FATSize;
U32 FATStartSec;
U32 TotalClusCnt;
U32 TotalSec;
U32 DataSecSize;
U32 ClusterSize;
U32 SecPerClus;
}VolStruct;
void print_longName(LongDirEntry* pLongDir, U32 EntryNum);
U32 show_dir(DirEntry* pDir);
U32 get_BPB_info(FAT16_BPB* BPB, VolStruct* pVol);
U32 HDD_read(U8 drv, U32 SecAddr, U32 blocks, U8* buf);
VolStruct gVol;
int main(void){
U8 buf[512];
U8* pRootBuf;
gVol.Drive = 0x2; //드라이브 설정
gVol.VolBeginSec = 0x87; //시작 섹터 설정
if(HDD_read(gVol.Drive, gVol.VolBeginSec, 1, buf) == 0){
printf("Boot Sector Read Failed \n");
return 1;
}
//buf를 읽어서 처리한 뒤 gVol 변수에 담는다. FAT16전용이기 때문에 다르면 에러 발생
if(get_BPB_info((FAT16_BPB*)buf, &gVol) == 0){
printf("It is not FAT16 File System\n");
return 1 ;
}
//루트 디렉토리를 읽고 메모리에 담고 있을 버퍼를 잡는다. byte 단위(섹터 512)
pRootBuf = (U8*)malloc(gVol.RootDirSecCnt * 512);
//저장장치로부터 부트 디렉토리의 섹터를 읽어 옮김
if(HDD_read(gVol.Drive, gVol.RootDirSec, gVol.RootDirSecCnt, pRootBuf) == 0){
printf("Root Dir Read Failed \n");
return 1;
}
//읽어 들인 루트 디렉토리의 데이터 화면 출력
show_dir((DirEntry*)pRootBuf);
return 0;
}
U32 show_dir(DirEntry* pDir){
U32 i, y, LongEntryEn=0;
for(i=0; i<=gVol.RootEntCnt ; i++)
{
//Name[0]값 조사
switch((U8) pDir[i].Name[0]){
case 0x00 : return 1;
case 0xE5 : continue;
}
//해당 Entry Long File Name Entry이므로 나중에 처리하도록 LongEntryEn 변수를 1로 변경
if(pDir[i].Attr == 0xF0){
LongEntryEn = 1;
continue;
}
printf("=======Entry Number %d =======\n", i);
//해당 Entry 출력
if(pDir[i].Attr == 0x10) printf("Directory Name : ");
else printf("File Name : ");
//print_longName함수를 호출하여 다시 조사함. 해당 Entry의 위쪽부터 저장되기 때문에 i-1
if(LongEntryEn == 1){
print_longName((LongDirEntry*)pDir, i-1);
LongEntryEn = 0;
}else{
for(y = 0; y< 11 ; y++)
printf("%c",pDir[i].Name[y]);
}
printf("\n");
//해당 클러스터 크기, 시작 클러스터를 화면에 출력
printf("File size %d\n",pDir[i].FileSize);
printf("Start Cluster : %d\n",(pDir[i].FstClusLow | pDir[i].FstClusHi << 16));
}
return 1;
}
void print_longName(LongDirEntry* pLongDir, U32 EntryNum){
wchar_t filename[512];
char final[512];
U32 nameOffset = 0;
do{ // Long File Name Entry 항목의 Order이 0x40의 비트 1이 나올 때 까지 파일명을 얻음.
memcpy(&filename[nameOffset], pLongDir[EntryNum].Name1, 10); nameOffset +=5;
memcpy(&filename[nameOffset], pLongDir[EntryNum].Name2, 12); nameOffset +=6;
memcpy(&filename[nameOffset], pLongDir[EntryNum].Name3, 4); nameOffset +=2;
}while (( pLongDir[EntryNum--].order & 0x40) == 0);
filename[nameOffset] = 0x0000; //문자열의 끝을 알려주는 값 넣음
wcstombs(final, filename, 512); //유니코드를 ASCII 코드로 변환해준 후 화면에 출력
printf("%s", final);
}
U32 HDD_read (U8 drv, U32 SecAddr, U32 blocks, U8* buf){
U32 ret;
U32 ldistanceLow, ldistanceHigh, dwpointer, bytestoread, numread;
char cur_drv[100];
HANDLE g_hDevice;
sprintf_s(cur_drv,sizeof(cur_drv),"\\\\.\\PhysicalDrive%d",(U32)drv);
g_hDevice=CreateFile(cur_drv, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if(g_hDevice==INVALID_HANDLE_VALUE) return 0;
ldistanceLow = SecAddr << 9;
ldistanceHigh = SecAddr >> (32 - 9);
dwpointer = SetFilePointer(g_hDevice, ldistanceLow, (long *)&ldistanceHigh, FILE_BEGIN);
if(dwpointer != 0xFFFFFFFF) {
bytestoread = blocks * 512;
ret = ReadFile(g_hDevice, buf, bytestoread, (unsigned long*)&numread, NULL);
if(ret) ret = 1;
else ret = 0;
}
CloseHandle(g_hDevice);
return ret;
}
U32 get_BPB_info(FAT16_BPB* BPB, VolStruct* pVol){
//Root Entry Count 항목 조사, 0이면 FAT16이므로 파일시스템 종류 알기 좋다.
if(BPB->RootEntCnt == 0 || BPB->Signature != 0xAA55)
return 0;
//부트 레코드 정보 얻기.
if(BPB->TotSec16 !=0) pVol->TotalSec = BPB->TotSec16;
else pVol->TotalSec = BPB->TotSec32;
//get FAT Size
pVol->FATSize = BPB->FATs16;
//get FAT Start Secotr
pVol->FATStartSec = pVol->VolBeginSec + BPB->RsvdSecCnt;
//get Root Dir Entry Count
pVol->RootEntCnt = BPB->RootEntCnt;
//get Root Dir Sector
pVol->RootDirSec = pVol->VolBeginSec + BPB->RsvdSecCnt + (BPB->NumFATs * BPB->FATs16);
//get Root Dir Sector Count
pVol->RootDirSecCnt = ((BPB->RootEntCnt * 32) + (BPB->BytsPerSec -1 )) /BPB->BytsPerSec;
//get FAT Start Sector
pVol->FirstDataSec = pVol->VolBeginSec +BPB->RsvdSecCnt + (BPB->NumFATs * pVol->FATSize) + pVol->RootDirSecCnt;
//get Size of Data Area
pVol->DataSecSize = pVol->TotalSec - (BPB->RsvdSecCnt + (BPB->NumFATs * pVol->FATSize) + pVol->RootDirSecCnt);
//get Total Cluster Count
pVol->TotalClusCnt = pVol->DataSecSize / BPB->SecPerClus;
//get Sector per Cluster
pVol->ClusterSize = BPB->SecPerClus * BPB->BytsPerSec;
//get Sector Per Cluster
pVol->SecPerClus = BPB->SecPerClus;
return 1;
}
[드라이브 설정]
위에 진하게 주석 한 부분에서 드라이브 설정을 할 때
gVol.Drive = 0x2를 한 것은 아래 D드라이브를 테스트 하려고 한 것이다.
만약 디스크0번을 분석하려면
gVol.Drive = 0x0 으로 설정 해야한다.
[부트레코드 섹터위치]
gVol.VolBeginSec = 0x87;
아래 스샷은 FTK Imager로 확인을 해보았다.
[실행화면]
'Operating System' 카테고리의 다른 글
[파일시스템] EXT2 파일시스템 (1/2) (0) | 2020.05.26 |
---|---|
[파일시스템] FAT16 파일시스템 (3/3) (0) | 2020.05.26 |
[파일시스템] FAT16 파일시스템 (1/3) (0) | 2020.05.26 |
[파일시스템] 파티션 (1) | 2020.05.26 |
[파일시스템] 파일시스템의 이해 (0) | 2020.05.24 |