Root Storage Sector ID
Root Storage는 최상위 폴더로서 Small Sector와 다른 스트림, 스토리지들을 포함하고 있기 때문에 헤더 다음으로 가장 먼저 읽어야 되는 항목이다. 이를 읽기 위해서는 헤더에서 Root Storage Sector ID(0x30 ~ 0x33)을 읽어서 BBAT를 참조해야 된다.
Root Storage를 읽으면 OLE 파일의 전체적인 구조를 파악할 수 있다
Root Storage를 읽는 방법은 다음과 같다. 다음 예제를 보고 직접 따라해보자.
-
헤더에서 Root Storage Sector ID를 읽는다.
-
BBAT를 참조하여 모든 Sector를 연결시킨다.
-
읽어온 Root Storage와 연결된 모든 디렉토리(Stream/Storage)를 분석한다.
-
OLE 파일의 전체적인 구조 파악한다.
가장 먼저 헤더에서 0x30 ~ 0x33에 위치한 Root Storage Sector ID를 읽는다.
현재 이 파일에서 Root Storage Sector ID는 39(0x27)이다. BBAT를 참조하여 Root Storage의 시작인 39번 섹터와 연결된 다른 모든 섹터(스토리지, 스트림)들을 전 부 읽어야한다. 그렇기에 다음으로는 BBAT를 읽는다.
BBAT를 읽기 위해서는 BBAT Depot를 전부 읽고 합쳐야 한다.
현재 BBAT Depot에 저장된 Big Block Allocation Table의 개수는 0x4C에 위치한 0x26 한 개뿐이다. 그렇기 때문에 이 파일에서 BBAT는 0x26번에 위치한 한 개의 섹터만 읽으면 된다.
만약 BBAT가 1개가 아닌 여러 개일 경우 모두 순서대로 읽고 연결하여 하나의 BBAT를 만들면 된다. 만약 BBAT의 개수가 BBAT Depot(0x4C~0x1FF)에 저장 가능한 최대 개수인 109개를 초과할 경우 헤더에 Start Block of Extra Big Block Allocation Table를 참조해서 확장된 BBAT Depot를 읽어야 한다. 확장된 BBAT에 대해서는 나중에 다시 상세히 설명하도록 하겠다.
현재 BBAT의 주소는 (0x26 + 0x01) * 0x200 = 0x4E00
에 위치한다.
0x26은 BBAT의 시작 주소이고 1을 더하는 이유는 처음에 앞서 설명했듯이 데이터 영역에서 Sector ID로 접근할 때에는 헤더를 제외해야 하기 때문에 더하는 것이다. (헤더의 오프셋을 건너 뛰기 위해서 헤더 크기만큼 더한다.) 그리고 마지막으로 섹터 크기(512)만큼 곱해주면 BBAT의 주소를 구할 수 있다.
0x4E00 ~ 0X4FFF 까지가 BBAT 영역이다. (1섹터의 크기)
BBAT에는 섹터에 연결된 다음 섹터를 나타내는 DWORD(4바이트) 값들이 저장되 어 있다. 즉, 1개의 BBAT(1섹터)에는 128개의 next 값들이 저장되어 있다.
이 파일에서 Root Storage Sector ID는 0x27(39)이다. 그리고 BBAT의 시작 주소는 0x4E00이다. 그렇다면 다음과 같이 BBAT에 처음으로 접근할 수 있다.
0x4E00 + (0x27 * 0x04) = 0x4E9C
0x4E00은 BBAT의 시작 주소, 0x27은 Root Storage Sector ID이고 4를 곱하는 이유는 BBAT에 저장된 next값 하나의 크기가 4바이트이기 때문이다.
0x4E9C에는 0x28이라는 값이 저장되어 있다. 섹터 연결의 끝을 나타내는 값인 0xFFFFFFFE가 보일 때까지 계속해서 같은 방식으로 next 값을 계산해서 쫓아가서 읽은 섹터들을 전부 하나로 연결하면 Root Storage를 완성할 수 있다.
값 | 의미 |
0xFFFFFFFD | 특수 블록을 의미 |
0xFFFFFFFE | End of Chain |
0xFFFFFFFF | 비어 있음을 의미 |
0 ~ | Next 값 (연결된 다음 섹터 ID) |
0x4E00 + (0x28 * 0x04) = 0x4EA0
0x4EA0에는 0xFFFFFFFE (FE FF FF FF)가 저장되어 있다. 이걸로 Root Storage는 0x27, 0x28 섹터가 연결되어 있음을 알 수 있다. BBAT를 참조하여 파악한 Root Storage의 구조는 다음과 같다.
SECTOR[39]는 Root Storage Sector ID에서 확인한 값이고, SECTOR[40]은 BBAT를 통해 연결된 것을 확인하였다. 이제 Root Storage의 구조를 확인하였으니 실질적인 내용을 읽고 분석해보도록 한다. 먼저 각 섹터들의 메모리를 읽어온다.
SECTOR[39] : (0x27 + 0x01) * 0x200 = 0x5000 ~ 0x51FF (512Byte)
SECTOR[40] : (0x28 + 0x01) * 0x200 = 0x5200 ~ 0x53FF (512Byte)
Root Storage 영역(39 ~ 40섹터)에는 OLE 파일을 구성하고 있는 디렉토리(스트림 또는 스토리지)들이 저장되어 있다. 디렉토리 1개의 크기는 0x80(128) 바이트이다. 각각의 디렉토리에는 이름, 크기, 타입, 자식 노드 등 디렉토리에 대한 정보들이 저장되어 있다. 저장된 디렉토리의 데이터 구조는 다음과 같다.
디렉토리 데이터 구조
(1) Name
이름 | Name |
위치 | 0x00 ~ 0x3F |
크기 | 64바이트 |
일반적인 값 | |
설명 | Stream 또는 Storage의 이름이다. |
(2) Name Length
이름 | Name Length |
위치 | 0x40 ~ 0x41 |
크기 | 2바이트 |
일반적인 값 | |
설명 | 앞에서 설명한 Name에 저장된 문자열의 크기. (문자의 끝을 나타내는 NULL Character 포함) |
(3) Type
이름 | Type |
위치 | 0x42 |
크기 | 1바이트 |
일반적인 값 | |
설명 | 현재 읽고있는 디렉토리의 타입을 나타내는 값 0 : Empty 1 : Storage 2 : Stream 3 : Lock bytes 4 : Property 5 : Root Storage |
(4) Node Color
이름 | Node Color |
위치 | 0x43 |
크기 | 1바이트 |
일반적인 값 | |
설명 | 현재 읽고있는 디렉토리의 Node Color 값.
디렉토리들은 Red-Black Tree 구조로 구성되어 있다.
0x00 : Red 0x01 : Black |
(5) Left Node
이름 | Left Node |
위치 | 0x44 ~ 0x47 |
크기 | 4바이트 |
일반적인 값 | |
설명 | 현재 노드(디렉토리)의 좌측 자식 노드의 인덱스 번호. 존재하지 않는 경우 0xFFFFFFFF 값이 기록된다. |
(6) Right Node
이름 | Right Node |
위치 | 0x48 ~ 0x4B |
크기 | 4바이트 |
일반적인 값 | |
설명 | 현재 노드(디렉토리)의 우측 자식 노드의 인덱스 번호. 존재하지 않는 경우 0xFFFFFFFF 값이 기록된다. |
(7) Child Node
이름 | Child Node |
위치 | 0x4C ~ 0x4F |
크기 | 4바이트 |
일반적인 값 | |
설명 | 현재 노드(디렉토리)의 자식 노드의 인덱스 번호. 존재하지 않는 경우 0xFFFFFFFF 값이 기록된다. |
(8) CLSID
이름 | CLSID |
위치 | 0x50 ~ 0x5F |
크기 | 16바이트 |
일반적인 값 | |
설명 | 현재 노드의 CLSID |
(9) User Flag
이름 | User Flag |
위치 | 0x60 ~ 0x63 |
크기 | 4바이트 |
일반적인 값 | 00 00 00 00 |
설명 | User Flag (사용하지 않음) |
(10) Creation Time
이름 | Creation Time |
위치 | 0x64 ~ 0x6B |
크기 | 8바이트 |
일반적인 값 | 00 00 00 00 00 00 00 00 |
설명 | 생성 시간 (일반적으로 0) |
(11) Modify Time
이름 | Modify Time |
위치 | 0x6C ~ 0x73 |
크기 | 8바이트 |
일반적인 값 | 00 00 00 00 00 00 00 00 |
설명 | 마지막으로 수정한 시간 |
(12) Start Sector ID
이름 | Start Sector ID |
위치 | 0x74 ~ 0x77 |
크기 | 4바이트 |
일반적인 값 | |
설명 | 현재 오브젝트의 시작 섹터 ID. 디렉토리의 타입이 Stream 또는 Root Storage인 경우 이 값을 참조해야한다. 헤더에 존재하는 Root Storage Sector ID와 같은 메커니즘이다. |
(13) Size of Low
이름 | Size of Low |
위치 | 0x78 ~ 0x7B |
크기 | 4바이트 |
일반적인 값 | |
설명 | 현재 오브젝트의 크기 (Low) |
(14) Size of High
이름 | Size of High |
위치 | 0x7C ~ 0x7F |
크기 | 4바이트 |
일반적인 값 | 00 00 00 00 |
설명 | 현재 오브젝트의 크기 (High) Size of Low 크기의 확장 개념이다. |
디렉토리 예
각각의 디렉토리를 분석해보면 다음과 같다.
Left Node, Right Node, Child Node의 값이 0xFFFFFFFF이고 다른 값이 전부 0인 경우 비어있는 공간임을 뜻하며 읽지 않아도 된다. 만약 파일에 디렉토리를 새로 추가하게 될 경우 빈 공간을 먼저 활용하고 공간이 부족할 경우 새로 할당한다.
- Directory[0]
Name | Root Entry |
Type | Root Storage (0x05) |
Color | Black (0x01) |
Left Node | |
Right Node | |
Child Node | 0x03 |
Start Sector Id | 0x2A |
Size | 0x80 |
- Directory[1]
Name | 1Table |
Type | Stream (0x02) |
Color | Black (0x01) |
Left Node | |
Right Node | 0x05 |
Child Node | |
Start Sector Id | 0x08 |
Size | 0x1A8B |
- Directory[2]
Name | WordDocument |
Type | Stream (0x02) |
Color | Black (0x01) |
Left Node | 0x01 |
Right Node | |
Child Node | |
Start Sector Id | 0x00 |
Size | 0x1000 |
- Directory[3]
Name | \005SummaryInformation |
Type | Stream (0x02) |
Color | Black (0x01) |
Left Node | 0x02 |
Right Node | 0x04 |
Child Node | |
Start Sector Id | 0x16 |
Size | 0x1000 |
- Directory[4]
Name | \005DocumentSummaryInformation |
Type | Stream (0x02) |
Color | Black (0x01) |
Left Node | |
Right Node | |
Child Node | |
Start Sector Id | 0x1E |
Size | 0x1000 |
- Directory[5]
Name | \001CompObj |
Type | Stream (0x02) |
Color | Red (0x00) |
Left Node | |
Right Node | |
Child Node | |
Start Sector Id | 0x00 |
Size | 0x6E |
분석한 각각의 디렉토리 정보들을 통해 저장된 메모리 구조와 OLE 파일에 저장된 디렉토리(스토리지 또는 스트림)의 구조를 다음과 같이 파악할 수 있다.
결론
이번 글에서는 Root Storage를 수집하여 해당 디렉토리에 어떤 스트림과 스토리지가 존재하는지 확인하는 방법을 살펴보았다. 다음에는 스트림을 읽는 방법에 대해서 알아보기로 한다.