Ejemplo n.º 1
0
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()
Ejemplo n.º 2
0
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'
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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))
Ejemplo n.º 5
0
    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)