[Ext4 File System] 3. image 파일 분석 해보기 ( ext4_image1.001 )

cryptosecurity.tistory.com/15?category=841651

 

[ Ext4 File System ] 3. image 파일 분석 해보기 ( ext4_image1.001 )

ext4_image1.001 이미지를 HxD 프로그램으로 분석하고 파일을 추출해보자. 1. HxD 프로그램 열기 [도구] - [디스크 이미지 열기] - [ext4_image1.001 파일 선택] 디스크 이미지 열기를 하면 섹터 (512byte) 단위..

cryptosecurity.tistory.com

전에 ext4_image1.001,  ext4_image2.001 이미지 파일을 분석해 보았다.

분석한 것을 토대로 파이썬으로 파싱해서 파일 목록 리스트 구해보자.

 

 

1. 모듈 설정

1
2
3
4
import binascii
import struct
 
sector = 512
cs

binascii  -> 바이너리와 ASCII 간의 변환 모듈

struct -> little endian 모듈

 

 

2. 이미지 파일 받아오기

1
2
image_name = input('Input the ext4 image name (ex. ext4_image1.txt) : ')
= open(image_name, 'rb+')
 
cs

 

 

3. little endian 함수

1
2
3
4
# little endian
def little4(hex4): return struct.unpack('<L', hex4)[0# 4byte
def little2(hex2): return struct.unpack('<H', hex2)[0# 2byte
def little1(hex1): return struct.unpack('<B', hex1)[0# 1byte
cs

little4() : 4byte Hex 값을 little endian해서 10진수로 변환하는 함수

little2() : 2byte Hex 값을 little endian해서 10진수로 변환하는 함수

little1() : 1byte Hex 값을 little endian해서 10진수로 변환하는 함수

 

 

4. MBR 함수

1
2
3
4
5
6
7
# MBR
def MBR():
    f.seek(454)
    LBA_str = f.read(4# b'\x00\x08\x00\x00'
    LBA = little4(LBA_str) # 2048
 
    return LBA
 
 

MBR 영역에서 LBA 주소 구하는 함수

LBA = 오프셋 454부터 4byte 값을 little endian한 값

 

1
2
LBA = MBR()
Superblock_addr = LBA + 2
cs

Super Block 주소는 LBA 시작 주소 + 2 이다.

 

 

5. Super Block 함수

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
# Super Block
def Superblock(Superblock_addr):  
    f.seek(Superblock_addr * sector) # 2050 * 512
    sb = f.read(sector)
 
    # 4byte씩 나누기
    sb_str = binascii.b2a_hex(sb).decode()
    sb_list = [sb_str[i:i+8for i in range(0, sector*28)]
    
    # log block size
    log_block_hex = binascii.unhexlify(sb_list[7])
    log_block = little4(log_block_hex)
    
    if log_block == 0:
        log_block_size = 1 # 1kb
    elif log_block == 1:
        log_block_size = 2 # 2kb
    elif log_block == 2:
        log_block_size = 4 # 4kb
 
    # Inode_Per_Group
    Inode_Per_Group_hex = binascii.unhexlify(sb_list[10])
    Inode_Per_Group = little4(Inode_Per_Group_hex)
    
    return log_block_size, Inode_Per_Group
cs

Super block 주소는 섹터단위이므로 Superblock_addr에 sector값을 곱해준다.

sb = 섹터 Super block 주소부터 512byte 값 저장

sb_list = 512byte를 4byte씩 나눠서 리스트에 저장

 

log_block = sb_list 중 7번째 값을 little endian한 값

log_block이 0이면 log_block_size는 1KB

log_block이 1이면 log_block_size는 2KB

log_block이 2이면 log_block_size는 4KB

 

Inode_Per_Group = sb_list 중 10번째 값을 little endian한 값

 

log_block_size와 Inode_Per_Group 값을 리턴하는 함수

 

1
2
log_block_size, Inode_Per_Group = Superblock(Superblock_addr )
GDT_addr = Superblock_addr - 2 + log_block_size * 2
cs

GDT 주소는 Superblock 주소 - 2 + log_block_size x 2 이다.

GDT_addr = 2056

 

 

6. GDT (Group Descriptor Table) 함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Group Descriptor Table
def GDT(GDT_addr, bg_num):
    bg_size = 32
 
    f.seek(GDT_addr * sector + bg_size * bg_num)
    GDT = f.read(bg_size)
 
    GDT_str = binascii.b2a_hex(GDT).decode() 
    GDT_list = [GDT_str[i:i+8for i in range(0, sector*28)]
 
    # Inode Table Address
    Inode_Table_hex = binascii.unhexlify(GDT_list[2])
    Inode_Table_dec = little4(Inode_Table_hex) 
    
    return Inode_Table_dec
 
cs

GDT 주소는 섹터단위이므로 GDT_addr에 sector값을 곱해준다.

GDT에는 Block Group 정보들이 32byte씩 여러개 저장되어 있다.

root_directory를 먼저 살펴봐야 하기에 이때는 Block Group 0번을 살펴봐야 한다. ( bg_num = 0 )

 

나중에 root_directory에 저장되어 있는 파일을 살펴 본 후 root_directory 안에 Directory 파일이 있을 경우에,

0이 아닌 bg_num 값을 받아오게 된다.

 

따라서 GDT_addr x sector 값에 bg_size x bg_num을 더해준다.

 

GDT  = 섹터 GDT 주소 + (bg_size x bg_num)byte부터 32byte 값 저장

GDT_list = 32byte를 4byte씩 나눠서 리스트에 저장

 

Inode_Table_dec = GDT_list 중 2번째 값을 little endian한 값

 

1
2
Inode_Table_dec = GDT(GDT_addr, 0)
Inode_Table_addr = (Inode_Table_dec * log_block_size * 2+ LBA
cs

root_directory를 살펴봐야 하기에 bg_num = 0을 넣는다.

Inode_Table 주소는 Inode_Table_dec x log_block_size x 2 + LBA 주소 이다.

 

 

7. Inode Table 함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Inode Table
def Inode_Table(Inode_Table_addr, Inode_num):
    Inode_size = 256
    f.seek(Inode_Table_addr * sector + Inode_size * (Inode_num - 1))
    Inode = f.read(Inode_size)
 
    Inode_str = binascii.b2a_hex(Inode).decode() 
    Inode_list = [Inode_str[i:i+8for i in range(0, Inode_size*28)]
 
    # Directory Pointer
    DP_list = Inode_list[10:22]
 
    for a in reversed(DP_list):
        if a != '00000000':
            DP = a
            break
    
    DP_hex = binascii.unhexlify(DP)
   DP_dec = little4(DP_hex)
 
    return DP_dec
 
cs

Inode Table 주소는 섹터단위이므로 Inode_Table_addr에 sector값을 곱해준다.

Inode Table에는 Inode 들이 여러개 저장되어 있다.그 중에 우리는 Root Directory 정보가 저장되어 있는 Inode 2번을 살펴볼 것이기에 Inode_num = 2 이다.따라서 Inode_Table_addr x sector 값에 Inode_size x (Inode_num - 1)을 더해준다.

 

Inode  = (섹터 Inode Table 주소+ 256byte)부터 256byte 값 저장

Inode_list = 256byte를 4byte씩 나눠서 리스트에 저장

 

Directory Poiner는 Inode_list 중 10번부터 22번째 값 중 0이 아닌 제일 마지막 값이다.

DP_dec = DP_hex 값을 little endian한 값

 

1
2
DP_dec = Inode_Table(Inode_Table_addr, 2# root directory
DP_addr = (DP_dec * log_block_size * 2+ LBA
cs

Directory Pointer 주소는 DP x log_block_size x 2 + LBA 주소 이다.

 

 

 

8. Directory Entry 함수

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# Directory Entry
def Directory_Entry(DP_addr):
    DE_size = 256
    f.seek(DP_addr * sector)
    DE = f.read(DE_size)
    
    # 1byte씩 나누기
    DE_str = binascii.b2a_hex(DE).decode()
    DE_list = [DE_str[i:i+2for i in range(0, DE_size*22)]
 
   File_name, Dir_name, Block_Group_num = [], [], []
    i = 24
    while(i < DE_size):
        # Inode_num
        I_num_hex = binascii.unhexlify(DE_list[i] + DE_list[i+1+ DE_list[i+2+ DE_list[i+3])
        Inode_num = little4(I_num_hex)
        
        # record length
        r_len_hex = binascii.unhexlify(DE_list[i+4+ DE_list[i+5])
        record_len = little2(r_len_hex)
        
        # file name length
        n_len_hex = binascii.unhexlify(DE_list[i+6])
        name_len = little1(n_len_hex)
 
        # file type
        FT_hex = binascii.unhexlify(DE_list[i+7])
        FT_dec = little1(FT_hex)
 
        if FT_dec == 0:
            file_type = 'unknown'
        elif FT_dec == 1:
            file_type = 'Regular'
        elif FT_dec == 2:
            file_type = 'Directory'
 
        # file name
        name = []
        for k in range(88 + name_len):
            name.append(DE_list[i+k])
 
        name_hex = binascii.unhexlify(''.join(name))
        name = name_hex.decode()
        File_name.append(name)
 
 
        if file_type == 'Directory' and name != 'lost+found':
           Dir_name.append(name_hex.decode())
            Block_Group_num.append(int((Inode_num-1)/Inode_Per_Group))
        
        i += record_len
 
    return File_name, Dir_name, Block_Group_num
 
cs

Directory Entry 주소는 섹터단위이므로 DE_addr에 sector값을 곱해준다.

 

DE  = 섹터 75056 부터 256byte 값 저장

DE_list = 256byte를 1byte씩 나눠서 리스트에 저장

 

하나의 레코드 당

      Inode num은 4byte

      record length는 2byte

      file name length는 1byte

      file type은 1byte

      file name은 file name length byte

로 구성되어 있다.

 

i = 24

Inode_num은 DE_list에 i번째 부터 4byte 값을 little endian한 값

record_len은 DE_list에 i+4번째 부터 2byte 값을 little endian한 값

name_len은 DE_list에 i+6번째 부터 1byte 값을 little endian한 값

FT_dec은 DE_list에 i+7번째 부터 1byte 값을 little endian한 값

name은 DE_list에 i+8번째 부터 file name length byte 값을 little endian한 값

 

i에 record length 값을 더하고 다시 반복

 

File_name에 name을 리스트로 저장

만약에 'lost+found' 제외하고 file_type이 Directory라면

                Folder_name에 그때의 name을 리스트로 저장

                Block_Group_num에 그때의 ( ( inode_num - 1 ) / Inode_Per_Group ) 값을 리스트 저장

 

File_name, Folder_name, Block_Group_num 반환

 

1
2
3
4
File_name, Dir_name, Block_Group_num = [], [], []
File_name, Dir_name, Block_Group_num = Directory_Entry(DP_addr)
 
print('root -->', File_name)
 
cs

root Directory에 들어있는 File_name을 출력한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
while(len(Dir_name) != 0):
    i=0
    for bg_num in Block_Group_num:
        f_Inode_Table_dec = GDT(GDT_addr, bg_num)
        f_Inode_Table_addr = (f_Inode_Table_dec * log_block_size * 2+ LBA
 
        f_DP_dec = Inode_Table(f_Inode_Table_addr, 1)
        f_DP_addr = (f_DP_dec * log_block_size * 2+ LBA
 
       f_File_name, f_Dir_name, f_Block_Group_num = [], [], []
       f_File_name, f_Dir_name, f_Block_Group_num = Directory_Entry(f_DP_addr)
 
        print(Dir_name[i], '-->', f_File_name)
        i+=1
 
   Block_Group_num, Dir_name= f_Block_Group_num, f_Dir_name
cs

Dir_name 길이가 0이 아닐 때

              Block_Group_num 값 하나하나를 bg_num으로 놓는다.

               GDT(), Inode_Table(), Directory_Entry() 함수를 다시 통과해서 Directory안에 들어있는 Regular 파일을 구한다.

              Dir_name 파일에 들어있는 f_File_name을 출력한다.

              Directory 파일이 없을 때 까지 반복한다.

 

 

== 실행결과 ==

  • ext4_image1.txt 이미지 실행 결과

 

  • ext4_image2.txt 이미지 실행결과

ext4_image1.001 이미지를 HxD 프로그램으로 분석하고 파일을 추출해보자.

 

 

1. HxD 프로그램 열기

[도구] - [디스크 이미지 열기] - [ext4_image1.001 파일 선택]

디스크 이미지 열기를 하면 섹터 (512byte) 단위로 분석할 수 있다.

 

 

2. MBR 분석

MBR이란 저장 매체(물리 디스크)의 첫 번째 섹터에 위치하며 512Byte의 크기이다.

맨 마지막 2byte 가 MBR signature 부분이다. 

MBR signature = 0x55AA

 

연두색 네모 부분이 다음과 같은 구조를 가진다.

 

• Part type

    -  0x83 --> Linux native file system (ex2fs/xiafs)

 

• LBA 시작주소 (Starting LBA Addr)

   - 실제 파티션이 시작되는 섹터의 위치

   - 주소계산 할 때 사용

   - 0x00000800 = 2048 (Little Endian)

 

 

3. Super Block

LBA 시작주소인 섹터 2048이 Superblock의 주소이다.

 

[ 섹터 2048, 2049 ]

섹터 2048, 2049는 다음과 같이 0패딩이 되어있어 0으로 채워져 있다.

 

 

[ 섹터 2050 ~ ] 

 

superblock은 총 1024byte로 실제 사용하는 영역은 상위 264byte이고 나머지 하위 영역은 예약된 영역이다.

아래와 같은 구조를 가진다.

•  Log Block Size

  - 블록의 크기 지정

  - 0 : 1KB, 1 : 2KB, 2 : 4KB

  - 0x02 = 2 --> 블록 크기는 4KB이다.

 

•  Block Per Group

  - 각 블록 그룹에 속한 블록 개수

  - 0x8000 = 32768

 

•  Inode Per Group

  - 각 블록 그룹에 속한 아이노드 개수

  - 0x1FF0 = 8176

 

•  Magic Signature

  - 슈퍼블록의 시그니처

  - 0x53EF

 

Super Block은 총 1block의 크기를 지닌다. 

블록의 크기가 4KB이므로 SuperBlock은 8sector의 크기이다. (1KB = 1024byte) 

 

• Boot Sector 영역 (0패딩) = 2sector

• Superblock의 실제 영역 = 2sector

• unused 영역 = 4sector

따라서 Group Descriptor Table 영역 시작 주소는 섹터 2056이다.

 

 

4. Group Descriptor Table

Group Descriptor Table 시작 주소인 섹터 2056으로 이동하자.

연두색 네모 부분이 다음과 같은 구조를 가진다.

맨 처음 32byte 부분이 0번 블록그룹 정보이다.

그 다음도 32byte씩 1번, 2번 등등의 블록그룹 정보가 저장되어 있다.

0번 블록그룹 정보를 자세히 살펴보자.

 

 Block Bitmap 시작 주소 ( Starting Block Address of Block Bitmap )

  - 0x0396 = 918

  - 섹터단위 주소계산

                 = Block Bitmap 시작 주소 x 1 block 크기 + LBA 시작주소

                 = ( 918 x 8 ) + 2048 = 9392

 

 Inode Bitmap 시작주소

  - 0x03A6 = 934

  - 섹터단위 주소계산 = ( 934 x 8 ) + 2048 = 9520

 

 Inode Table 시작주소

  - 0x03B6 = 950

  - 섹터단위 주소계산 = ( 950 x 8 ) + 2048 = 9648

 

 

5. Block Bitmap

Block Bitmap 시작 주소인 섹터 9392로 이동하자.

Block Bitmap은 블록의 사용 현황을 Bit로 표현하여 나타낸 것이다.

 

ex) 0xFF = 0b1111 1111

            --> 1의 개수 = 8

            --> 8개 블록 사용중

 

 오프셋 496000 ~ 496474 : 0xFF로 채워져 있다.

                          --> 8 * ( 16 * 47 + 5 ) = 5056

 오프셋 496475 : 0x0F

                          --> 4

   

                          --> 5056 + 4 = 6060

 

 블록현황 : 6060개 블록 사용 중

 

 

6. Inode Bitmap

Inode Bitmap 시작 주소인 섹터 9520으로 이동하자.

Inode Bitmap은 아이노드의 사용 현황을 Bit로 표현하여 나타낸 것이다.

 0xFFFF = 0b 1111 1111 1111 1111

 아이노드 현황 : 16개 아이노드 사용 중

 

 

7. Inode Table

Inode Table 시작 주소인 섹터 9648으로 이동하자.

Inode 크기 = 256byte

한 섹터 당 두개의 Inode를 지니고 있다.

Inode 2번이 Root Directory이다. 

따라서 Inode 2번을 자세히 살펴보자.

 

 

[ Inode 2번 ]

Inode의 구조는 다음과 같다.

 

 Direct Pointer

   - 파일의 내용이 들어있는 블록의 번호 기록

   - 루트 디렉토리의 포인터

   - 0x23A6 = 9126

   - 섹터주소 계산 : ( 9126 * 8 ) + 2048 = 75056

 

 

8. Directory Entry

Director Entry 구조는 다음과 같다.

 

다음과 같이 lost+found 디렉토리 1개와 KAKAO.jpeg, TEST_FILE, apeach.png, kakao_apeach.png, toystory.jpeg 파일 5개가 이미지 안에 저장되어 있는 것을 알 수 있다.

 

먼저 KAKAO.jpeg 파일을 추출하기 위해 아이노드 12번을 자세히 살펴보자.

 

 

8.1. KAKAO.jpeg

Inode 1번이 섹터 9648이므로 Inode 11번은 섹터 9653이다.

 

[ 아이노드 12번 ] 

 Direct Pointer : 0x8396 = 33686

 주소 계산 : ( 33686 * 8 ) + 2048 = 271536

 

 

KAKAO.jpeg 정보가 저장되어 있는 섹터 271536로 이동하자.

 

 JPEG 파일

   - 헤더 시그니처 : FF D8 FF E0 --> 오프셋 : 8496000

   - 푸터 시그니처 : FF D9 --> 오프셋 : 849CC99

 

1) [편집] - [블록단위 선택]  :  오프셋 8496000 부터 오프셋 849CC99 까지 블록선택을 하고 복사를 한다.

2) [파일] - [새로 만들기]  :  새로운 창에 복사한 값을 붙여넣기 삽입한다.

3) KAKAO.jpeg로 저장한다.

 

KAKAO.jpeg 파일을 열어보면 다음과 같은 사진을 볼 수 있다.

 

 

8.2. TEST_FILE

[ 아이노드 13번 ]

 Direct Pointer : 0x839D = 33693

 섹터 주소 계산 : ( 33693 * 8 ) + 2048 = 271592

 

TEST_FILE 정보가 저장되어 있는 섹터 271592로 이동하자.

2020 FaS filesystem!!!! 라고 저장되어 있는 것을 볼 수 있다.

 

 

8.3. apeach.png

[ 아이노드 14번 ]

파일 사이즈가 0으로 파일을 찾을 수 없다.

 

 

8.4. kakao_apeach.png

[ 아이노드 15번 ]

 Direct Pointer : 0x839E = 33694

 섹터 주소 계산 : ( 33694 * 8 ) + 2048 = 271600

 

kakao_apeach.png 정보가 저장되어 있는 섹터 271600으로 이동하자.

 PNG 파일

   - 헤더 시그니처 : 89 50 4E 47 0D 0A 1A 0A --> 오프셋 : 849E000

   - 푸터 시그니처 : 49 45 4E 44 AE 42 60 82 --> 오프셋 : 855B23F

 

1) [편집] - [블록단위 선택]  :  오프셋 849E000 부터 오프셋 855B23F 까지 블록선택을 하고 복사를 한다.

2) [파일] - [새로 만들기]  :  새로운 창에 복사한 값을 붙여넣기 삽입한다.

3) kakao_apeach.png로 저장한다.

 

kakao_apeach.png 파일을 열어보면 다음과 같은 사진을 볼 수 있다.

 

 

8.5. toystory.jpeg

[ 아이노드 16번 ]

 Direct Pointer : 0x845C = 33884

 주소 계산 : ( 33884 * 8 ) + 2048 = 273120

 

toystory.jpeg 정보가 저장되어 있는 섹터 273120로 이동하자.

 JPEG 파일

   - 헤더 시그니처 : FF D8 FF E0 --> 오프셋 : 8496000

   - 푸터 시그니처 : FF D9 --> 오프셋 : 849CC99

 

1) [편집] - [블록단위 선택]  :  오프셋 849E000 부터 오프셋 855B23F 까지 블록선택을 하고 복사를 한다.

2) [파일] - [새로 만들기]  :  새로운 창에 복사한 값을 붙여넣기 삽입한다.

3) toystory.jpeg로 저장한다.

 

toystory.jpeg 파일을 열어보면 다음과 같은 사진을 볼 수 있다.

 

 

9. 총 정리

따라서 ext4_image1.001의 디렉터리 구조는 다음과 같다.

 

EXT4 파일 시스템이란?

  • 현재 가장 널리 사용되는 리눅스 파일시스템
  • 리눅스 커널 2.6(현재)에 기본 파일시스템으로 사용
  • EXT2/3/4의 기본 레이아웃은 동일
  • EXT4 특징
    1. 더 큰 파일 시스템과 파일 사이즈
    2. 파일 단편화 현상 해소
    3. 디스크 할당 정책
    4. 입출력 성능 향상

 

EXT 레이아웃

  • EXT 파일시스템은 Boot Sector와 여러 개의 Block Group들로 이루어져 있다. 
  • 블록의 크기는 1K, 2K, 4K 중에 설정이 가능하다.
  • Block Group 내부를 보면 super block, group descriptors, reserved GDT Blocks, block bitmap, inode bitmap, inode table, data block 구조로 이루어져 있다.
  • Data block 영역은 실제 파일이 저장되는 영역이며, 나머지 영역은 meta data가 저장되는 공간이다.

 

EXT2, 3 레이아웃 단점

하나의 블록 그룹이 가질 수 있는 최대 블록 개수

          = block bitmap이 표시할 수 있는 최대 블록 개수 * 1 block당 최대 블록 크기

          = 32KB * 4KB = 128 MB

 

128MB 보다 큰 파일은 Block Group 하나에 저장되지 않는다.

 

 

EXT4 Flexible Block Group  -  FLEX_BG

  • FLEX_BG 플래그를 사용하면, Block Group 0번이 2^log_group_per_flex 만큼의 관리 정보를 모두 가진다.
  • 플래그를 사용하지 않으면 예전 버전을 사용한다.

 

 

 

 

  [ 예 : log_group_per_flex가 3인 경우 ]

 

  Block Group 0번이 8개의 Block Group을 하나로 관리.

  FGBBM(그룹 0~8에 대한 Block Bitmap) – 8블록

  FGIBM(그룹 0~8에 대한 Inode Bitmap) - 8블록

  FGIT(그룹 0~8에 대한 Inode Table) – N * 8블록

 

   -> 메타 데이터 블록 사용이 크게 감소하고 디스크 효율성 향상되어

매우 큰 파일을 할당 가능

   -> 사용 가능한 연속 데이터 블록의 최대 범위가 128MB보다 커짐

 

 

 

 

 

EXT4 레이아웃  -  Super Block

  • Super Block은 전체 파일 시스템의 메타 데이터 저장소
  • 파일시스템에서 사용되는 주요 설정 정보들이 기록
  • 1024 byte 크기

 

[ struct ext4_super_block (1024 bytes) ]

 

[ Super Block에 저장되는 주요 데이터 ]

1. 블록의 크기(1KB, 2KB, 4KB)

2. 총 블록의 개수

3. 블록 그룹의 개수

4. Inode의 개수

5. 그룹 내의 블록/Inode의 개수

 

 

 

 

EXT4 레이아웃  -  Group Descriptor Table

해당 파일시스템 내의 모든 블록 그룹에 대한 정보를 기록

 

[ struct ext4_group_desc (64 bytes) ]

[ Group Descriptor Table에 저장되는

주요 데이터 ]

1. Block Bitmap의 블록 번호

2. Inode Bitmap의 블록 번호

3. 첫 번째 Inode Table Block의 블록 번호

4. 그룹 안에 있는 빈 블록 수

5. 그룹 안에 있는 Inode 수

6. 그룹 안에 있는 빈 디렉터리 수

 

 

 

EXT4 레이아웃  -  Super Block & Group Descriptor Table

  • Block Group 0에 있는 Super Block 및 Block Descriptor의 사본이 모든 블록 그룹의 앞에 복제되어 사용한다.
  • Sparse super 기능 플래그가 설정되면 그룹 번호가 0 또는 3, 5, 7의 거듭제곱인 그룹에만 Super Block 및 group descriptors의 사본이 복제된다. 이로 인해 공간을 상당히 절약할 수 있다.
  • 플러그가 설정되어 있지 않다면 모든 그룹에서 복제된다.

 

EXT4 레이아웃  -  Block Bitmap & Inode Bitmap

Bitmap

  • Block Bitmap은 블록의 사용 현황을 Bit로 표현하여 나타낸 것으로 추후 새로운 블록 할당 시 유용하게 쓰임.
  • 그룹 내에 존재하는 각각의 블록은 Block Bitmap에서 하나하나의 Bit에 해당. (사용되는 곳의 bit를 1로 표시)
  • Block Bitmap은 하나의 블록 안에 기록되어야 하며, 블록의 크기는 1,2,4 KB로 나뉘기 때문에 Block Bitmap은 블록의 크기에 따라 8, 16, 32 KB (8192, 16384, 32768) 개의 블록 현황을 나타낼 수 있다.
  • Inode Bitmap은 블록에 대한 정보가 Inode에 대한 정보로 바뀔 뿐 기능적으로 block Bitmap과 동일

 

Inode

  • Inode는 파일과 디스크 영역의 관련 내용을 정리한 것.
  • 모든 파일과 디렉터리들은 각각 1개의 inode를 할당하며, 모든 inode들은 고유한 주소를 가짐
  • inode 인덱스는 1부터 시작
  • Inode의 크기  ->  EXT2,3 : 128byte,  EXT4 : 256byte

[ 특정 Inode의 주소를 알면 Inode가 속한 Block Group 유추 가능 ]

 

Block Group = ( Inode – 1 ) / INODES_PER_GROUP

  • INODES_PER_GROUP의 값은 Super Block에 정의
  • Inode의 인덱스가 1부터 시작하기에 1을 빼준다.

 

EXT4 특징  -  Inode 크기 확장

 

 

 

 

  EXT4는 Inode 크기가 128byte에서 256byte로 확장

  처음 128byte는 동일한 레이아웃을 유지

  나머지 inode는 두 부분으로 나뉜다.

            - 나노초 타임스탬프

            - 빠른 확장 속성 (EA)

 

 

 

 

 

EXT4 레이아웃  -  Inode Table

  • Inode Table은 인접한 연속된 블록으로 이루어져 있으며 각 블록은 Inode 개수를 포함
  • Inode는 Inode Table에 저장되고, Inode Table의 위치는 Group Descriptor Table에 기록

[ struct ext4_inode (128/256 bytes) ]

 

 

 

i_block 필드는 Block Mapping에 사용

 

 

 

 

 

 

EXT2, 3 Block Mapping  -  Indirect Block Map

  • i_block : 파일의 데이터 블록을 가리키는 포인터 배열
  • i_block은 4byte씩 총 15개로 나뉘어져 60byte의 공간이 할당되어 있다.
  • 15개의 칸은 12개의 Direct block과 각각 하나 씩의 indirect, double indirect, triple indirect로 구성

 

 

[ 예) 블록 크기가 4K일 경우 ]

  • Direct Block : 12 * 4KB = 48KB
  • 1 block에 저장할 수 있는 정보 개수 : 4KB / 4byte = 1024개
  • Indirect Block : 1024 * 4KB = 4MB
  • Double Indirect Block : 1024 * 1024 * 4KB = 4GB
  • Triple Indirect Block : 1024 * 1024 * 1024 * 4KB = 4TB

      -> 하나의 Inode에 저장할 수 있는 파일 크기 

                      : 48KB + 4MB + 4GB + 4TB

      -> Indirect Block Map 구조는 큰 연속 파일에 대해서

          많은 매핑 데이터를 채워야 해서 비효율적

 

 

 

 

EXT4 Block Mapping  -  Extent Tree

  • Inode flag에 USE_EXTENT가 설정되어 있다면 Extent tree 구조를 사용한다.
  • i_block : Extent 정보 (60byte)  ->  Extent :한 번에 예약하여 처리할 수 있는 연속된 물리적 블록의 범위
  • inode (60byte) = extent_header(12byte) + extent(12byte) 4개
  • extent를 활용하면 지정된 파일에 필요한 inode 수를 줄이고 단편화를 감소시키며, 큰 용량의 파일을 쓸 때 메타 데이터의 양이 줄어 성능을 향상시킬 수 있다.

   [ Struct ext4_extent ( 12byte ) ]

   물리적 블록 필드 (48bit) – extent가 시작되는 파일 시스템 블록

   논리적 블록 필드 (32bit) – 블록 실행이 시작되는 파일의 오프셋

 

   [ Struct ext4_extent_header (12byte) ]

   eh_magic : 0xF30A

   eh_entries : 유효한 extent 항목 수

   eh_max : 최대 extent 항목 수

 

   [ Struct ext4_extnet_idx(12byte) ]

   ei_block : 인덱스 참조가 시작하는 파일 블록을 나타냄 (4byte)

   ei_leaf : extent 구조를 포함하는 파일 시스템 블록을 가리킴

 

 

 

  • 트리의 각 노드는 extent header로 시작한다.
  • Inode 구조에서 루트는 하나 이상의 extent index를 보유하는 index node를 가리킨다.
  • 그 다음 각 index는 파일 extent가 포함된leaf node를 가리킨다.
  • 각 extent는 파일의 연속 블록을 가리킨다.
  • 단일 extent는 블록 크기가 4KB이면 2^15개의 연속 블록 또는 128MB 나타낼 수 있다.

 

 

 

EXT4 특징  -  블록 할당

< Mutiblock Allocation >

  • Ext4는 매 호출마다 싱글 블록을 할당하는 대신 많은 오버헤드를 피하기 위해서 한 번의 호출로 많은 블록을 할당할 수 있는 다중 블록 할당자 (multiblock allocator)를 사용
  • 동일한 cpu를 사용하는 작은 파일은 물리적으로 서로 가깝게 배치되고, 큰 파일은 사전 할당을 통해 조각화가 줄어든다.

< Delayed Allocation >

  • 지연할당은 데이터를 기록하는 중에 파일 시스템 블록이 한 번에 하나씩 사용되는 대신 할당 요청이 메모리에 유지.
  • 그 파일이 실제로 디스크에 쓰여질 때 블록이 할당
  • 이전에 여러 요청을 한 항목으로 요약할 수 있으므로 시간이 절약
  • 조금씩 크기가 커지는 파일이 여러 조각으로 나뉘어 저장되는 파일 조각화를 방지
  • 짧은 수명의 임시 파일들에 대해서는 디스크 블록이 할당되지 않기 때문에 할당 해제 작업도 필요하지 않다

< Extent + Multi Allocation + Delayed Allocation 입출력 성능 향상 >

  • Ext4의 extent, Multi Allocation, Delayed Allocation은 서로의 특성과 함께 효율적으로 디스크 할당을 수행
  1. 파일이 디스크에 쓰기를 마쳤을 때 생기는 많은 작업량은 extent로 인접한 블록으로의 할당이 준비
  2. Multi allocation에 의해 많은 작업량에 대한 큰 블록이 할당될 수 있음
  3. 지연 할당은 위의 두 특성이 효율적으로 작동할 수 있도록 해줌

 

EXT4 특징  -  확장성

< Ext2, 3 단점 - 확장성 한계 >

  • 파일 크기는 i_blocks 카운터 값을 기반으로 계산되지만 장치는 파일 시스템 블록 크기(4KB)가 아닌 섹터(512byte)를 기반으로 한다.
  • Ext2, 3의 i_blocks는 inode 구조에서 32bit 주소 체계를 나타낸다.
  • Ext2, 3 최대 파일 크기 : 2^32 * 512 byte = 2^41 byte = 2TB로 제한
  • Ext2, 3 최대 파일 시스템 크기 : 2^32 * 4KB = 2^44 = 16TB로 제한

 

< Ext4 최대 파일 크기 확장 ( + HUGE_FILE ) >

  • Ext4 inode의 i_blocks의 단위를 파일 시스템 블록으로 변경
  • HUGE_FILE 플래그가 추가되면 Inode에 EXT4_HUGE_FILE_FL이 표시되어 일부 inode의 i_blocks 필드가 파일 시스템 블록 크기 단위 임을 나타냄
  • EXT4 최대 파일 크기 : 2^32 * 4KB = 2^44 = 16TB로 확장

 

< Ext4 최대 파일 시스템 크기 확장 ( + i_blocks 확장 ) >

  • 예약된 inode 필드 중 일부를 사용하여 i_blocks (주소 체계)을 48bit로 확장
  • EXT4 최대 파일 시스템 크기 : 2^48 * 4KB = 1EB로 확장

 

EXT4 특징  -  서브 디렉터리 수 한계

< Ext2, 3 단점 - 확장성 한계 >

  • EXT3는 서브 디렉터리 수는 32000개가 한계
  • EXT3에서 디렉터리 항목은 연결된 목록에 저장되므로, 항목 수가 많은 디렉터리에는 매우 비효율적

 

< Ext4 디렉터리 색인 기능 >

  • 32bit 해시를 사용하는 Htree 데이터 구조에 디렉터리 항목을 저장하여 확장성 문제를 해결
  • 조회시간을 줄여서 큰 디렉터리에서 성능을 향상시키는 데 도움
  • 파일 시스템  Super Block에서 주어진 파일 이름과 시드를 사용하여 32bit 해시를 계산
  • 해시는 디렉터리 항목을 포함하는 블록 또는 인덱스 블록을 직접 가리키는 데 사용
  • 인덱스 블록의 크기는 고정되어 있고 정렬되어 있으므로 이진 검색이 사용
  • 대상 블록을 얻은 후에는 색인 없이 블록의 항목을 선형으로 검색하여 조회가 정확하게 진행됨
  • 서브 디렉터리 수 제한이 없다.

 

EXT4 특징 정리

 

파일 시스템이란?

file system

  • 데이터를 효과적으로 관리하기 위해 파일을 체계적으로 기록 및 보관하는 방식
  • 파일의 위치, 파일의 이름, 디렉터리 등 계층 구조로 데이터가 저장되고 조직화되도록 하는 메커니즘
  • 압축, 암호화, 저널, 동적 할당, 다국어 지원 등 추가 기능 지원
  • 저장장치 공간이 커질수록 파일 수 또한 증가하며 파일 시스템이 필요해 짐

 

파일 시스템의 종류

운영체제마다 지원하는 파일 시스템이 다름.

 

파일 시스템의 추상적 구조

  • 거의 모든 파일 시스템은 메타 영역과 데이터 영역으로 구분
  • Data Area – 파일의 실제 데이터 기록
  • Meta Area - 파일의 이름, 위치, 크기, 시간 정보, 삭제 유무 등 기록
  • 데이터 영역에 기록된 모든 파일은 메타 영역에 의해 해당 정보를 얻을 수 있으므로 직접 파일 데이터가 필요한 경우가 아니라면 메타 영역만의 접근으로 해당 파일 정보를 확인할 수 있다.
  • 메타 영역을 어떻게 구분하는지에 따라 파일 시스템이 구분된다.

 

파일 시스템의 구성 요소 - Cluster & Block

  • 파일 시스템들은 데이터 관리와 CPU 성능 효율을 위해 섹터(512byte) 단위로 데이터를 관리하지 않고 여러 섹터를 묶은 개념인 클러스터 또는 블록을 사용
  • Window - 클러스터, Linux – 블록
  • 클러스터 및 블록의 크기는 저장장치의 크기 및 사용 용도에 따라 달라진다.
  • 클러스터 또는 블록의 크기보다 파일의 크기가 작아서 공간이 남게 되면 나머지 공간은 사용할 수 없게 되어 낭비되는 공간이 발생
  • 그럼에도 불구하고 I/O 효율이 크기 때문에 사용

 

파일 시스템의 구성 요소 - Slack Space Area

  • 슬랙 공간은 저장매체의 물리적인 구조와 논리적인 구조의 차이로 발생하는 낭비 공간
  • 일반적으로 램 슬랙, 드라이브 슬랙, 파일시스템 슬랙, 볼륨 슬랙으로 나눌 수 있다.
  • (디지털 포렌식 관점) 슬랙 공간에 정보 은닉 가능성, 파일 복구와 삭제된 파일의 파편 조사
    1. RAM Slack : 파일의 끝을 알 수 있기 때문에 삭제된 파일 복구 시 유용하게 사용
    2. Drive Slack : 이전에 사용한 데이터가 존재, 흔적 조사에 활용

+ Recent posts