Created
January 14, 2021 10:30
-
-
Save zhangyoufu/06919f592cb2fe9faeac22b469e016fa to your computer and use it in GitHub Desktop.
Secure Boot related blob parser (for PK/KEK/db/dbx/dbxupdate), requires Python 3.7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| from dataclasses import dataclass, field | |
| import enum | |
| import uuid | |
| class Bytes(bytes): | |
| def __str__(self): | |
| return self.hex() | |
| def __repr__(self): | |
| return self.hex() | |
| class Enum(enum.Enum): | |
| def __str__(self): | |
| return self.name | |
| def __repr__(self): | |
| return self.name | |
| def u16(data): | |
| assert len(data) == 2 | |
| return int.from_bytes(data, byteorder='little') | |
| def u32(data): | |
| assert len(data) == 4 | |
| return int.from_bytes(data, byteorder='little') | |
| class EFI_SIGNATURE_TYPE(Enum): | |
| EFI_CERT_SHA256_GUID = uuid.UUID('c1c41626-504c-4092-aca9-41f936934328') | |
| EFI_CERT_RSA2048_GUID = uuid.UUID('3c5766e8-269c-4e34-aa14-ed776e85b3b6') | |
| EFI_CERT_RSA2048_SHA256_GUID = uuid.UUID('e2b36190-879b-4a3d-ad8d-f2e7bba32784') | |
| EFI_CERT_SHA1_GUID = uuid.UUID('826ca512-cf10-4ac9-b187-be01496631bd') | |
| EFI_CERT_RSA2048_SHA1_GUID = uuid.UUID('67f8444f-8743-48f1-a328-1eaab8736080') | |
| EFI_CERT_X509_GUID = uuid.UUID('a5c059a1-94e4-4aa7-87b5-ab155c2bf072') | |
| class EFI_SIGNATURE_OWNER(Enum): | |
| EFI_SIGNATURE_OWNER_CANONICAL = uuid.UUID('685984e3-5d0f-4682-94c1-0f85ecb55d34') | |
| EFI_SIGNATURE_OWNER_MICROSOFT = uuid.UUID('77fa9abd-0359-4d32-bd60-28f4e78f784b') | |
| @dataclass | |
| class EFI_SIGNATURE_DATA: | |
| Owner: uuid.UUID | |
| Data: Bytes | |
| @classmethod | |
| def from_bytes(cls, data): | |
| return cls( | |
| Owner=EFI_SIGNATURE_OWNER(uuid.UUID(bytes_le=data[:0x10])), | |
| Data=Bytes(data[0x10:]), | |
| ) | |
| @dataclass | |
| class EFI_SIGNATURE_LIST(list): | |
| Type: EFI_SIGNATURE_TYPE | |
| Size: int | |
| Header: Bytes | |
| @classmethod | |
| def from_bytes(cls, data): | |
| Type = EFI_SIGNATURE_TYPE(uuid.UUID(bytes_le=data[:0x10])) | |
| ListSize = u32(data[0x10:0x14]) | |
| assert len(data) >= ListSize | |
| HeaderSize = u32(data[0x14:0x18]) | |
| SignatureSize = u32(data[0x18:0x1C]) | |
| assert SignatureSize > 0 | |
| offsetof_Signatures = 0x1C + HeaderSize | |
| sizeof_Signatures = ListSize - offsetof_Signatures | |
| assert sizeof_Signatures % SignatureSize == 0 | |
| Signatures = data[offsetof_Signatures:ListSize] | |
| self = cls( | |
| Type=Type, | |
| Size=ListSize, | |
| Header=Bytes(data[0x1C:0x1C+HeaderSize]), | |
| ) | |
| self += [ | |
| EFI_SIGNATURE_DATA.from_bytes(Signatures[offset:offset+SignatureSize]) | |
| for offset in range(0, sizeof_Signatures, SignatureSize) | |
| ] | |
| return self | |
| def __repr__(self): | |
| Signatures = '\n'.join(f' {item},' for item in self) | |
| return f'EFI_SIGNATURE_LIST(Type={self.Type}, Header={self.Header}, Signatures=[\n{Signatures}\n])' | |
| @dataclass | |
| class EFI_TIME: | |
| Year: int | |
| Month: int | |
| Day: int | |
| Hour: int | |
| Minute: int | |
| Second: int | |
| Nanosecond: int | |
| TimeZone: int | |
| Daylight: int | |
| @classmethod | |
| def from_bytes(cls, data): | |
| assert len(data) == 16 | |
| Pad1 = data[7] | |
| Pad2 = data[15] | |
| assert Pad1 == Pad2 == 0 | |
| return cls( | |
| Year=u16(data[0:2]), | |
| Month=data[2], | |
| Day=data[3], | |
| Hour=data[4], | |
| Minute=data[5], | |
| Second=data[6], | |
| Nanosecond=u32(data[8:12]), | |
| TimeZone=u16(data[12:14]), | |
| Daylight=data[14], | |
| ) | |
| class WIN_CERT_TYPE(Enum): | |
| WIN_CERT_TYPE_PKCS_SIGNED_DATA = 0x0002 | |
| WIN_CERT_TYPE_EFI_PKCS115 = 0x0EF0 | |
| WIN_CERT_TYPE_EFI_GUID = 0x0EF1 | |
| @dataclass(init=False) | |
| class WIN_CERTIFICATE: | |
| Length: int | |
| Revision: int | |
| CertificateType: WIN_CERT_TYPE | |
| Certificate: Bytes | |
| @classmethod | |
| def from_bytes(cls, data): | |
| self = cls() | |
| self.Length = u32(data[0:4]) | |
| assert self.Length > 8 | |
| self.Revision = u16(data[4:6]) | |
| assert self.Revision == 0x0200, f'Revision=0x{self.Revision:X}' | |
| self.CertificateType = WIN_CERT_TYPE(u16(data[6:8])) | |
| self.Certificate = data[8:self.Length] | |
| return self | |
| def __len__(self): | |
| return self.Length | |
| class EFI_CERT_TYPE(Enum): | |
| EFI_CERT_TYPE_PKCS7_GUID = uuid.UUID('4aafd29d-68df-49ee-8aa9-347d375665a7') | |
| EFI_CERT_TYPE_RSA2048_SHA256_GUID = uuid.UUID('a7717414-c616-4977-9420-844712a735bf') | |
| @dataclass(init=False) | |
| class WIN_CERTIFICATE_UEFI_GUID(WIN_CERTIFICATE): | |
| Certificate: Bytes = field(repr=False) | |
| CertType: EFI_CERT_TYPE | |
| CertData: Bytes | |
| @classmethod | |
| def from_bytes(cls, data): | |
| self = super().from_bytes(data) | |
| self.CertType = EFI_CERT_TYPE(uuid.UUID(bytes_le=self.Certificate[:16])) | |
| self.CertData = Bytes(self.Certificate[16:]) | |
| return self | |
| @dataclass | |
| class EFI_VARIABLE_AUTHENTICATION_2: | |
| TimeStamp: EFI_TIME | |
| AuthInfo: WIN_CERTIFICATE_UEFI_GUID | |
| @classmethod | |
| def from_bytes(cls, data): | |
| return cls( | |
| TimeStamp=EFI_TIME.from_bytes(data[:16]), | |
| AuthInfo=WIN_CERTIFICATE_UEFI_GUID.from_bytes(data[16:]), | |
| ) | |
| def __len__(self): | |
| return 0x10 + len(self.AuthInfo) | |
| def __repr__(self): | |
| return f'EFI_VARIABLE_AUTHENTICATION_2(\n TimeStamp={self.TimeStamp},\n AuthInfo={self.AuthInfo},\n)' | |
| def parse_binary(data): | |
| if data[:4] == b'\x27\x00\x00\x00': # EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE | |
| # skip 4 bytes attributes exposed by efivarfs filesystem | |
| data = data[4:] | |
| if data[22:24] != b'\x00\x00': | |
| var_auth_2 = EFI_VARIABLE_AUTHENTICATION_2.from_bytes(data) | |
| print(var_auth_2) | |
| data = data[len(var_auth_2):] | |
| offset = 0 | |
| while offset < len(data): | |
| sig_list = EFI_SIGNATURE_LIST.from_bytes(data[offset:]) | |
| print(sig_list) | |
| offset += sig_list.Size | |
| def parse_file(path): | |
| import mmap | |
| with open(path, 'rb') as f: | |
| try: | |
| data = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) | |
| except OSError: | |
| data = f.read() | |
| parse_binary(data) | |
| def main(): | |
| import argparse | |
| parser = argparse.ArgumentParser('Secure Boot related blob parser (for PK/KEK/db/dbx/dbxupdate)') | |
| parser.add_argument('path', help='path to input blob') | |
| args = parser.parse_args() | |
| parse_file(args.path) | |
| if __name__ == '__main__': | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment