def sha256_hash(filename): """Produces an SHA-256 hash of a file.""" if not os.path.exists(filename): raise MissingFileError('The "%s" file was not found.' % filename) with open(filename, 'rb') as f: return hashlib.sha256(f.read()).hexdigest()
def detect_elf_binary(filename): """Returns `True` if a file has an ELF header.""" if not os.path.exists(filename): raise MissingFileError('The "%s" file was not found.' % filename) with open(filename, 'rb') as f: first_four_bytes = f.read(4) return first_four_bytes == b'\x7fELF'
def resolve_binary(binary): """Attempts to find the absolute path to the binary.""" absolute_binary_path = os.path.normpath(os.path.abspath(binary)) if not os.path.exists(absolute_binary_path): for path in os.getenv('PATH', '').split(os.pathsep): absolute_binary_path = os.path.normpath( os.path.abspath(os.path.join(path, binary))) if os.path.exists(absolute_binary_path): break else: raise MissingFileError( 'The "%s" binary could not be found in $PATH.' % binary) return absolute_binary_path
def resolve_file_path(path, search_environment_path=False): """Attempts to find a normalized path to a file. If the file is not found, or if it is a directory, appropriate exceptions will be thrown. Args: path (str): Either a relative or absolute path to a file, or the name of an executable if `search_environment_path` is `True`. search_environment_path (bool): Whether PATH should be used to resolve the file. """ if search_environment_path: path = resolve_binary(path) if not os.path.exists(path): raise MissingFileError('The "%s" file was not found.' % path) if os.path.isdir(path): raise UnexpectedDirectoryError('"%s" is a directory, not a file.' % path) return os.path.normpath(os.path.abspath(path))
def __init__(self, path, chroot=None, file_factory=None): """Constructs the `Elf` instance. Args: path (str): The full path to the ELF binary. chroot (str, optional): If specified, all dependency and linker paths will be considered relative to this directory (mainly useful for testing). file_factory (function, optional): A function to use when creating new `File` instances. """ if not os.path.exists(path): raise MissingFileError('The "%s" file was not found.' % path) self.path = path self.chroot = chroot self.file_factory = file_factory or File with open(path, 'rb') as f: # Make sure that this is actually an ELF binary. first_four_bytes = f.read(4) if first_four_bytes != b'\x7fELF': raise InvalidElfBinaryError( 'The "%s" file is not a binary ELF file.' % path) # Determine whether this is a 32-bit or 64-bit file. format_byte = f.read(1) self.bits = {b'\x01': 32, b'\x02': 64}.get(format_byte) if not self.bits: raise UnsupportedArchitectureError( ('The "%s" file does not appear to be either 32 or 64 bits. ' % path) + 'Other architectures are not currently supported, but you can open an ' 'issue at https://github.com/intoli/exodus stating your use-case and ' 'support might get extended in the future.', ) # Determine whether it's big or little endian and construct an integer parsing function. endian_byte = f.read(1) byteorder = {b'\x01': 'little', b'\x02': 'big'}[endian_byte] assert byteorder == 'little', 'Big endian is not supported right now.' if not byteorder: raise UnsupportedArchitectureError( ('The "%s" file does not appear to be little endian, ' % path) + 'and big endian binaries are not currently supported. You can open an ' 'issue at https://github.com/intoli/exodus stating your use-case and ' 'support might get extended in the future.', ) def hex(bytes): return bytes_to_int(bytes, byteorder=byteorder) # Determine the type of the binary. f.seek(hex(b'\x10')) e_type = hex(f.read(2)) self.type = { 1: 'relocatable', 2: 'executable', 3: 'shared', 4: 'core' }[e_type] # Find the program header offset. e_phoff_start = {32: hex(b'\x1c'), 64: hex(b'\x20')}[self.bits] e_phoff_length = {32: 4, 64: 8}[self.bits] f.seek(e_phoff_start) e_phoff = hex(f.read(e_phoff_length)) # Determine the size of a program header entry. e_phentsize_start = {32: hex(b'\x2a'), 64: hex(b'\x36')}[self.bits] f.seek(e_phentsize_start) e_phentsize = hex(f.read(2)) # Determine the number of program header entries. e_phnum_start = {32: hex(b'\x2c'), 64: hex(b'\x38')}[self.bits] f.seek(e_phnum_start) e_phnum = hex(f.read(2)) # Loop through each program header. self.linker_file = None for header_index in range(e_phnum): header_start = e_phoff + header_index * e_phentsize f.seek(header_start) p_type = f.read(4) # A p_type of \x03 corresponds to a PT_INTERP header (e.g. the linker). if len(p_type) == 0: break if not p_type == b'\x03\x00\x00\x00': continue # Determine the offset for the segment. p_offset_start = header_start + { 32: hex(b'\04'), 64: hex(b'\x08') }[self.bits] p_offset_length = {32: 4, 64: 8}[self.bits] f.seek(p_offset_start) p_offset = hex(f.read(p_offset_length)) # Determine the size of the segment. p_filesz_start = header_start + { 32: hex(b'\x10'), 64: hex(b'\x20') }[self.bits] p_filesz_length = {32: 4, 64: 8}[self.bits] f.seek(p_filesz_start) p_filesz = hex(f.read(p_filesz_length)) # Read in the segment. f.seek(p_offset) segment = f.read(p_filesz) # It should be null-terminated (b'\x00' in Python 2, 0 in Python 3). assert segment[-1] in [ b'\x00', 0 ], 'The string should be null terminated.' assert self.linker_file is None, 'More than one linker found.' linker_path = segment[:-1].decode('ascii') if chroot: linker_path = os.path.join( chroot, os.path.relpath(linker_path, '/')) self.linker_file = self.file_factory(linker_path, chroot=self.chroot)