| # Copyright 2019 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """This library contains 64 bit ELF headers access and modification methods. |
| |
| This library implements classes representing various ELF structures. |
| For more detailed description of ELF headers and fields refer to the ELF |
| standard: http://www.skyfree.org/linux/references/ELF_Format.pdf and to the |
| 64 bit update: https://uclibc.org/docs/elf-64-gen.pdf. |
| |
| This library was created because the script required precise manipulations of |
| file offsets. I.e: move the headers segment to the end of the file. |
| No package, capable of doing this kind of manipulations was found, so the |
| creation of this library was deemed necessary. |
| |
| The point of entry is ElfHeader class that provides methods for accessing |
| additional parts of the ELF file. |
| """ |
| |
| import enum |
| import logging |
| |
| |
| class ElfEntry(object): |
| """Base class for ELF headers. |
| |
| Provides methods for populating fields. |
| """ |
| |
| def __init__(self, byte_order, fields=None): |
| """ElfEntry constructor. |
| |
| Args: |
| byte_order: str. Either 'little' for little endian or 'big' for big |
| endian. |
| fields: List[Tuple[str, int]]. An ordered list of pairs of |
| (attribute name, size in bytes). This list will be used for parsing |
| data and automatically setting up those fields. |
| """ |
| if fields is None: |
| self._fields = [] |
| else: |
| self._fields = fields |
| self.byte_order = byte_order |
| |
| def ParseBytes(self, data, offset): |
| """Parses Entry fields from data starting at offset using _fields. |
| |
| Args: |
| data: bytes. |
| offset: int. The start point of parsing. |
| """ |
| current_offset = offset |
| for field_name, field_size in self._fields: |
| value = int.from_bytes( |
| data[current_offset:current_offset + field_size], |
| byteorder=self.byte_order) |
| setattr(self, field_name, value) |
| current_offset += field_size |
| |
| def ApplyKwargs(self, **kwargs): |
| """Set the fields from kwargs matching the _fields array entries.""" |
| for field_name, _ in self._fields: |
| if field_name not in kwargs: |
| logging.error('field_name %s not found in kwargs', field_name) |
| continue |
| setattr(self, field_name, kwargs[field_name]) |
| |
| def ToBytes(self): |
| """Returns byte representation of ELF entry.""" |
| bytearr = bytearray() |
| for field_name, field_size in self._fields: |
| field_bytes = getattr(self, field_name).to_bytes( |
| field_size, byteorder=self.byte_order) |
| bytearr.extend(field_bytes) |
| return bytearr |
| |
| @classmethod |
| def Create(cls, byte_order, **kwargs): |
| """Static wrapper around ApplyKwargs method. |
| |
| Args: |
| byte_order: str. Either 'little' for little endian or 'big' for big |
| endian. |
| **kwargs: will be passed directly to the ApplyKwargs method. |
| """ |
| obj = cls(byte_order) |
| obj.ApplyKwargs(**kwargs) |
| return obj |
| |
| @classmethod |
| def FromBytes(cls, byte_order, data, offset): |
| """Static wrapper around ParseBytes method. |
| |
| Args: |
| byte_order: str. Either 'little' for little endian or 'big' for big |
| endian. |
| data: bytes. |
| offset: int. The start point of parsing. |
| """ |
| obj = cls(byte_order) |
| obj.ParseBytes(data, offset) |
| return obj |
| |
| |
| class ProgramHeader(ElfEntry): |
| """This class represent PhdrEntry from ELF standard.""" |
| |
| class Type(enum.IntEnum): |
| PT_NULL = 0 |
| PT_LOAD = 1 |
| PT_DYNAMIC = 2 |
| PT_INTERP = 3 |
| PT_NOTE = 4 |
| PT_SHLIB = 5 |
| PT_PHDR = 6 |
| |
| class Flags(enum.IntFlag): |
| PF_X = 1 |
| PF_W = 2 |
| PF_R = 4 |
| |
| def __init__(self, byte_order): |
| """ProgramHeader constructor. |
| |
| Args: |
| byte_order: str. |
| """ |
| # We have to set them here to avoid attribute-error from pylint. |
| self.p_type = None |
| self.p_flags = None |
| self.p_offset = None |
| self.p_vaddr = None |
| self.p_paddr = None |
| self.p_filesz = None |
| self.p_memsz = None |
| self.p_align = None |
| fields = [ |
| ('p_type', 4), |
| ('p_flags', 4), |
| ('p_offset', 8), |
| ('p_vaddr', 8), |
| ('p_paddr', 8), |
| ('p_filesz', 8), |
| ('p_memsz', 8), |
| ('p_align', 8), |
| ] |
| super(ProgramHeader, self).__init__(byte_order, fields) |
| |
| |
| class ElfHeader(ElfEntry): |
| """This class represents ELFHdr from the ELF standard. |
| |
| On its initialization it determines the bitness and endianness of the binary. |
| """ |
| |
| class EiClass(enum.IntEnum): |
| ELFCLASS32 = 1 |
| ELFCLASS64 = 2 |
| |
| class EiData(enum.IntEnum): |
| ELFDATALSB = 1 |
| ELFDATAMSB = 2 |
| |
| class EType(enum.IntEnum): |
| ET_NONE = 0 |
| ET_REL = 1 |
| ET_EXEC = 2 |
| ET_DYN = 3 |
| ET_CORE = 4 |
| |
| _EI_CLASS_OFFSET = 4 |
| _EI_DATA_OFFSET = 5 |
| |
| def _GetEiClass(self, data): |
| """Returns the value of ei_class.""" |
| return data[self._EI_CLASS_OFFSET] |
| |
| def _GetEiData(self, data): |
| """Returns the value of ei_data.""" |
| return data[self._EI_DATA_OFFSET] |
| |
| def _ValidateBitness(self, data): |
| """Verifies that library supports file's bitness.""" |
| if self._GetEiClass(data) != ElfHeader.EiClass.ELFCLASS64: |
| raise RuntimeError('only 64 bit objects are supported') |
| |
| def _ReadByteOrder(self, data): |
| """Reads and returns the file's byte order.""" |
| ei_data = data[self._EI_DATA_OFFSET] |
| if ei_data == ElfHeader.EiData.ELFDATALSB: |
| return 'little' |
| elif ei_data == ElfHeader.EiData.ELFDATAMSB: |
| return 'big' |
| raise RuntimeError('Failed to parse ei_data') |
| |
| def _ParsePhdrs(self, data): |
| current_offset = self.e_phoff |
| for _ in range(0, self.e_phnum): |
| self.phdrs.append( |
| ProgramHeader.FromBytes(self.byte_order, data, current_offset)) |
| current_offset += self.e_phentsize |
| |
| def __init__(self, data): |
| """ElfHeader constructor. |
| |
| Args: |
| data: bytearray. |
| """ |
| # We have to set them here to avoid attribute-error from pylint. |
| self.ei_magic = None |
| self.ei_class = None |
| self.ei_data = None |
| self.ei_version = None |
| self.ei_osabi = None |
| self.ei_abiversion = None |
| self.ei_pad = None |
| self.e_type = None |
| self.e_machine = None |
| self.e_version = None |
| self.e_entry = None |
| self.e_phoff = None |
| self.e_shoff = None |
| self.e_flags = None |
| self.e_ehsize = None |
| self.e_phentsize = None |
| self.e_phnum = None |
| self.e_shentsize = None |
| self.e_shnum = None |
| self.e_shstrndx = None |
| fields = [ |
| ('ei_magic', 4), |
| ('ei_class', 1), |
| ('ei_data', 1), |
| ('ei_version', 1), |
| ('ei_osabi', 1), |
| ('ei_abiversion', 1), |
| ('ei_pad', 7), |
| ('e_type', 2), |
| ('e_machine', 2), |
| ('e_version', 4), |
| ('e_entry', 8), |
| ('e_phoff', 8), |
| ('e_shoff', 8), |
| ('e_flags', 4), |
| ('e_ehsize', 2), |
| ('e_phentsize', 2), |
| ('e_phnum', 2), |
| ('e_shentsize', 2), |
| ('e_shnum', 2), |
| ('e_shstrndx', 2), |
| ] |
| |
| self._ValidateBitness(data) |
| byte_order = self._ReadByteOrder(data) |
| super(ElfHeader, self).__init__(byte_order, fields) |
| |
| self.ParseBytes(data, 0) |
| if self.e_type != ElfHeader.EType.ET_DYN: |
| raise RuntimeError('Only shared libraries are supported') |
| |
| self.phdrs = [] |
| self._ParsePhdrs(data) |
| |
| def GetPhdrs(self): |
| """Returns the list of file's program headers.""" |
| return self.phdrs |
| |
| def AddPhdr(self, phdr): |
| """Adds a new ProgramHeader entry correcting the e_phnum variable. |
| |
| Args: |
| phdr: ProgramHeader. Instance of ProgramHeader to add. |
| """ |
| self.phdrs.append(phdr) |
| self.e_phnum += 1 |
| |
| def PatchData(self, data): |
| """Patches the given data array to reflect all changes made to the header. |
| |
| This method doesn't completely rewrite the data, instead it patches |
| inplace. Not only the ElfHeader is patched but all of its ProgramHeader |
| as well. |
| |
| The important limitation is that this method doesn't take changes of sizes |
| and offsets into account. As example, if new ProgramHeader is added, this |
| method will override whatever data is located under its placement so the |
| user has to move the headers to the end beforehand or the user mustn't |
| change header's size. |
| |
| Args: |
| data: bytearray. The data array to be patched. |
| """ |
| elf_bytes = self.ToBytes() |
| data[:len(elf_bytes)] = elf_bytes |
| current_offset = self.e_phoff |
| for phdr in self.GetPhdrs(): |
| phdr_bytes = phdr.ToBytes() |
| data[current_offset:current_offset + len(phdr_bytes)] = phdr_bytes |
| current_offset += self.e_phentsize |