├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── cap.py ├── fd.py ├── ffs.py ├── fsdump.py ├── fv.py ├── guids.py ├── ichdesc.py ├── raw.py ├── rom.py ├── romdump.py ├── util.py └── xfv.py /.gitignore: -------------------------------------------------------------------------------- 1 | # python 2 | *.py[co] 3 | __pycache__/ 4 | 5 | # editors 6 | *~ 7 | *.bak 8 | .idea/ 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012 Andrew McRae 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | romdump 2 | ======= 3 | 4 | EFI bios rom dumping tools 5 | 6 | License 7 | ------- 8 | 9 | Copyright (c) Andrew McRae. Distributed under an [MIT License](LICENSE). 10 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | EFI ROM dumper 3 | """ 4 | -------------------------------------------------------------------------------- /cap.py: -------------------------------------------------------------------------------- 1 | """ 2 | CAP parser 3 | """ 4 | 5 | import os 6 | import struct 7 | from uuid import UUID 8 | 9 | from ichdesc import ICHDesc 10 | from fd import FD 11 | import guids as g 12 | 13 | 14 | _SIG_OFFSET = 0 15 | _SIG_SIZE = 0x10 16 | 17 | _S_HEADER = struct.Struct('< 16s I I I') 18 | _S_SIGNED_HEADER = struct.Struct('< H H') 19 | _S_FULL_HEADER = struct.Struct('< I 16s I I I I I I I I') 20 | 21 | 22 | class CAP(object): 23 | def __init__(self, data, start, prefix=''): 24 | self.start = start 25 | self.prefix = prefix 26 | offset = 0 27 | (guid_bytes, self.hdrlen, self.flags, self.size) = _S_HEADER.unpack_from(data, offset) 28 | offset += _S_HEADER.size 29 | if self.size > len(data): 30 | raise ValueError('bad size: 0x%x > 0x%x' % (self.size, len(data))) 31 | self.guid = UUID(bytes_le=guid_bytes) 32 | self.full_header = False 33 | self.signed_header = False 34 | if self.guid == g.EFI_SIGNED_CAPSULE_GUID: 35 | if self.hdrlen != 0x1C: 36 | raise ValueError('bad signed header size: 0x%x' % self.hdrlen) 37 | self.signed_header = True 38 | (self.body_offset, self.oem_offset) = _S_SIGNED_HEADER.unpack_from(data, offset) 39 | offset += _S_SIGNED_HEADER.size 40 | else: 41 | if self.hdrlen == 0x50: 42 | self.full_header = True 43 | (self.sequence_number, self.instance_id, self.split_offset, self.body_offset, self.oem_offset, 44 | self.author_string, self.revision_string, self.short_string, self.long_string, 45 | self.devices_offset) = _S_FULL_HEADER.unpack_from(data, offset) 46 | offset += _S_FULL_HEADER.size 47 | elif self.hdrlen == 0x1C: 48 | self.body_offset = offset 49 | self.oem_offset = None 50 | else: 51 | raise ValueError('bad header size: 0x%x' % self.hdrlen) 52 | self.hdr = data[:self.hdrlen] 53 | self.hdr_ext = data[self.hdrlen:self.body_offset] 54 | self.data = data[self.body_offset:self.size] 55 | if ICHDesc.check_sig(self.data): 56 | self.contents = ICHDesc(self.data, 0, prefix) # start + self.body_offset 57 | else: 58 | self.contents = FD(self.data, 0, prefix + 'bios_') 59 | 60 | def __str__(self): 61 | return '0x%08x+0x%08x: CAP' % (self.start, self.size) 62 | 63 | def showinfo(self, ts=' '): 64 | print ts + 'Size: 0x%x (header 0x%x) (ext 0x%x) (data 0x%x)' % (self.size, len(self.hdr), len(self.hdr_ext), 65 | len(self.data)) 66 | print ts + str(self.contents) 67 | self.contents.showinfo(ts + ' ') 68 | 69 | def dump(self): 70 | fnprefix = 'cap_%s%08x' % (self.prefix, self.start) 71 | fn = '%s.bin' % fnprefix 72 | fn = os.path.normpath(fn) 73 | print 'Dumping CAP to %s' % fn 74 | dn = os.path.dirname(fn) 75 | if dn and not os.path.isdir(dn): 76 | os.makedirs(dn) 77 | with open(fn, 'wb') as fout: 78 | fout.write(self.hdr) 79 | fout.write(self.hdr_ext) 80 | 81 | self.contents.dump() 82 | 83 | @staticmethod 84 | def check_sig(data, offset=0): 85 | offset += _SIG_OFFSET 86 | return data[offset:offset + _SIG_SIZE] in g.CAPSULE_GUIDS 87 | -------------------------------------------------------------------------------- /fd.py: -------------------------------------------------------------------------------- 1 | """ 2 | FD parser 3 | """ 4 | 5 | import os 6 | 7 | from fv import FV 8 | from raw import RAW 9 | 10 | 11 | class FD(object): 12 | def __init__(self, data, start, prefix='', full_dump=True): 13 | self.start = start 14 | self.prefix = prefix 15 | self.data = data 16 | self.size = len(data) 17 | self.full_dump = full_dump 18 | self.blocks = [] 19 | start_ncb = 0 20 | cur_pos = 0 21 | index = 0 22 | if prefix.startswith('bios_'): 23 | prefix = prefix[len('bios_'):] 24 | while cur_pos < len(data): 25 | cur_prefix = '%s%02d_' % (prefix, index) 26 | if FV.check_sig(data, cur_pos): 27 | if start_ncb < cur_pos: 28 | ncb = RAW(data[start_ncb:cur_pos], start + start_ncb, cur_prefix) 29 | self.blocks.append(ncb) 30 | index += 1 31 | start_ncb = cur_pos 32 | continue 33 | fv = FV(data[cur_pos:], start + cur_pos, cur_prefix) 34 | self.blocks.append(fv) 35 | index += 1 36 | cur_pos += fv.size 37 | start_ncb = cur_pos 38 | else: 39 | cur_pos += 8 40 | if start_ncb < len(data): 41 | cur_prefix = '%s%02d_' % (prefix, index) 42 | ncb = RAW(data[start_ncb:], start + start_ncb, cur_prefix) 43 | self.blocks.append(ncb) 44 | index += 1 45 | 46 | def __str__(self): 47 | return '0x%08x+0x%08x: FD' % (self.start, self.size) 48 | 49 | def showinfo(self, ts=' '): 50 | for block in self.blocks: 51 | print ts + str(block) 52 | block.showinfo(ts + ' ') 53 | 54 | def dump(self): 55 | if self.full_dump: 56 | fnprefix = '%s%08x' % (self.prefix, self.start) 57 | fn = '%s.fd' % fnprefix 58 | fn = os.path.normpath(fn) 59 | print 'Dumping FD to %s' % fn 60 | dn = os.path.dirname(fn) 61 | if dn and not os.path.isdir(dn): 62 | os.makedirs(dn) 63 | with open(fn, 'wb') as fout: 64 | fout.write(self.data) 65 | 66 | for block in self.blocks: 67 | block.dump() 68 | -------------------------------------------------------------------------------- /ffs.py: -------------------------------------------------------------------------------- 1 | """ 2 | FFS parser 3 | """ 4 | 5 | import struct 6 | from uuid import UUID 7 | 8 | from raw import RAW 9 | import guids as g 10 | 11 | 12 | _S_HEADER = struct.Struct('< 16s BB B B BBB B') 13 | _S_EXT_SIZE = struct.Struct('< L') 14 | 15 | 16 | class FFS(object): 17 | def __init__(self, revision, data, start, prefix=''): 18 | self.start = start 19 | self.prefix = prefix 20 | self.revision = revision 21 | 22 | offset = 0 23 | (guid, self.header_csum, self.file_csum, self.file_type, self.attributes, size_lo, size_mid, size_hi, 24 | self.state) = _S_HEADER.unpack_from(data, offset) 25 | offset += _S_HEADER.size 26 | self.guid = UUID(bytes_le=guid) 27 | self.int_check = self.header_csum | (self.file_csum << 8) 28 | self.size = size_lo | (size_mid << 8) | (size_hi << 16) 29 | self.has_tail = False 30 | if self.revision == 1 and (self.attributes & 1): 31 | self.has_tail = True 32 | self.large_file = False 33 | if self.revision == 3 and (self.attributes & 1): 34 | self.large_file = True 35 | extended_size, = _S_EXT_SIZE.unpack_from(data, offset) 36 | offset += _S_EXT_SIZE.size 37 | self.size = extended_size 38 | self.data = data[offset:self.size] 39 | self.contents = RAW(self.data, None, prefix + 'f') 40 | 41 | def __str__(self): 42 | return '0x%08x+0x%08x: FFS' % (self.start, self.size) 43 | 44 | def showinfo(self, ts=' '): 45 | print ts + 'Size: 0x%x (data 0x%x)' % (self.size, len(self.data)) 46 | print ts + 'Name: %s' % g.name(self.guid) 47 | print ts + 'Type: 0x%02x' % self.file_type 48 | print ts + 'Check: 0x%04x' % self.int_check 49 | print ts + 'Attributes: 0x%02x' % self.attributes 50 | print ts + 'State: 0x%02x' % self.state 51 | print ts + 'Has tail: %s' % self.has_tail 52 | print ts + 'Large file: %s' % self.large_file 53 | print ts + str(self.contents) 54 | self.contents.showinfo(ts + ' ') 55 | 56 | def dump(self): 57 | self.contents.dump() 58 | 59 | @staticmethod 60 | def new(ffs_guid, data, start, prefix=''): 61 | if ffs_guid == g.EFI_FIRMWARE_FILE_SYSTEM_GUID: 62 | return FFS(1, data, start, prefix) 63 | elif ffs_guid == g.EFI_FIRMWARE_FILE_SYSTEM2_GUID: 64 | return FFS(2, data, start, prefix) 65 | elif ffs_guid == g.EFI_FIRMWARE_FILE_SYSTEM3_GUID: 66 | return FFS(3, data, start, prefix) 67 | else: 68 | raise ValueError('Unknown FFS guid: %s' % g.name(ffs_guid)) 69 | -------------------------------------------------------------------------------- /fsdump.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Dump EFI FIRMWARE FILE SYSTEM 4 | """ 5 | 6 | import sys 7 | import struct 8 | from uuid import UUID 9 | 10 | import guids as g 11 | 12 | 13 | class FFSSection(object): 14 | def __init__(self, data): 15 | hdr = data[:0x4] 16 | (self.size, self.type) = struct.unpack('<3sB', hdr) 17 | self.size = struct.unpack(' len(data): 33 | raise ValueError('bad size: 0x%x > 0x%x' % (self.size, len(data))) 34 | if self.hdrlen > self.size: 35 | raise ValueError('bad hdrlen: 0x%x > 0x%x' % (self.hdrlen, self.size)) 36 | self.guid = UUID(bytes_le=guid_bytes) 37 | if guid_bytes not in g.FFS_GUIDS: 38 | raise ValueError('unknown guid: %s' % g.name(self.guid)) 39 | self.hdr = data[:self.hdrlen] 40 | self.checksum_valid = (csum16(self.hdr) == 0) 41 | self.data = data[self.hdrlen:self.size] 42 | self.blocks = [] 43 | block_size = 0 44 | while offset < self.hdrlen: 45 | (numb, blen) = _S_BLOCK.unpack_from(data, offset) 46 | offset += _S_BLOCK.size 47 | if (numb, blen) == (0, 0): 48 | break 49 | block_size += numb * blen 50 | self.blocks.append((numb, blen)) 51 | self.block_size = block_size 52 | offset = self.hdrlen 53 | self.files = [] 54 | index = 0 55 | while offset < self.size: 56 | padding = data[offset:offset + 8] 57 | if padding == chr(0xff) * 8: 58 | offset += 8 59 | continue 60 | cur_prefix = '%sffs/%03d_' % (prefix, index) 61 | cur_file = FFS.new(self.guid, data[offset:], start + offset, cur_prefix) 62 | offset += cur_file.size 63 | self.files.append(cur_file) 64 | index += 1 65 | padding_size = (8 - (offset & 7)) & 7 66 | offset += padding_size 67 | 68 | def __str__(self): 69 | return '0x%08x+0x%08x: FV' % (self.start, self.size) 70 | 71 | def showinfo(self, ts=' '): 72 | print ts + 'Reserved boot zone: %s' % (' '.join('%02x' % ord(c) for c in self.boot)) 73 | print ts + 'GUID: %s' % g.name(self.guid) 74 | print ts + 'Size: 0x%x (data 0x%x) (blocks 0x%x)' % (self.size, len(self.data), self.block_size) 75 | print ts + 'Attributes: 0x%08x' % self.attributes 76 | print ts + 'Checksum valid: %s' % self.checksum_valid 77 | print ts + 'Ext header: 0x%04x' % self.exthdr 78 | print ts + 'Revision: %d' % self.revision 79 | print ts + 'Blocks:' 80 | for numb, blen in self.blocks: 81 | print ts + ' ' + '%d: len 0x%x' % (numb, blen) 82 | for f in self.files: 83 | print ts + str(f) 84 | f.showinfo(ts + ' ') 85 | 86 | def dump(self): 87 | fnprefix = '%s%08x' % (self.prefix, self.start) 88 | fn = '%s.fv' % fnprefix 89 | fn = os.path.normpath(fn) 90 | print 'Dumping FV to %s' % fn 91 | dn = os.path.dirname(fn) 92 | if dn and not os.path.isdir(dn): 93 | os.makedirs(dn) 94 | with open(fn, 'wb') as fout: 95 | fout.write(self.hdr) 96 | fout.write(self.data) 97 | for f in self.files: 98 | f.dump() 99 | 100 | @staticmethod 101 | def check_sig(data, offset=0): 102 | offset += _SIG_OFFSET 103 | return data[offset:offset + _SIG_SIZE] == _SIG 104 | -------------------------------------------------------------------------------- /guids.py: -------------------------------------------------------------------------------- 1 | """ 2 | Common EFI GUIDs and utility functions 3 | """ 4 | 5 | from uuid import UUID 6 | 7 | 8 | ZERO_GUID = UUID('{00000000-0000-0000-0000-000000000000}') 9 | 10 | EFI_CAPSULE_GUID = UUID('{3B6686BD-0D76-4030-B70E-B5519E2FC5A0}') 11 | EFI_SIGNED_CAPSULE_GUID = UUID('{4A3CA68B-7723-48FB-803D-578CC1FEC44D}') 12 | 13 | EFI_FIRMWARE_FILE_SYSTEM_GUID = UUID('{7A9354D9-0468-444A-81CE-0BF617D890DF}') 14 | EFI_FIRMWARE_FILE_SYSTEM2_GUID = UUID('{8C8CE578-8A3D-4F1C-9935-896185C32DD3}') 15 | EFI_FIRMWARE_FILE_SYSTEM3_GUID = UUID('{5473C07A-3DCB-4DCA-BD6F-1E9689E7349A}') 16 | EFI_SYSTEM_NV_DATA_FV_GUID = UUID('{FFF12B8D-7696-4C8B-A985-2747075B4F50}') 17 | 18 | EFI_FFS_VOLUME_TOP_FILE_GUID = UUID('{1BA0062E-C779-4582-8566-336AE8F78F09}') 19 | 20 | EFI_CERT_TYPE_RSA2048_SHA256_GUID = UUID('{A7717414-C616-4977-9420-844712A735BF}') 21 | EFI_CERT_TYPE_PKCS7_GUID = UUID('{4AAFD29D-68DF-49EE-8AA9-347D375665A7}') 22 | EFI_CERT_SHA256_GUID = UUID('{C1C41626-504C-4092-ACA9-41F936934328}') 23 | EFI_CERT_RSA2048_GUID = UUID('{3C5766E8-269C-4E34-AA14-ED776E85B3B6}') 24 | EFI_CERT_RSA2048_SHA256_GUID = UUID('{E2B36190-879B-4A3D-AD8D-F2E7BBA32784}') 25 | EFI_CERT_SHA1_GUID = UUID('{826CA512-CF10-4AC9-B187-BE01496631BD}') 26 | EFI_CERT_RSA2048_SHA1_GUID = UUID('{67F8444F-8743-48F1-A328-1EAAB8736080}') 27 | EFI_CERT_X509_GUID = UUID('{A5C059A1-94E4-4AA7-87B5-AB155C2BF072}') 28 | EFI_CERT_SHA224_GUID = UUID('{0B6E5233-A65C-44C9-9407-D9AB83BFC8BD}') 29 | EFI_CERT_SHA384_GUID = UUID('{FF3E5307-9FD0-48C9-85F1-8AD56C701E01}') 30 | EFI_CERT_SHA512_GUID = UUID('{093E0FAE-A6C4-4F50-9F1B-D41E2B89C19A}') 31 | 32 | EFI_HASH_ALGORITHM_SHA1_GUID = UUID('{2AE9D80F-3FB2-4095-B7B1-E93157B946B6}') 33 | EFI_HASH_ALGORITHM_SHA224_GUID = UUID('{8DF01A06-9BD5-4BF7-B021-DB4FD9CCF45B}') 34 | EFI_HASH_ALGORITHM_SHA256_GUID = UUID('{51AA59DE-FDF2-4EA3-BC63-875FB7842EE9}') 35 | EFI_HASH_ALGORITHM_SHA384_GUID = UUID('{EFA96432-DE33-4DD2-AEE6-328C33DF777A}') 36 | EFI_HASH_ALGORITHM_SHA512_GUID = UUID('{CAA4381E-750C-4770-B870-7A23B4E42130}') 37 | EFI_HASH_ALGORTIHM_MD5_GUID = UUID('{0AF7C79C-65B5-4319-B0AE-44EC484E4AD7}') 38 | 39 | EFI_SECTION_CRC32_GUID = UUID('{FC1BCDB0-7D31-49AA-936A-A4600D9DD083}') 40 | EFI_SECTION_TIANO_COMPRESS_GUID = UUID('{A31280AD-481E-41B6-95E8-127F4C984779}') 41 | EFI_SECTION_LZMA_COMPRESS_GUID = UUID('{EE4E5898-3914-4259-9D6E-DC7BD79403CF}') 42 | EFI_SECTION_LZMAF86_COMPRESS_GUID = UUID('{D42AE6BD-1352-4BFB-909A-CA72A6EAE889}') 43 | EFI_SECTION_VPDTOOL_GUID = UUID('{8C3D856A-9BE6-468E-850A-24F7A8D38E08}') 44 | 45 | 46 | CAPSULE_GUIDS = ( 47 | EFI_CAPSULE_GUID.bytes_le, 48 | EFI_SIGNED_CAPSULE_GUID.bytes_le, 49 | ) 50 | 51 | FFS_GUIDS = ( 52 | EFI_FIRMWARE_FILE_SYSTEM_GUID.bytes_le, 53 | EFI_FIRMWARE_FILE_SYSTEM2_GUID.bytes_le, 54 | EFI_FIRMWARE_FILE_SYSTEM3_GUID.bytes_le, 55 | ) 56 | 57 | SECTION_GUIDS = ( 58 | EFI_SECTION_CRC32_GUID.bytes_le, 59 | EFI_SECTION_TIANO_COMPRESS_GUID.bytes_le, 60 | EFI_SECTION_LZMA_COMPRESS_GUID.bytes_le, 61 | EFI_SECTION_LZMAF86_COMPRESS_GUID.bytes_le, 62 | EFI_SECTION_VPDTOOL_GUID.bytes_le, 63 | ) 64 | 65 | 66 | GUID_NAME = { 67 | ZERO_GUID: 'ZERO_GUID', 68 | EFI_CAPSULE_GUID: 'EFI_CAPSULE_GUID', 69 | EFI_SIGNED_CAPSULE_GUID: 'EFI_SIGNED_CAPSULE_GUID', 70 | EFI_FIRMWARE_FILE_SYSTEM_GUID: 'EFI_FIRMWARE_FILE_SYSTEM_GUID', 71 | EFI_FIRMWARE_FILE_SYSTEM2_GUID: 'EFI_FIRMWARE_FILE_SYSTEM2_GUID', 72 | EFI_FIRMWARE_FILE_SYSTEM3_GUID: 'EFI_FIRMWARE_FILE_SYSTEM3_GUID', 73 | EFI_SYSTEM_NV_DATA_FV_GUID: 'EFI_SYSTEM_NV_DATA_FV_GUID', 74 | EFI_FFS_VOLUME_TOP_FILE_GUID: 'EFI_FFS_VOLUME_TOP_FILE_GUID', 75 | EFI_CERT_TYPE_RSA2048_SHA256_GUID: 'EFI_CERT_TYPE_RSA2048_SHA256_GUID', 76 | EFI_CERT_TYPE_PKCS7_GUID: 'EFI_CERT_TYPE_PKCS7_GUID', 77 | EFI_CERT_SHA256_GUID: 'EFI_CERT_SHA256_GUID', 78 | EFI_CERT_RSA2048_GUID: 'EFI_CERT_RSA2048_GUID', 79 | EFI_CERT_RSA2048_SHA256_GUID: 'EFI_CERT_RSA2048_SHA256_GUID', 80 | EFI_CERT_SHA1_GUID: 'EFI_CERT_SHA1_GUID', 81 | EFI_CERT_RSA2048_SHA1_GUID: 'EFI_CERT_RSA2048_SHA1_GUID', 82 | EFI_CERT_X509_GUID: 'EFI_CERT_X509_GUID', 83 | EFI_CERT_SHA224_GUID: 'EFI_CERT_SHA224_GUID', 84 | EFI_CERT_SHA384_GUID: 'EFI_CERT_SHA384_GUID', 85 | EFI_CERT_SHA512_GUID: 'EFI_CERT_SHA512_GUID', 86 | EFI_HASH_ALGORITHM_SHA1_GUID: 'EFI_HASH_ALGORITHM_SHA1_GUID', 87 | EFI_HASH_ALGORITHM_SHA224_GUID: 'EFI_HASH_ALGORITHM_SHA224_GUID', 88 | EFI_HASH_ALGORITHM_SHA256_GUID: 'EFI_HASH_ALGORITHM_SHA256_GUID', 89 | EFI_HASH_ALGORITHM_SHA384_GUID: 'EFI_HASH_ALGORITHM_SHA384_GUID', 90 | EFI_HASH_ALGORITHM_SHA512_GUID: 'EFI_HASH_ALGORITHM_SHA512_GUID', 91 | EFI_HASH_ALGORTIHM_MD5_GUID: 'EFI_HASH_ALGORTIHM_MD5_GUID', 92 | EFI_SECTION_CRC32_GUID: 'EFI_SECTION_CRC32_GUID', 93 | EFI_SECTION_TIANO_COMPRESS_GUID: 'EFI_SECTION_TIANO_COMPRESS_GUID', 94 | EFI_SECTION_LZMA_COMPRESS_GUID: 'EFI_SECTION_LZMA_COMPRESS_GUID', 95 | EFI_SECTION_LZMAF86_COMPRESS_GUID: 'EFI_SECTION_LZMAF86_COMPRESS_GUID', 96 | EFI_SECTION_VPDTOOL_GUID: 'EFI_SECTION_VPDTOOL_GUID', 97 | } 98 | 99 | 100 | def name(guid): 101 | if guid in GUID_NAME: 102 | return GUID_NAME[guid] 103 | else: 104 | return str(guid) 105 | -------------------------------------------------------------------------------- /ichdesc.py: -------------------------------------------------------------------------------- 1 | """ 2 | ICH flash descriptor 3 | """ 4 | 5 | import struct 6 | 7 | from raw import RAW 8 | from fd import FD 9 | 10 | 11 | _SIG = '5AA5F00F'.decode('hex') 12 | _SIG_OFFSET = 0x10 13 | _SIG_SIZE = 0x4 14 | 15 | _S_HEADER = struct.Struct('< 16s 4s BBBB BBBB BBBB') 16 | _S_REGION = struct.Struct('< H H') 17 | 18 | _REGIONS = [('ich', RAW), ('bios', FD), ('me', RAW), ('gbe', RAW), ('plat', RAW)] 19 | 20 | 21 | class ICHDesc(object): 22 | def __init__(self, data, start, prefix=''): 23 | self.start = start 24 | self.prefix = prefix 25 | offset = 0 26 | (self.rsvd, self.flvalsig, fcba, nc, frba, nr, fmba, nm, fpsba, isl, 27 | fmsba, psl, _, _) = _S_HEADER.unpack_from(data, offset) 28 | offset += _S_HEADER.size 29 | if self.flvalsig != _SIG: 30 | raise ValueError('bad magic %s' % repr(self.flvalsig)) 31 | 32 | self.fcba = fcba << 4 33 | self.nc = nc + 1 34 | self.frba = frba << 4 35 | self.nr = nr + 1 36 | self.fmba = fmba << 4 37 | self.nm = nm + 1 38 | self.fpsba = fpsba << 4 39 | self.isl = isl 40 | self.fmsba = fmsba << 4 41 | self.psl = psl 42 | 43 | self.blocks = [] 44 | self.regions = [] 45 | offset = self.frba 46 | region_size = 0 47 | for name, class_ in _REGIONS: 48 | (base, limit) = _S_REGION.unpack_from(data, offset) 49 | offset += _S_REGION.size 50 | if limit >= base: 51 | base = base << 12 52 | limit = (limit << 12) | 0xfff 53 | region_size += limit - base + 1 54 | cur_prefix = '%s%s_' % (prefix, name) 55 | self.blocks.append(class_(data[base:limit + 1], start + base, cur_prefix)) 56 | else: 57 | base = None 58 | limit = None 59 | self.blocks.append(None) 60 | self.regions.append((base, limit)) 61 | self.size = region_size 62 | 63 | def __str__(self): 64 | return '0x%08x+0x%08x: ICH' % (self.start, self.size) 65 | 66 | def showinfo(self, ts=' '): 67 | print ts + 'Size: 0x%x' % self.size 68 | print ts + 'Reserved: %s' % (' '.join('%02x' % ord(c) for c in self.rsvd)) 69 | print ts + 'FR: 0x%03x %2d' % (self.frba, self.nr) 70 | print ts + 'FC: 0x%03x %2d' % (self.fcba, self.nc) 71 | print ts + 'FPS: 0x%03x %2d' % (self.fpsba, self.isl) 72 | print ts + 'FM: 0x%03x %2d' % (self.fmba, self.nm) 73 | print ts + 'FMS: 0x%03x %2d' % (self.fmsba, self.psl) 74 | print ts + 'Regions:' 75 | for index, (name, _) in enumerate(_REGIONS): 76 | (base, limit) = self.regions[index] 77 | if base is None: 78 | print ts + ' ' + '%4s:-' % name 79 | else: 80 | print ts + ' ' + '%4s:0x%06x:0x%06x' % (name, base, limit) 81 | 82 | for block in self.blocks: 83 | if block: 84 | print ts + str(block) 85 | block.showinfo(ts + ' ') 86 | 87 | def dump(self): 88 | for block in self.blocks: 89 | if block: 90 | block.dump() 91 | 92 | @staticmethod 93 | def check_sig(data, offset=0): 94 | offset += _SIG_OFFSET 95 | return data[offset:offset + _SIG_SIZE] == _SIG 96 | -------------------------------------------------------------------------------- /raw.py: -------------------------------------------------------------------------------- 1 | """ 2 | NCB 3 | """ 4 | 5 | import os 6 | 7 | from util import is_blank 8 | 9 | 10 | class RAW(object): 11 | def __init__(self, data, start, prefix=''): 12 | self.start = start 13 | self.prefix = prefix 14 | self.data = data 15 | self.size = len(data) 16 | self.is_blank = is_blank(data) 17 | 18 | def __str__(self): 19 | if self.start is None: 20 | return '??????????+0x%08x: RAW' % self.size 21 | else: 22 | return '0x%08x+0x%08x: RAW' % (self.start, self.size) 23 | 24 | def showinfo(self, ts=' '): 25 | print ts + 'Size: 0x%x' % self.size 26 | print ts + 'Blank: %s' % self.is_blank 27 | 28 | def dump(self): 29 | if self.start is None: 30 | fnprefix = self.prefix 31 | else: 32 | fnprefix = '%s%08x' % (self.prefix, self.start) 33 | fn = '%s.bin' % fnprefix 34 | fn = os.path.normpath(fn) 35 | print 'Dumping RAW to %s' % fn 36 | dn = os.path.dirname(fn) 37 | if dn and not os.path.isdir(dn): 38 | os.makedirs(dn) 39 | with open(fn, 'wb') as fout: 40 | fout.write(self.data) 41 | -------------------------------------------------------------------------------- /rom.py: -------------------------------------------------------------------------------- 1 | """ 2 | ROM parser 3 | """ 4 | 5 | from cap import CAP 6 | from ichdesc import ICHDesc 7 | from fd import FD 8 | from raw import RAW 9 | 10 | 11 | class ROM(object): 12 | def __init__(self, data, start=0, prefix=''): 13 | self.start = start 14 | self.prefix = prefix 15 | self.data = data 16 | self.size = len(data) 17 | if CAP.check_sig(data): 18 | self.contents = CAP(data, start) 19 | elif ICHDesc.check_sig(data): 20 | self.contents = ICHDesc(data, start) 21 | else: 22 | self.contents = FD(data, start, 'bios_', full_dump=False) 23 | self.trailing = None 24 | if self.size > self.contents.size: 25 | cur_prefix = '%strl_' % prefix 26 | self.trailing = RAW(data[self.contents.size:], start + self.contents.size, cur_prefix) 27 | 28 | def __str__(self): 29 | return '0x%08x+0x%08x: ROM' % (self.start, self.size) 30 | 31 | def showinfo(self, ts=' '): 32 | print ts + 'Size: 0x%x' % self.size 33 | if self.trailing: 34 | print ts + 'Trailing: 0x%x' % self.trailing.size 35 | print ts + str(self.contents) 36 | self.contents.showinfo(ts + ' ') 37 | if self.trailing: 38 | print ts + str(self.trailing) 39 | self.trailing.showinfo(ts + ' ') 40 | 41 | def dump(self): 42 | self.contents.dump() 43 | if self.trailing: 44 | self.trailing.dump() 45 | -------------------------------------------------------------------------------- /romdump.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Dump EFI cap/fd/rom file 4 | """ 5 | 6 | import sys 7 | 8 | from rom import ROM 9 | 10 | 11 | def parse_diskfile(filename): 12 | print 'Reading %s' % filename 13 | with open(filename, 'rb') as f: 14 | d = f.read() 15 | 16 | print 'Parsing %s' % filename 17 | r = ROM(d) 18 | print r 19 | r.showinfo() 20 | 21 | print 'Dumping %s' % filename 22 | r.dump() 23 | 24 | 25 | def main(): 26 | if len(sys.argv) > 1: 27 | for filename in sys.argv[1:]: 28 | parse_diskfile(filename) 29 | else: 30 | print 'No file specified, giving up' 31 | 32 | 33 | if __name__ == '__main__': 34 | main() 35 | -------------------------------------------------------------------------------- /util.py: -------------------------------------------------------------------------------- 1 | """ 2 | Misc utility functions 3 | """ 4 | 5 | 6 | def csum16(data): 7 | csum = 0 8 | for i in range(0, len(data), 2): 9 | val = ord(data[i]) + (ord(data[i + 1]) << 8) 10 | csum += val 11 | return csum & 0xFFFF 12 | 13 | 14 | def is_blank(data, fill=0xFF): 15 | blank = True 16 | for i in data: 17 | if ord(i) != fill: 18 | blank = False 19 | break 20 | return blank 21 | -------------------------------------------------------------------------------- /xfv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import os 5 | from struct import unpack 6 | 7 | import guids as g 8 | 9 | #fvh_count = 0 10 | 11 | 12 | ### Formatting: GUIDs 13 | def format_guid(guid_s): 14 | parts = unpack('= len(fvdata): 57 | break 58 | else: 59 | fvdata = fvdata[usedsize:] 60 | 61 | 62 | ### Handle a firmware volume 63 | def handle_fv(fvdata, name='default'): 64 | 65 | ### Check header 66 | (fvzero, fvfstype, fvlen, fvsig, fvattr, fvhdrlen, fvchecksum, fvrev) = unpack('<16s16sQ4sLHH3xB', fvdata[0:0x38]) 67 | if fvsig != '_FVH': 68 | print 'Not a EFI firmware volume (sig missing)' 69 | return 0 70 | if fvlen > len(fvdata): 71 | print 'WARNING: File too short, header gives length as 0x%X bytes' % fvlen 72 | else: 73 | print 'Size per header: 0x%X bytes' % fvlen 74 | offset = fvhdrlen 75 | 76 | # global fvh_count 77 | # fvhdir = 'fvh-%d' % fvh_count 78 | # fvh_count += 1 79 | fvhdir = 'fvh-' + name 80 | os.mkdir(fvhdir) 81 | os.chdir(fvhdir) 82 | 83 | ### Decode files 84 | 85 | print 'Listing files' 86 | print '-----' 87 | 88 | while True: 89 | if offset == fvlen: 90 | print '-----' 91 | print 'End of volume (size reached cleanly)' 92 | break 93 | if offset + 24 > fvlen: 94 | print '-----' 95 | print 'End of volume (size reached uncleanly)' 96 | break 97 | 98 | (fileguid, fileintcheck, filetype, fileattr, filelenandstate) = unpack('<16sHBBL', fvdata[offset:offset + 24]) 99 | if filetype == 0xff: 100 | print '-----' 101 | print 'End of volume (filler data found)' 102 | break 103 | fileentrylen = filelenandstate & 0xffffff 104 | filestate = filelenandstate >> 24 105 | filelen = fileentrylen - 24 106 | if fileattr & 1: 107 | print ' has tail!' 108 | filelen -= 2 109 | 110 | fileoffset = offset + 24 111 | nextoffset = (offset + fileentrylen + 7) & ~7 112 | 113 | filedata = fvdata[fileoffset:fileoffset + filelen] 114 | compressed = False 115 | if filetype != 1 and filedata[3] == "\x01": 116 | compressed = True 117 | filedata = decompress(filedata) 118 | 119 | if compressed: 120 | print '%s %s C %d (%d)' % (format_guid(fileguid), format_filetype(filetype), len(filedata), filelen) 121 | else: 122 | print '%s %s U %d' % (format_guid(fileguid), format_filetype(filetype), filelen) 123 | 124 | handle_file('file-%s.%s' % (format_guid(fileguid), extention_filetype(filetype)), filetype, filedata) 125 | 126 | offset = nextoffset 127 | 128 | os.chdir('..') 129 | return fvlen 130 | 131 | 132 | ### Handle decompression of a compressed section 133 | def decompress(compdata): 134 | (sectlenandtype, uncomplen, comptype) = unpack("_tmp_result') 147 | 148 | f = file('_tmp_result', 'rb') 149 | decompdata = f.read() 150 | f.close() 151 | 152 | if len(decompdata) < uncomplen: 153 | print 'WARNING: Decompressed data too short!' 154 | return decompdata 155 | 156 | elif comptype == 2: 157 | f = file('_tmp_decompress', 'wb') 158 | f.write(compdata[13:sectlen + 4]) # for some reason there is junk in 9:13 that I don't see in the raw files?! 159 | f.close() 160 | 161 | os.system('lzmadec <_tmp_decompress >_tmp_result') 162 | 163 | f = file('_tmp_result', 'rb') 164 | decompdata = f.read() 165 | f.close() 166 | 167 | if len(decompdata) < uncomplen: 168 | print 'WARNING: Decompressed data too short!' 169 | return decompdata 170 | else: 171 | print 'ERROR: Unknown compression type %d' % comptype 172 | return compdata 173 | 174 | 175 | ### Handle the contents of one firmware file 176 | def handle_file(filename, filetype, filedata): 177 | if filetype == 1: 178 | f = file('%s.raw' % filename, 'wb') 179 | f.write(filedata) 180 | f.close() 181 | if filetype != 1: 182 | handle_sections(filename, 0, filedata) 183 | 184 | 185 | ### Handle section data (i.e. multiple sections), recurse if necessary 186 | def handle_sections(filename, sectindex, imagedata): 187 | imagelen = len(imagedata) 188 | filename_override = None 189 | 190 | # first try to find a filename 191 | offset = 0 192 | while offset + 4 <= imagelen: 193 | (sectlenandtype,) = unpack('> 24 196 | nextoffset = (offset + sectlen + 3) & ~3 197 | dataoffset = offset + 4 198 | datalen = sectlen - 4 199 | sectdata = imagedata[dataoffset:dataoffset + datalen] 200 | 201 | if secttype == 0x15: 202 | filename_override = sectdata[:-2].decode('utf-16le').encode('utf-8') 203 | print " Filename '%s'" % filename_override 204 | 205 | offset = nextoffset 206 | 207 | # then analyze the sections for good 208 | offset = 0 209 | while offset + 4 <= imagelen: 210 | (sectlenandtype,) = unpack('> 24 213 | nextoffset = (offset + sectlen + 3) & ~3 214 | dataoffset = offset + 4 215 | datalen = sectlen - 4 216 | 217 | if secttype == 2: 218 | (sectguid, sectdataoffset, sectattr) = unpack('<16sHH', imagedata[offset + 4:offset + 24]) 219 | dataoffset = offset + sectdataoffset 220 | datalen = sectlen - sectdataoffset 221 | if sectguid == g.EFI_SECTION_CRC32_GUID.bytes_le: 222 | # CRC32 section 223 | sectindex = handle_sections(filename, sectindex, imagedata[dataoffset:dataoffset + datalen]) 224 | else: 225 | print ' %02d GUID %s' % (sectindex, format_guid(sectguid)) 226 | sectindex += 1 227 | sectindex = handle_sections(filename, sectindex, imagedata[dataoffset:dataoffset + datalen]) 228 | else: 229 | secttype_name = 'UNKNOWN(%02X)' % secttype 230 | ext = 'data' 231 | sectdata = imagedata[dataoffset:dataoffset + datalen] 232 | extraprint = '' 233 | 234 | if secttype == 0x10: 235 | secttype_name = 'PE32' 236 | ext = 'efi' 237 | elif secttype == 0x11: 238 | secttype_name = 'PIC' 239 | ext = 'pic.efi' 240 | elif secttype == 0x12: 241 | secttype_name = 'TE' 242 | ext = 'te' 243 | elif secttype == 0x13: 244 | secttype_name = 'DXE_DEPEX' 245 | ext = None 246 | elif secttype == 0x14: 247 | secttype_name = 'VERSION' 248 | ext = None 249 | elif secttype == 0x15: 250 | secttype_name = 'USER_INTERFACE' 251 | ext = None 252 | elif secttype == 0x16: 253 | secttype_name = 'COMPATIBILITY16' 254 | ext = 'bios' 255 | elif secttype == 0x17: 256 | secttype_name = 'FIRMWARE_VOLUME_IMAGE' 257 | ext = 'fd' 258 | elif secttype == 0x18: 259 | secttype_name = 'FREEFORM_SUBTYPE_GUID' 260 | ext = None 261 | elif secttype == 0x19: 262 | secttype_name = 'RAW' 263 | ext = 'raw' 264 | if sectdata[0:8] == "\x89PNG\x0D\x0A\x1A\x0A": 265 | ext = 'png' 266 | elif sectdata[0:4] == 'icns': 267 | ext = 'icns' 268 | elif secttype == 0x1B: 269 | secttype_name = 'PEI_DEPEX' 270 | ext = None 271 | 272 | print ' %02d %s %d%s' % (sectindex, secttype_name, datalen, extraprint) 273 | 274 | if ext is not None: 275 | use_filename = '%s-%02d' % (filename, sectindex) 276 | if filename_override is not None: 277 | use_filename = filename_override 278 | f = file('%s.%s' % (use_filename, ext), 'wb') 279 | f.write(sectdata) 280 | f.close() 281 | 282 | if secttype == 0x17: 283 | print '*** Recursively analyzing the contained firmware volume...' 284 | handle_fv(sectdata, filename) 285 | 286 | sectindex += 1 287 | 288 | offset = nextoffset 289 | 290 | return sectindex 291 | 292 | 293 | ### main code 294 | def main(): 295 | if len(sys.argv) > 1: 296 | for filename in sys.argv[1:]: 297 | analyze_diskfile(filename) 298 | else: 299 | print 'No file specified, giving up' 300 | 301 | 302 | if __name__ == '__main__': 303 | main() 304 | --------------------------------------------------------------------------------