Digital Forensics/File System

#3 GPT - GPT 복구

geunyeong 2022. 1. 8. 22:49

Table of Contents

    Abstract

    이번 글에서는 Backup GPT에 대한 보다 자세한 설명과 함께 Backup GPT를 사용해 Primary GPT를 복원하는 방법에 대해 알아본다. 백업 GPT는 주 GPT에 오류가 발생했을 때 이를 복구하기 위해 존재하는 공간이며, 어렵지 않은 방법으로 주 GPT를 복구할 수 있다. GPT에 대한 설명은 아래 링크를 참조하길 바란다.

    • #1 GPT - GUID Partition Table (1)
    https://geun-yeong.tistory.com/70
    • #2 GPT - GUID Partition Table (2)
    https://geun-yeong.tistory.com/71

    Backup GPT

    GPT Disk Layout

    GPT는 Primary GPT 뿐만 아니라 Backup GPT도 있다. Backup GPT는 Primary GPT에 손상이 생겼을 때 이를 복구하는 용도로 사용된다. 때문에 Primary GPT에 수정(update) 사항이 생기면 Backup GPT도 함께 수정되어야 한다. 아래 그림은 GPT의 디스크 레이아웃에서 Backup GPT를 나타내고 있다.

    Disk Layout of GPT Partition System

    Backup GPT는 Backup GPT Header와 Backup GPT Partition Entry Array로 구성된다.

    Backup GPT Header

    백업 GPT 헤더는 주 GPT 헤더에 대한 백업본이다. 구조와 저장되는 값 모두 주 GPT 헤더와 동일하나 CRC, GPT LBA, Partition Entry LBA 값만 다르다. 백업 GPT 헤더는 백업 GPT를 기준으로 하는 값들을 가지게 된다.

    Primary GPT Header(좌)와 Backup GPT Header(우)의 차이

    아래 표는 주 GPT 헤더와 백업 GPT 헤더 간 차이를 정리한 것이다.

    영역 오프셋(Hex) 크기 차이
    Signature 0 8 Primary와 Backup 모두 "EFI PART"로 동일하다.
    Revision 8 4 Primary와 Backup 모두 0x00010000으로 동일하다.
    HeaderSize 12(0xC) 4 Primary와 Backup 모두 92(0x5C)로 동일하다.
    HeaderCRC32 16(0x10) 4 Primary와 Backup의 CRC32 값이 다르다. MyLBA, AlternateLBA, PartitionEntryLBA 값이 다르기 때문이다.
    Reserved 20(0x14) 4 -
    MyLBA 24(0x18) 8 Primary는 1인 반면 Backup은 0x7FFFFF다. Primary는 1번 LBA에, Backup은 마지막 섹터에 위치하다보니 자기 자신의 LBA를 가리키는 값에서 차이가 생긴다. 위 그림은 4 GB 크기의 VHD에서 백업 GPT 헤더를 추출한 것이기 때문에 마지막 섹터인 0x7FFFFF가 MyLBA 값으로 저장됐다.
    AlternateLBA 32(0x20) 8 Primary는 마지막 섹터이고, Backup은 1이다. 서로 상대방의 GPT 헤더를 가리키다보니 반대되는 값을 가진다.
    FirstUsableLBA 40(0x28) 8 Primary와 Backup 모두 34(0x22)로 동일하다.
    LastUsableLBA 48(0x30) 8 Primary와 Backup 모두 0x7FFFDE로 동일하다.
    DiskGUID 56(0x38) 16 Primary와 Backup 모두 F8463214-BB31-4888-9C80-322FCC9BC031로 동일하다.
    PartitionEntryLBA 72(0x48) 8 Primary는 2를, Backup은 마지막섹터-33을 가리키고 있다. 주 GPT 헤더는 주 파티션 테이블을, 백업 GPT 헤더는 백업 파티션 테이블을 가리키기 때문이다.
    NumberOfPartitionEntires 80(0x50) 4 Primary와 Backup 모두 128(0x80)로 동일하다.
    SizeOfPartitionEntry 84(0x54) 4 Primary와 Backup 모두 128(0x80)로 동일하다.
    PartitionEntryArrayCRC32 88(0x58) 4 Primary와 Backup 모두 0x3B43B040으로 동일하다. 파티션 테이블(GPT Partition Entry Array)는 Primary와 Backup 간의 차이가 없기 때문이다.
    Reserved 92(0x5C) ~ -

    Backup GPT Partition Entry Array

    백업 파티션 테이블은 백업 GPT 헤더보다 32 섹터 앞에서부터 시작한다. 전체 n개 섹터를 가진 디스크라면 n-1 섹터에 백업 GPT 헤더가, 그보다 32 섹터 앞인 n-33부터 백업 파티션 테이블이 시작되는 셈이다. 32 섹터인 이유는 일반적으로 엔트리의 크기가 128 바이트, 최대 개수가 128개이기 때문에 GPT Partition Entry Array 영역이 32개 섹터를 필요로 하기 때문이다. 크기가 더 크거나 개수가 더 많아진다면 Primary 뿐만 아니라 Backup 파티션 테이블 영역도 더 커진다.

    4 GB 크기의 VHD를 예로 들면, VHD는 총 8,388,608(0x800000) 개의 섹터가 존재한다. 마지막 섹터(n-1 섹터)인 8,388,607(0x7FFFFF)에 백업 GPT 헤더가 있는 건 앞서 봤다. 파티션 테이블이 32개 섹터를 사용하므로 백업 GPT 헤더보다 32개 앞 섹터인 8,388,575(0x7FFFDF = 백업 GPT 헤더 LBA - 32 = n - 33 섹터)부터 백업 파티션 테이블이 시작될 것이다. 이는 백업 GPT 헤더에서 PartitionEntryLBA 값과 동일하다.

    아래 그림은 n - 33 섹터인 8,388,575(0x7FFFDF 섹터에 백업 파티션 테이블이 존재하는 걸 나타내고 있다. 백업 파티션 테이블의 내용은 주 파티션 테이블 내용과 모두 동일하다(UEFI Specification 상으로 둘은 항상 동일하게 유지되어야 한다. Primary에 수정사항이 생기면 Backup에도 그 내용이 반영되도록 명시하고 있기 때문이다).

    Backup GPT Partition Entry Array

    Recovering a GPT

    본 글의 주제인 GPT를 복구하는 법을 알아보자. GPT를 복구하는 방법은 Backup GPT의 내용을 Primary GPT 영역에 덮어 쓰는 것이다. 하지만 앞서 백업 GPT 헤더에서 보았듯 주 GPT 헤더와 내용이 상이한 필드가 존재하기 때문에 이를 적절히 맞춰주는 작업을 해야 한다.

    Overwriting a Backup GPT

    먼저 백업 GPT 영역의 내용을 주 GPT 영역에 덮어쓴다. 

    백업 GPT를 주 GPT에 덮어쓰기

    만약 주 파티션 테이블이 멀쩡하다면 주 파티션 테이블도 백업 파티션 테이블 내용으로 덮어쓸 필요는 없다. 하지만 확실하게 하고자 한다면 백업 파티션 테이블 내용으로 주 파티션 테이블 영역까지 모두 덮어쓰는 것이 좋다. 주 GPT 헤더는 LBA 1에 위치해야 하므로 백업 GPT 헤더 내용을 LBA 1 섹터에 덮어 쓰고, 백업 파티션 테이블 내용은 GPT 헤더에 정의된 PartitionEntryLBA 필드가 가리키는 LBA에 덮어써야 한다. 본 예제에서 PartitionEntryLBA는 2이므로 백업 파티션 테이블 내용은 LBA 2부터 파티션 테이블 섹터 크기인 32 섹터 만큼을 덮어썼다.

    백업 GPT 헤더 내용을 주 GPT 헤더에 덮어쓰기

    Updating a MyLBA

    백업 GPT 헤더의 내용을 그대로 복사했기 때문에 MyLBA 값이 백업 GPT 헤더의 LBA를 가지고 있다. 주 GPT 헤더의 LBA에 맞게 1로 수정한다.

    MyLBA 수정

    Updating an AlternateLBA

    주 GPT 헤더의 AlternateLBA는 백업 GPT 헤더의 LBA다. 그러므로 백업 GPT 헤더가 위치한 0x7FFFFF를 AlternateLBA 필드의 값으로 수정한다.

    AlternateLBA 수정

    Updating a PartitionEntryLBA

    백업 GPT 헤더는 PartitionEntryLBA 필드에 백업 파티션 테이블의 시작 LBA를 갖고있다. 그러므로 주 파티션 테이블의 시작 LBA인 2로 수정해야 한다.

    PartitionEntryLBA 수정

    Updating a HeaderCRC32

    UEFI Specification 상으로 GPT 헤더의 체크섬을 계산하는 방법은 HeaderCRC32 필드를 0으로 채우고 HeaderSize 만큼에 해당하는 영역만 체크섬을 계산하도록 되어있다.

    Fill a HeaderCRC32 with NULL

    먼저 주 GPT 헤더의 HeaderCRC32 필드를 0으로 채운다.

    HeaderCRC32 필드를 0으로 채우기

    Calculate a Checksum

    GPT 헤더의 시작 오프셋부터 HeaderSize 만큼에 대한 CRC32 값을 구해야 한다. 구하는 방법은 개인이 편한대로 하면 된다. 필자는 CRC32를 계산해주는 웹 사이트를 사용해 CRC32 값을 계산했다. 주의 하자, GPT 헤더가 위치한 섹터 전체가 아니라 GPT 헤더의 크기만큼만 CRC32를 계산해야 한다.

    복원할 GPT 헤더에 대한 CRC32

    Write a Checksum to HeaderCRC32 field

    앞서 계산한 CRC32 값을 HeaderCRC32 필드에 넣어주면 된다. 단 바이트 오더를 고려해서 넣어야 한다.

    새로 계산한 CRC32로 HeaderCRC32 수정하기

    Result

    VHD를 시스템에 Attach 해보면 FAT, FAT32, NTFS, exFAT로 포맷된 파티션 4개가 모두 정상적으로 인식되는 걸 볼 수 있다. (볼륨 레이블을 할당하지 않아서 접근은 할 수 없다)

    복원된 GPT 헤더로 Windows에 Attach 하기

    Close

    이상으로 Backup GPT에 대한 설명과 Backup GPT를 사용해 Primary GPT를 복원하는 방법을 알아봤다. 대용량 디스크를 인식하고 사용할 수 있다는 점 때문에 오늘날엔 MBR보단 GPT를 사용해 디스크를 파티션하기 때문에 필수로 알아둬야 할 지식이다.

    만약 파티션 테이블에 대한 CRC32 체크섬도 다시 계산해야 한다면 GPT 헤더와 달리 파티션 테이블 영역 전체 섹터를 대상으로 CRC32를 계산해야 한다. 즉 파티션이 4개 뿐이어서 32개 섹터 중 1개 섹터만 사용중이어도 널 바이트로 채워진 31개 섹터까지 모두 포함해 32개 섹터에 대한 CRC32를 계산해야 한다. 이 또한 UEFI Specification에 명시된 것이다.