#!/usr/bin/env python # # Copyright (C) 2017 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # import os import struct class ElfError(Exception): """The exception raised by ElfParser.""" pass class ElfParser(object): """The class reads information from an ELF file. Attributes: _file: The ELF file object. _file_size: Size of the ELF. bitness: Bitness of the ELF. _address_size: Size of address or offset in the ELF. _offsets: Offset of each entry in the ELF. _seek_read_address: The function to read an address or offset entry from the ELF. _sh_offset: Offset of section header table in the file. _sh_size: Size of section header table entry. _sh_count: Number of section header table entries. _section_headers: List of SectionHeader objects read from the ELF. """ _MAGIC_OFFSET = 0 _MAGIC_BYTES = b"\x7fELF" _BITNESS_OFFSET = 4 _BITNESS_32 = 1 _BITNESS_64 = 2 # Section type _SHT_DYNAMIC = 6 # Tag in dynamic section _DT_NULL = 0 _DT_NEEDED = 1 _DT_STRTAB = 5 class ElfOffsets32(object): """Offset of each entry in 32-bit ELF""" # offset from ELF header SECTION_HEADER_OFFSET = 0x20 SECTION_HEADER_SIZE = 0x2e SECTION_HEADER_COUNT = 0x30 # offset from section header SECTION_TYPE = 0x04 SECTION_ADDRESS = 0x0c SECTION_OFFSET = 0x10 class ElfOffsets64(object): """Offset of each entry in 64-bit ELF""" # offset from ELF header SECTION_HEADER_OFFSET = 0x28 SECTION_HEADER_SIZE = 0x3a SECTION_HEADER_COUNT = 0x3c # offset from section header SECTION_TYPE = 0x04 SECTION_ADDRESS = 0x10 SECTION_OFFSET = 0x18 class SectionHeader(object): """Contains section header entries as attributes. Attributes: type: Type of the section. address: The virtual memory address where the section is loaded. offset: The offset of the section in the ELF file. """ def __init__(self, type, address, offset): self.type = type self.address = address self.offset = offset def __init__(self, file_path): """Creates a parser to open and read an ELF file. Args: file_path: The path to the ELF. Raises: ElfError if the file is not a valid ELF. """ try: self._file = open(file_path, "rb") except IOError as e: raise ElfError(e) try: self._loadElfHeader() self._section_headers = [ self._loadSectionHeader(self._sh_offset + i * self._sh_size) for i in range(self._sh_count)] except: self._file.close() raise def __del__(self): """Closes the ELF file.""" self.close() def close(self): """Closes the ELF file.""" self._file.close() def _seekRead(self, offset, read_size): """Reads a byte string at specific offset in the file. Args: offset: An integer, the offset from the beginning of the file. read_size: An integer, number of bytes to read. Returns: A byte string which is the file content. Raises: ElfError if fails to seek and read. """ if offset + read_size > self._file_size: raise ElfError("Read beyond end of file.") try: self._file.seek(offset) return self._file.read(read_size) except IOError as e: raise ElfError(e) def _seekRead8(self, offset): """Reads an 1-byte integer from file.""" return struct.unpack("B", self._seekRead(offset, 1))[0] def _seekRead16(self, offset): """Reads a 2-byte integer from file.""" return struct.unpack("H", self._seekRead(offset, 2))[0] def _seekRead32(self, offset): """Reads a 4-byte integer from file.""" return struct.unpack("I", self._seekRead(offset, 4))[0] def _seekRead64(self, offset): """Reads an 8-byte integer from file.""" return struct.unpack("Q", self._seekRead(offset, 8))[0] def _seekReadString(self, offset): """Reads a null-terminated string starting from specific offset. Args: offset: The offset from the beginning of the file. Returns: A byte string, excluding the null character. """ ret = "" buf_size = 16 self._file.seek(offset) while True: try: buf = self._file.read(buf_size) except IOError as e: raise ElfError(e) end_index = buf.find('\0') if end_index < 0: ret += buf else: ret += buf[:end_index] return ret if len(buf) != buf_size: raise ElfError("Null-terminated string reaches end of file.") def _loadElfHeader(self): """Loads ElfHeader and initializes attributes""" try: self._file_size = os.fstat(self._file.fileno()).st_size except OSError as e: raise ElfError(e) magic = self._seekRead(self._MAGIC_OFFSET, 4) if magic != self._MAGIC_BYTES: raise ElfError("Wrong magic bytes.") bitness = self._seekRead8(self._BITNESS_OFFSET) if bitness == self._BITNESS_32: self.bitness = 32 self._address_size = 4 self._offsets = self.ElfOffsets32 self._seek_read_address = self._seekRead32 elif bitness == self._BITNESS_64: self.bitness = 64 self._address_size = 8 self._offsets = self.ElfOffsets64 self._seek_read_address = self._seekRead64 else: raise ElfError("Wrong bitness value.") self._sh_offset = self._seek_read_address( self._offsets.SECTION_HEADER_OFFSET) self._sh_size = self._seekRead16(self._offsets.SECTION_HEADER_SIZE) self._sh_count = self._seekRead16(self._offsets.SECTION_HEADER_COUNT) return True def _loadSectionHeader(self, offset): """Loads a section header from ELF file. Args: offset: The starting offset of the section header. Returns: An instance of SectionHeader. """ return self.SectionHeader( self._seekRead32(offset + self._offsets.SECTION_TYPE), self._seek_read_address(offset + self._offsets.SECTION_ADDRESS), self._seek_read_address(offset + self._offsets.SECTION_OFFSET)) def _loadDtNeeded(self, offset): """Reads DT_NEEDED entries from dynamic section. Args: offset: The offset of the dynamic section from the beginning of the file Returns: A list of strings, the names of libraries. """ strtab_address = None name_offsets = [] while True: tag = self._seek_read_address(offset) offset += self._address_size value = self._seek_read_address(offset) offset += self._address_size if tag == self._DT_NULL: break if tag == self._DT_NEEDED: name_offsets.append(value) if tag == self._DT_STRTAB: strtab_address = value if strtab_address is None: raise ElfError("Cannot find string table offset in dynamic section") try: strtab_offset = next(x.offset for x in self._section_headers if x.address == strtab_address) except StopIteration: raise ElfError("Cannot find dynamic string table.") names = [self._seekReadString(strtab_offset + x) for x in name_offsets] return names def listDependencies(self): """Lists the shared libraries that the ELF depends on. Returns: A list of strings, the names of the depended libraries. """ deps = [] for sh in self._section_headers: if sh.type == self._SHT_DYNAMIC: deps.extend(self._loadDtNeeded(sh.offset)) return deps