├── README.md ├── setup.py └── src ├── botox ├── __init__.py ├── architecture.py ├── elf.py └── exceptions.py └── scripts └── botox /README.md: -------------------------------------------------------------------------------- 1 | Botox 2 | ===== 3 | 4 | This tool injects the following code into a Linux ELF file: 5 | 6 | ```C 7 | kill(getpid(), SIGSTOP); 8 | goto entry_point; 9 | ``` 10 | 11 | When the ELF file is loaded, this will immediately pause execution until a SIGCONT signal is sent to the process, at which point execution resumes from the ELF's original entry point. 12 | 13 | Why might this be useful? 14 | 15 | 1. You wish to debug a process that is executed by another process (e.g., a CGI file executed by a Web server). 16 | 2. You want to examine the memory layout (`/proc/pid/maps`) of a short-lived process without requiring a debugger. 17 | 3. You wish to debug a process, but starting the process from inside a debugger can modify process environment variables, stack offsets, etc. 18 | 19 | These goals are not always easily realized via traditional methods, especially on embedded systems where you may have limited access to debugging tools / toolchains. 20 | 21 | Usage 22 | ===== 23 | 24 | Simply provide the path to the ELF binary you wish to modify, and Botox will add the SIGSTOP code to it: 25 | 26 | ```bash 27 | $ botox ./path/to/some/file.cgi 28 | ``` 29 | 30 | Supported Architectures 31 | ======================= 32 | 33 | Botox currently supports x86, x86_64, ARM, and MIPS Linux ELF files (executable, non-relocatable). 34 | 35 | Installation 36 | ============ 37 | 38 | Install the `Botox` Python module and script using `pip`: 39 | 40 | ```bash 41 | $ sudo pip3 install . 42 | ``` 43 | 44 | Dependencies 45 | ============ 46 | 47 | Botox is written for Python3, and requires the [keystone assembler](http://www.keystone-engine.org/) library and Python module. 48 | 49 | ```bash 50 | $ sudo pip3 install keystone-engine 51 | ``` 52 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | from distutils.core import setup, Command 4 | from distutils.dir_util import remove_tree 5 | 6 | MODULE_NAME = "botox" 7 | SCRIPT_NAME = MODULE_NAME 8 | 9 | class CleanCommand(Command): 10 | description = "Clean Python build directories" 11 | user_options = [] 12 | 13 | def initialize_options(self): 14 | pass 15 | 16 | def finalize_options(self): 17 | pass 18 | 19 | def run(self): 20 | try: 21 | remove_tree("build") 22 | except KeyboardInterrupt as e: 23 | raise e 24 | except Exception: 25 | pass 26 | 27 | try: 28 | remove_tree("dist") 29 | except KeyboardInterrupt as e: 30 | raise e 31 | except Exception: 32 | pass 33 | 34 | # Install the module, script, and support files 35 | setup(name = MODULE_NAME, 36 | version = "0.2b", 37 | description = "ELF patching tool", 38 | author = "Craig Heffner", 39 | url = "https://github.com/devttys0/%s" % MODULE_NAME, 40 | 41 | requires = [], 42 | package_dir = {"" : "src"}, 43 | packages = [MODULE_NAME], 44 | scripts = [os.path.join("src", "scripts", SCRIPT_NAME)], 45 | 46 | cmdclass = {'clean' : CleanCommand} 47 | ) 48 | 49 | -------------------------------------------------------------------------------- /src/botox/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import struct 3 | from botox.elf import ELF 4 | import botox.architecture as architecture 5 | from botox.exceptions import BotoxException 6 | 7 | class Botox(object): 8 | 9 | def __init__(self, elfile, verbose=False): 10 | ''' 11 | Class constructor. 12 | 13 | @elfile - Path to the target ELF file to patch. 14 | @verbose - Set to True to enable verbose debug print statements. 15 | 16 | Returns None. 17 | ''' 18 | self.elfile = elfile 19 | self.verbose = verbose 20 | 21 | def _resolve_architecture(self, machine_type): 22 | ''' 23 | Returns a subclass of architecture.Architecture that corresponds 24 | to the target ELF's architecture. 25 | 26 | @machine_type - The e_machine value from the ELF header. 27 | 28 | Returns a subclass of architecture.Architecture on success. 29 | Returns None on failure. 30 | ''' 31 | # Loop through all the attributes in the architecture.py module 32 | # and find the Architecture subclass that matches the target 33 | # ELF's machine type. 34 | for attribute_name in dir(architecture): 35 | try: 36 | try: 37 | attribute = getattr(architecture, attribute_name) 38 | except AttributeError as e: 39 | continue 40 | 41 | if issubclass(attribute, architecture.Architecture): 42 | if attribute.MACHINE == machine_type: 43 | return attribute 44 | except TypeError as e: 45 | continue 46 | 47 | return None 48 | 49 | def _debug_print(self, msg): 50 | ''' 51 | For internal debug use. 52 | 53 | @msg - Message to print to stderr, if self.verbose is True. 54 | 55 | Returns None. 56 | ''' 57 | if True == self.verbose: 58 | sys.stderr.write(msg + "\n") 59 | 60 | def patch(self, payload=None): 61 | ''' 62 | Injects the supplied payload into the target ELF file. 63 | The entry point will be modified to point to the injected code. 64 | 65 | @payload - The payload to inject into the ELF file. 66 | If no payload is provided, the default pause payload will be used. 67 | 68 | Returns the new entry point address on success. 69 | Returns None on failure, or (more likely) raises an exception. 70 | ''' 71 | alignment_size = None 72 | load_segment_size = None 73 | load_segment_offset = None 74 | load_segment_virtual_base_address = None 75 | 76 | # Open the target ELF file for writing 77 | with ELF(self.elfile, read_only=False) as elf: 78 | # Relocatable files, shared objects, etc should be ignored. 79 | # These can be supported in the future, but relative addressing 80 | # support needs to be added to the payload code in architectures.py. 81 | if ELF.ET_EXEC != elf.header.e_type: 82 | raise BotoxException("Sorry, I only support non-PIE ELF executable files!") 83 | 84 | # If no payload was specified, use the built-in pause payload 85 | if payload is None: 86 | arch = self._resolve_architecture(elf.header.e_machine) 87 | if arch is None: 88 | raise BotoxException("Sorry, this architecture [0x%X 0x%X] is not supported!" % (elf.header.e_machine, elf.header.e_ident.ei_class)) 89 | payload = arch(elf.header.e_ident.ei_encoding).payload(elf.header.e_entry) 90 | 91 | # Loop through all the program headers looking for the first executable load segment 92 | for phdr in elf.program_headers: 93 | if ELF.PT_LOAD == phdr.p_type and True == phdr.flags.execute: 94 | self._debug_print("Modifying program header #%d" % phdr.index) 95 | 96 | alignment_size = phdr.p_align 97 | 98 | load_segment_size = phdr.p_filesz 99 | load_segment_offset = phdr.p_offset 100 | load_segment_virtual_base_address = phdr.p_vaddr - phdr.p_offset 101 | 102 | # Don't want to insert multiple SIGSTOPs, do a sanity check before modifying anything. 103 | # Check the first few bytes of the current entry point against the first few bytes of the payload. 104 | # Can't check against the entire payload, since the end of the payload will be jumping to the 105 | # entry point, which will change each time botox modifies an ELF file; 16 bytes should be sufficient. 106 | if elf.read((elf.header.e_entry - load_segment_virtual_base_address), 16) == payload[0:16]: 107 | raise BotoxException("I've already patched this binary, and I shan't do it again!") 108 | 109 | # Increase this segment's file and memory size so we can shove our payload in it 110 | phdr.p_memsz += alignment_size 111 | phdr.p_filesz += alignment_size 112 | 113 | break 114 | 115 | # Sanity checks 116 | if None in [alignment_size, load_segment_size, load_segment_offset, load_segment_virtual_base_address]: 117 | raise BotoxException("Failed to locate a loadable, executable segment! What is this, an ELF file for ants?!") 118 | if len(payload) > alignment_size: 119 | raise BotoxException("Sorry, my developer was too lazy to tell me how to handle payloads larger than the segment alignment size (%d)!" % alignment_size) 120 | 121 | # Pad our payload out to the alignment size of the load segment 122 | payload += "\x00" * (alignment_size - len(payload)) 123 | payload_size = len(payload) 124 | 125 | # By default, the payload is just slapped on the end of the executable 126 | # load segment as defined in the program headers. 127 | payload_offset = load_segment_offset + load_segment_size 128 | self._debug_print("Payload will be placed at file offset 0x%X (virtual address: 0x%X)" % (payload_offset, load_segment_virtual_base_address + payload_offset)) 129 | 130 | # Each segment defined in the program headers that starts *after* 131 | # the offset where our payload will be inserted must have its 132 | # starting offset increased by the size of our payload. 133 | for phdr in elf.program_headers: 134 | if payload_offset <= phdr.p_offset: 135 | self._debug_print("Increasing the size of program header #%d by 0x%X" % (phdr.index, payload_size)) 136 | phdr.p_offset += payload_size 137 | 138 | # Each section defined in the section headers that starts *after* 139 | # the offset where our payload will be inserted must have its 140 | # starting offset increased by the size of our payload. 141 | for shdr in elf.section_headers: 142 | if payload_offset <= shdr.sh_offset: 143 | self._debug_print("Increasing the size of section header %s by 0x%X" % (shdr.name, payload_size)) 144 | shdr.sh_offset += payload_size 145 | 146 | # The section in which the actual payload should reside must have its size increased 147 | # to acommodate the new payload, and must also be marked as executable. 148 | if payload_offset > shdr.sh_offset and payload_offset <= (shdr.sh_offset + shdr.sh_size): 149 | self._debug_print("Payload will reside in section %s, increasing its size by 0x%X" % (shdr.name, payload_size)) 150 | shdr.flags.execute = True 151 | shdr.flags.allocate = True 152 | shdr.sh_size += payload_size 153 | 154 | # If the section headers come after the new payload insertion location 155 | # (which they will), update the offset of the section headers by the 156 | # size of the payload. 157 | if payload_offset <= elf.header.e_shoff: 158 | self._debug_print("Increasing section header offset by 0x%X" % payload_size) 159 | elf.header.e_shoff += payload_size 160 | 161 | # Update the program entry point to be the location of our payload 162 | self._debug_print("Setting ELF entry point to 0x%X" % (load_segment_virtual_base_address + payload_offset)) 163 | elf.header.e_entry = load_segment_virtual_base_address + payload_offset 164 | 165 | # Now that all header information has been updated to acommodate the 166 | # payload, insert the payload into the ELF file. 167 | self._debug_print("Inserting payload of size 0x%X at file offset 0x%X" % (payload_size, payload_offset)) 168 | elf.insert(payload_offset, payload) 169 | 170 | return elf.header.e_entry 171 | 172 | return None 173 | 174 | -------------------------------------------------------------------------------- /src/botox/architecture.py: -------------------------------------------------------------------------------- 1 | import struct 2 | from .elf import ELF 3 | from .exceptions import BotoxException 4 | 5 | try: 6 | from keystone import * 7 | except ImportError as e: 8 | raise BotoxException("Botox requires the keystone module! Please install it from: https://github.com/keystone-engine/keystone") 9 | 10 | class Architecture(object): 11 | ''' 12 | Architecture class. All other arch-specific classes should be subclassed from this. 13 | ''' 14 | # Payload code, one assembly instruction per list entry. This code will 15 | # be assembled at run time by the keystone library. The payload code should 16 | # send itself a SIGSTOP signal, then jump to the original program's entry 17 | # point; effectively: 18 | # 19 | # kill(getpid(), SIGSTOP); 20 | # goto entry_point; 21 | # 22 | # As the actual entry point address will not be known until runtime, the 23 | # literal string "entry_point" may be used in the assembly code. This will 24 | # be replaced at runtime by the hexadecimal entry point address prior to 25 | # assembly. 26 | ASM = [] 27 | # The keystone.KS_ARCH_XXX architecture associated with this architecture 28 | ARCH = None 29 | # The keystone.KS_MODE_XXX mode to be used with this architecture 30 | MODE = None 31 | # The machine type of the target architecture, as defined in the ELF header 32 | # See the elf.ELF.EM_XXX constants. 33 | MACHINE = None 34 | 35 | BIG = ELF.ELFDATA2MSB 36 | LITTLE = ELF.ELFDATA2LSB 37 | 38 | ENTRY_POINT = "entry_point" 39 | 40 | def __init__(self, endianess): 41 | ''' 42 | Class constructor. 43 | 44 | @endianess - The endianess of the target architecture, as specified in the ELF 45 | header (e_ident.ei_encoding). 46 | 47 | Returns None. 48 | ''' 49 | self.endianess = endianess 50 | 51 | def payload(self, jump_address): 52 | ''' 53 | Generates a payload that will pause the process execution 54 | until a SIGCONT signal is passed to the process, at which 55 | point the code will jump to a specified address. 56 | 57 | @jump_address - The address to jump to when SIGCONT is encountered. 58 | 59 | Returns a string containing the shellcode. 60 | ''' 61 | encoding = [] 62 | 63 | # Set big/little endian flag for keystone 64 | if self.endianess == self.BIG: 65 | endian_mode = KS_MODE_BIG_ENDIAN 66 | else: 67 | endian_mode = KS_MODE_LITTLE_ENDIAN 68 | 69 | # Instatiate the keystone.Ks class for assembly 70 | ks = Ks(self.ARCH, self.MODE | endian_mode) 71 | 72 | # Assemble each line to a list of raw bytes that are appended to the 73 | # encoding list. Exceptions in assembling any specific line of code 74 | # will be caught and a BotoxException will be raised. 75 | for line in self.ASM: 76 | assembly = line.replace(self.ENTRY_POINT, hex(jump_address)) 77 | 78 | try: 79 | (hexbytes, count) = ks.asm(assembly) 80 | encoding += hexbytes 81 | except KeyboardInterrupt as e: 82 | raise e 83 | except Exception as e: 84 | raise BotoxException("Failed to assemble payload line '%s': %s" % (assembly, str(e))) 85 | 86 | # Convert the list of raw bytes into a string and return 87 | return ''.join([chr(byte) for byte in encoding]) 88 | 89 | class X86(Architecture): 90 | MACHINE = ELF.EM_386 91 | ARCH = KS_ARCH_X86 92 | MODE = KS_MODE_32 93 | ASM = [ 94 | "mov eax, 20", 95 | "int 0x80", # getpid(); 96 | "mov ebx, eax", 97 | "mov ecx, 19", 98 | "mov eax, 37", 99 | "int 0x80", # kill(pid, SIGSTOP); 100 | "mov eax, %s" % Architecture.ENTRY_POINT, 101 | "jmp eax", # goto entry_point 102 | ] 103 | 104 | class X86_64(Architecture): 105 | MACHINE = ELF.EM_X86_64 106 | ARCH = KS_ARCH_X86 107 | MODE = KS_MODE_64 108 | ASM = [ 109 | "mov eax, 0x27", 110 | "syscall", # getpid(); 111 | "mov rdi, rax", 112 | "mov rsi, 19", 113 | "mov rax, 0x3E", 114 | "syscall", # kill(pid, SIGSTOP); 115 | "mov rax, %s" % Architecture.ENTRY_POINT, 116 | "jmp rax", # goto entry_point; 117 | ] 118 | 119 | class MIPS(Architecture): 120 | MACHINE = ELF.EM_MIPS 121 | ARCH = KS_ARCH_MIPS 122 | MODE = KS_MODE_MIPS32 123 | ASM = [ 124 | "li $v0, 0xFB4", 125 | "syscall 0", # getpid(); 126 | "move $a0, $v0", 127 | "li $a1, 23", 128 | "li $v0, 0xFC5", 129 | "syscall 0", # kill(pid, SIGSTOP); 130 | "li $t0, %s" % Architecture.ENTRY_POINT, 131 | "jr $t0", # goto entry_point; 132 | ] 133 | 134 | class ARM(Architecture): 135 | MACHINE = ELF.EM_ARM 136 | ARCH = KS_ARCH_ARM 137 | MODE = KS_MODE_ARM 138 | ASM = [ 139 | "mov R7, #0x14", 140 | "svc #0", # getpid(); 141 | "mov R1, #19", 142 | "mov R7, #0x25", 143 | "svc #0", # kill(pid, SIGSTOP); 144 | "ldr PC, =%s" % Architecture.ENTRY_POINT # goto entry_point 145 | ] 146 | 147 | -------------------------------------------------------------------------------- /src/botox/elf.py: -------------------------------------------------------------------------------- 1 | # http://www.skyfree.org/linux/references/ELF_Format.pdf 2 | # https://www.uclibc.org/docs/elf-64-gen.pdf 3 | import os 4 | import struct 5 | 6 | class Elf_Shdr_Flags(object): 7 | ''' 8 | Convenience wrapper class for reading and writing a section header's flags. 9 | ''' 10 | 11 | def __init__(self, shdr): 12 | ''' 13 | Class constructor. 14 | 15 | @shdr - Instance of Elf_Shdr. 16 | 17 | Returns None. 18 | ''' 19 | self.shdr = shdr 20 | 21 | @property 22 | def write(self): 23 | if (1 & self.shdr.sh_flags): 24 | return True 25 | return False 26 | @write.setter 27 | def write(self, value): 28 | if value == True: 29 | self.shdr.sh_flags |= 1 30 | else: 31 | self.shdr.sh_flags &= ~1 32 | 33 | @property 34 | def allocate(self): 35 | if (2 & self.shdr.sh_flags): 36 | return True 37 | return False 38 | @allocate.setter 39 | def allocate(self, value): 40 | if value == True: 41 | self.shdr.sh_flags |= 2 42 | else: 43 | self.shdr.sh_flags &= ~2 44 | 45 | @property 46 | def execute(self): 47 | if (4 & self.shdr.sh_flags): 48 | return True 49 | return False 50 | @execute.setter 51 | def execute(self, value): 52 | if value == True: 53 | self.shdr.sh_flags |= 4 54 | else: 55 | self.shdr.sh_flags &= ~4 56 | 57 | class Elf_Shdr(object): 58 | ''' 59 | Class for reading/writing the contents of an ELF section header entry. 60 | ''' 61 | 62 | def __init__(self, elf, n=0): 63 | ''' 64 | Class constructor. 65 | 66 | @elf - Instance of the ELF class. 67 | @n - The index number of the section header (zero-indexed). 68 | 69 | Returns None. 70 | ''' 71 | self.elf = elf 72 | self.index = n 73 | 74 | if self.elf.header.e_shstrndx != self.index: 75 | self._name = ".shstrtab" 76 | else: 77 | self._name = None 78 | 79 | self.flags = Elf_Shdr_Flags(self) 80 | 81 | @property 82 | def name(self): 83 | if self.index != self.elf.header.e_shstrndx: 84 | return self.elf.read_string(self.elf.shstrtab.sh_offset + self.sh_name) 85 | else: 86 | return self._name 87 | @name.setter 88 | def name(self, value): 89 | if self.index != self.elf.header.e_shstrndx: 90 | current_name = self.name 91 | if len(current_name) > len(value): 92 | raise Exception("New section header name must be of equal of lesser length than the current name (%s)!" % current_name) 93 | else: 94 | self.elf.write_string(self.elf.shstrtab.sh_offset + self.sh_name, value) 95 | else: 96 | self._name = value 97 | 98 | @property 99 | def sh_name(self): 100 | return self.elf.read_word(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+0) 101 | @sh_name.setter 102 | def sh_name(self, value): 103 | self.elf.write_word(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+0, value) 104 | 105 | @property 106 | def sh_type(self): 107 | return self.elf.read_word(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+4) 108 | @sh_type.setter 109 | def sh_type(self, value): 110 | self.elf.write_word(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+4, value) 111 | 112 | @property 113 | def sh_flags(self): 114 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 115 | return self.elf.read_double(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+8) 116 | else: 117 | return self.elf.read_word(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+8) 118 | @sh_flags.setter 119 | def sh_flags(self, value): 120 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 121 | self.elf.write_double(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+8, value) 122 | else: 123 | self.elf.write_word(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+8, value) 124 | 125 | @property 126 | def sh_addr(self): 127 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 128 | return self.elf.read_double(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+16) 129 | else: 130 | return self.elf.read_word(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+12) 131 | @sh_addr.setter 132 | def sh_addr(self, value): 133 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 134 | self.elf.write_double(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+16, value) 135 | else: 136 | self.elf.write_word(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+12, value) 137 | 138 | @property 139 | def sh_offset(self): 140 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 141 | return self.elf.read_double(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+24) 142 | else: 143 | return self.elf.read_word(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+16) 144 | @sh_offset.setter 145 | def sh_offset(self, value): 146 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 147 | self.elf.write_double(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+24, value) 148 | else: 149 | self.elf.write_word(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+16, value) 150 | 151 | @property 152 | def sh_size(self): 153 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 154 | return self.elf.read_double(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+32) 155 | else: 156 | return self.elf.read_word(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+20) 157 | @sh_size.setter 158 | def sh_size(self, value): 159 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 160 | self.elf.write_double(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+32, value) 161 | else: 162 | self.elf.write_word(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+20, value) 163 | 164 | @property 165 | def sh_link(self): 166 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 167 | return self.elf.read_word(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+40) 168 | else: 169 | return self.elf.read_word(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+24) 170 | @sh_link.setter 171 | def sh_link(self, value): 172 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 173 | self.elf.write_word(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+40, value) 174 | else: 175 | self.elf.write_word(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+24, value) 176 | 177 | @property 178 | def sh_info(self): 179 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 180 | return self.elf.read_word(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+44) 181 | else: 182 | return self.elf.read_word(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+28) 183 | @sh_info.setter 184 | def sh_info(self, value): 185 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 186 | self.elf.write_word(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+44, value) 187 | else: 188 | self.elf.write_word(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+28, value) 189 | 190 | @property 191 | def sh_addralign(self): 192 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 193 | return self.elf.read_double(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+48) 194 | else: 195 | return self.elf.read_word(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+32) 196 | @sh_addralign.setter 197 | def sh_addralign(self, value): 198 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 199 | self.elf.write_double(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+48, value) 200 | else: 201 | self.elf.write_word(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+32, value) 202 | 203 | @property 204 | def sh_entsize(self): 205 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 206 | return self.elf.read_double(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+56) 207 | else: 208 | return self.elf.read_word(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+36) 209 | @sh_entsize.setter 210 | def sh_entsize(self, value): 211 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 212 | self.elf.write_double(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+56, value) 213 | else: 214 | self.elf.write_word(self.elf.header.e_shoff+(self.elf.header.e_shentsize*self.index)+36, value) 215 | 216 | class Elf_Phdr_Flags(object): 217 | ''' 218 | Convenience wrapper class for reading and writing a program header's flags. 219 | ''' 220 | 221 | def __init__(self, phdr): 222 | ''' 223 | Class constructor. 224 | 225 | @phdr - Instance of the ELf32_Phdr class. 226 | 227 | Returns None. 228 | ''' 229 | self.phdr = phdr 230 | 231 | @property 232 | def read(self): 233 | if 0b100 & self.phdr.p_flags: 234 | return True 235 | return False 236 | @read.setter 237 | def read(self, tf): 238 | if True == tf: 239 | self.phdr.p_flags |= 0b100 240 | else: 241 | self.phdr.p_flags &= ~0b100 242 | 243 | @property 244 | def write(self): 245 | if 0b010 & self.phdr.p_flags: 246 | return True 247 | return False 248 | @write.setter 249 | def write(self, tf): 250 | if True == tf: 251 | self.phdr.p_flags |= 0b010 252 | else: 253 | self.phdr.p_flags &= ~0b010 254 | 255 | @property 256 | def execute(self): 257 | if 0b001 & self.phdr.p_flags: 258 | return True 259 | return False 260 | @execute.setter 261 | def execute(self, tf): 262 | if True == tf: 263 | self.phdr.p_flags |= 0b001 264 | else: 265 | self.phdr.p_flags &= ~0b001 266 | 267 | class Elf_Phdr(object): 268 | ''' 269 | Class for reading/writing the contents of an ELF program header entry. 270 | ''' 271 | 272 | def __init__(self, elf, n=0): 273 | ''' 274 | Class constructor. 275 | 276 | @elf - An instance of the ELF class. 277 | @n - The index number of the program header (zero-indexed). 278 | 279 | Returns None. 280 | ''' 281 | self.elf = elf 282 | self.index = n 283 | self.flags = Elf_Phdr_Flags(self) 284 | 285 | @property 286 | def p_type(self): 287 | return self.elf.read_word(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+0) 288 | @p_type.setter 289 | def p_type(self, value): 290 | self.elf.write_word(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+0, value) 291 | 292 | @property 293 | def p_offset(self): 294 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 295 | return self.elf.read_double(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+8) 296 | else: 297 | return self.elf.read_word(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+4) 298 | @p_offset.setter 299 | def p_offset(self, value): 300 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 301 | self.elf.write_double(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+8, value) 302 | else: 303 | self.elf.write_word(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+4, value) 304 | 305 | @property 306 | def p_vaddr(self): 307 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 308 | return self.elf.read_double(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+16) 309 | else: 310 | return self.elf.read_word(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+8) 311 | @p_vaddr.setter 312 | def p_vaddr(self, value): 313 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 314 | self.elf.write_double(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+16, value) 315 | else: 316 | self.elf.write_word(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+8, value) 317 | 318 | @property 319 | def p_paddr(self): 320 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 321 | return self.elf.read_double(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+24) 322 | else: 323 | return self.elf.read_word(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+12) 324 | @p_paddr.setter 325 | def p_paddr(self, value): 326 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 327 | self.elf.write_double(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+24, value) 328 | else: 329 | self.elf.write_word(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+12, value) 330 | 331 | @property 332 | def p_filesz(self): 333 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 334 | return self.elf.read_double(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+32) 335 | else: 336 | return self.elf.read_word(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+16) 337 | @p_filesz.setter 338 | def p_filesz(self, value): 339 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 340 | self.elf.write_double(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+32, value) 341 | else: 342 | self.elf.write_word(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+16, value) 343 | 344 | @property 345 | def p_memsz(self): 346 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 347 | return self.elf.read_double(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+40) 348 | else: 349 | return self.elf.read_word(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+20) 350 | @p_memsz.setter 351 | def p_memsz(self, value): 352 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 353 | self.elf.write_double(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+40, value) 354 | else: 355 | self.elf.write_word(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+20, value) 356 | 357 | @property 358 | def p_flags(self): 359 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 360 | return self.elf.read_word(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+4) 361 | else: 362 | return self.elf.read_word(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+24) 363 | @p_flags.setter 364 | def p_flags(self, value): 365 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 366 | self.elf.write_word(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+4, value) 367 | else: 368 | self.elf.write_word(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+24, value) 369 | 370 | @property 371 | def p_align(self): 372 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 373 | return self.elf.read_double(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+48) 374 | else: 375 | return self.elf.read_word(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+28) 376 | @p_align.setter 377 | def p_align(self, value): 378 | if self.elf.ELFCLASS64 == self.elf.header.e_ident.ei_class: 379 | self.elf.write_double(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+48, value) 380 | else: 381 | self.elf.write_word(self.elf.header.e_phoff+(self.elf.header.e_phentsize*self.index)+28, value) 382 | 383 | class Elf_Ident(object): 384 | ''' 385 | Class for reading/writing the contents of the e_ident section of the ELF header. 386 | ''' 387 | 388 | def __init__(self, elf): 389 | ''' 390 | Class constructor. 391 | 392 | @elf - An instance of the ELF class. 393 | 394 | Returns None. 395 | ''' 396 | self.elf = elf 397 | 398 | @property 399 | def ei_magic(self): 400 | return self.elf.read(0, 4) 401 | @ei_magic.setter 402 | def ei_magic(self, value): 403 | self.elf.write(0, value[0:3]) 404 | 405 | @property 406 | def ei_class(self): 407 | return self.elf.read_byte(4) 408 | @ei_class.setter 409 | def ei_class(self, value): 410 | self.elf.write_byte(4, value) 411 | 412 | @property 413 | def ei_encoding(self): 414 | return self.elf.read_byte(5) 415 | @ei_encoding.setter 416 | def ei_encoding(self, value): 417 | return self.elf.write_byte(5, value) 418 | 419 | @property 420 | def ei_version(self): 421 | return self.elf.read_byte(6) 422 | @ei_version.setter 423 | def ei_version(self, value): 424 | return self.elf.write_byte(6, value) 425 | 426 | class Elf_Header(object): 427 | ''' 428 | Class for reading/writing the contents of an ELF header. 429 | ''' 430 | 431 | def __init__(self, elf): 432 | ''' 433 | Class constructor. 434 | 435 | @elf - An instance of the ELF class. 436 | 437 | Returns None. 438 | ''' 439 | self.elf = elf 440 | self.e_ident = Elf_Ident(self.elf) 441 | 442 | @property 443 | def e_type(self): 444 | return self.elf.read_half(16) 445 | @e_type.setter 446 | def e_type(self, value): 447 | self.elf.write_half(16, value) 448 | 449 | @property 450 | def e_machine(self): 451 | return self.elf.read_half(18) 452 | @e_machine.setter 453 | def e_machine(self, value): 454 | self.elf.write_half(18, value) 455 | 456 | @property 457 | def e_version(self): 458 | return self.elf.read_word(20) 459 | @e_version.setter 460 | def e_version(self, value): 461 | self.elf.write_word(20, value) 462 | 463 | @property 464 | def e_entry(self): 465 | if self.elf.ELFCLASS64 == self.e_ident.ei_class: 466 | return self.elf.read_double(24) 467 | else: 468 | return self.elf.read_word(24) 469 | @e_entry.setter 470 | def e_entry(self, value): 471 | if self.elf.ELFCLASS64 == self.e_ident.ei_class: 472 | self.elf.write_double(24, value) 473 | else: 474 | self.elf.write_word(24, value) 475 | 476 | @property 477 | def e_phoff(self): 478 | if self.elf.ELFCLASS64 == self.e_ident.ei_class: 479 | return self.elf.read_double(32) 480 | else: 481 | return self.elf.read_word(28) 482 | @e_phoff.setter 483 | def e_phoff(self, value): 484 | if self.elf.ELFCLASS64 == self.e_ident.ei_class: 485 | self.elf.write_double(32) 486 | else: 487 | self.elf.write_word(28, value) 488 | 489 | @property 490 | def e_shoff(self): 491 | if self.elf.ELFCLASS64 == self.e_ident.ei_class: 492 | return self.elf.read_double(40) 493 | else: 494 | return self.elf.read_word(32) 495 | @e_shoff.setter 496 | def e_shoff(self, value): 497 | if self.elf.ELFCLASS64 == self.e_ident.ei_class: 498 | self.elf.write_double(40, value) 499 | else: 500 | self.elf.write_word(32, value) 501 | 502 | @property 503 | def e_flags(self): 504 | if self.elf.ELFCLASS64 == self.e_ident.ei_class: 505 | return self.elf_read_word(48) 506 | else: 507 | return self.elf.read_word(36) 508 | @e_flags.setter 509 | def e_flags(self, value): 510 | if self.elf.ELFCLASS64 == self.e_ident.ei_class: 511 | self.elf.write_word(48, value) 512 | else: 513 | self.elf.write_word(36, value) 514 | 515 | @property 516 | def e_ehsize(self): 517 | if self.elf.ELFCLASS64 == self.e_ident.ei_class: 518 | return self.elf.read_half(52) 519 | else: 520 | return self.elf.read_half(40) 521 | @e_ehsize.setter 522 | def e_ehsize(self, value): 523 | if self.elf.ELFCLASS64 == self.e_ident.ei_class: 524 | self.elf.write_half(52, value) 525 | else: 526 | self.elf.write_half(40, value) 527 | 528 | @property 529 | def e_phentsize(self): 530 | if self.elf.ELFCLASS64 == self.e_ident.ei_class: 531 | return self.elf.read_half(54) 532 | else: 533 | return self.elf.read_half(42) 534 | @e_phentsize.setter 535 | def e_phentsize(self, value): 536 | if self.elf.ELFCLASS64 == self.e_ident.ei_class: 537 | self.elf.write_half(54, value) 538 | else: 539 | self.elf.write_half(42, value) 540 | 541 | @property 542 | def e_phnum(self): 543 | if self.elf.ELFCLASS64 == self.e_ident.ei_class: 544 | return self.elf.read_half(56) 545 | else: 546 | return self.elf.read_half(44) 547 | @e_phnum.setter 548 | def e_phnum(self, value): 549 | if self.elf.ELFCLASS64 == self.e_ident.ei_class: 550 | self.elf.write_half(56, value) 551 | else: 552 | self.elf.write_half(44, value) 553 | 554 | @property 555 | def e_shentsize(self): 556 | if self.elf.ELFCLASS64 == self.e_ident.ei_class: 557 | return self.elf.read_half(58) 558 | else: 559 | return self.elf.read_half(46) 560 | @e_shentsize.setter 561 | def e_shentsize(self, value): 562 | if self.elf.ELFCLASS64 == self.e_ident.ei_class: 563 | self.elf.write_half(58, value) 564 | else: 565 | self.elf.write_half(46, value) 566 | 567 | @property 568 | def e_shnum(self): 569 | if self.elf.ELFCLASS64 == self.e_ident.ei_class: 570 | return self.elf.read_half(60) 571 | else: 572 | return self.elf.read_half(48) 573 | @e_shnum.setter 574 | def e_shnum(self, value): 575 | if self.elf.ELFCLASS64 == self.e_ident.ei_class: 576 | self.elf.write_half(60, value) 577 | else: 578 | self.elf.write_half(48, value) 579 | 580 | @property 581 | def e_shstrndx(self): 582 | if self.elf.ELFCLASS64 == self.e_ident.ei_class: 583 | return self.elf.read_half(62) 584 | else: 585 | return self.elf.read_half(50) 586 | @e_shstrndx.setter 587 | def e_shstrndx(self, value): 588 | if self.elf.ELFCLASS64 == self.e_ident.ei_class: 589 | self.elf.write_half(62, value) 590 | else: 591 | self.elf.write_half(50, value) 592 | 593 | class ELF(object): 594 | ''' 595 | Primary class for accessing and manipulating ELF files. 596 | Most other classes consist primarily of setters/getters. 597 | 598 | Important class objects: 599 | 600 | o self.size - Returns the size of the ELF file on disk 601 | o self.header - EL32_Header object, providing access to the ELF header data 602 | o self.program_headers - List of Elf_Phdr objects, providing access to the program header data 603 | o self.section_headers - List of Elf_Shdr objects, providing access to the section header data 604 | 605 | Be careful using this class to access elements of the ELF header!! Most everything is implemented 606 | as getters/setters that directly access the target file on disk. This means, for example, that 607 | reading from ELF.header.e_entry results in a read from the file, and assigning a value to 608 | ELF.header.e_entry results in that value being written to the file! 609 | 610 | If you want to make *sure* nothing gets accidentally written to disk, instantiate this class with 611 | read_only=True. 612 | ''' 613 | 614 | ELFDATA2LSB = 1 615 | ELFDATA2MSB = 2 616 | 617 | ET_NONE = 0 618 | ET_REL = 1 619 | ET_EXEC = 2 620 | ET_DYN = 3 621 | ET_CORE = 4 622 | ET_LOPROC = 0xFF00 623 | ET_HIPROC = 0xFFFF 624 | 625 | PT_NULL = 0 626 | PT_LOAD = 1 627 | PT_DYNAMIC = 2 628 | PT_INTERP = 3 629 | PT_NOTE = 4 630 | PT_SHLIB = 5 631 | PT_PHDR = 6 632 | PT_LOOS = 0x60000000 633 | PT_HIOS = 0x6FFFFFFF 634 | PT_LOPROC = 0x70000000 635 | PT_HIPROC = 0x7FFFFFFF 636 | 637 | EM_NONE = 0 638 | EM_SPARC = 2 639 | EM_386 = 3 640 | EM_MIPS = 8 641 | EM_ARM = 40 642 | EM_X86_64 = 62 643 | 644 | ELFCLASSNONE = 0 645 | ELFCLASS32 = 1 646 | ELFCLASS64 = 2 647 | 648 | SHT_SYMTAB = 2 649 | SHT_DYNSYM = 11 650 | 651 | def __init__(self, elfile, read_only=False): 652 | ''' 653 | Class constructor. 654 | 655 | @elfile - The ELF file to load. 656 | @read_only - Set to True for read-only access to the file. 657 | 658 | Returns None. 659 | ''' 660 | self.read_only = read_only 661 | 662 | if self.read_only == True: 663 | self.file_mode = 'rb' 664 | else: 665 | self.file_mode = 'r+b' 666 | 667 | # Get absolute path to the file 668 | self.elfile = os.path.abspath(elfile) 669 | 670 | # Process the ELF header, along with program and section headers 671 | self._load_elf_file() 672 | 673 | def __enter__(self): 674 | return self 675 | 676 | def __exit__(self, t, v, b): 677 | self.fp.close() 678 | return None 679 | 680 | def _load_elf_file(self): 681 | ''' 682 | Opens and loads critical data from the ELF file. 683 | 684 | Returns None. 685 | ''' 686 | # Open the ELF file 687 | self._open_file() 688 | 689 | # Create a ELF header object 690 | self.header = Elf_Header(self) 691 | 692 | # Grab all the program headers 693 | self.program_headers = [] 694 | for n in range(0, self.header.e_phnum): 695 | phdr = Elf_Phdr(self, n) 696 | self.program_headers.append(phdr) 697 | 698 | # Get the strings section header so that subsequent section 699 | # headers can resolve their section names. 700 | self.shstrtab = Elf_Shdr(self, self.header.e_shstrndx) 701 | 702 | # Grab all the section headers 703 | self.section_headers = [] 704 | for n in range(0, self.header.e_shnum): 705 | shdr = Elf_Shdr(self, n) 706 | self.section_headers.append(shdr) 707 | 708 | # The below methods are the only ones that should touch self.fp 709 | # directly! All others should be wrappers around these. 710 | def _open_file(self): 711 | ''' 712 | Opens the ELF file whose path is listed in self.elfile. 713 | 714 | Returns None. 715 | ''' 716 | # Open the file in unbuffered mode 717 | self.fp = open(self.elfile, self.file_mode, 0) 718 | def _read_from_file(self, offset, size): 719 | ''' 720 | Read data from the ELF file. 721 | 722 | @offset - Seek to this file offset before reading 723 | @size - Number of bytes to read 724 | 725 | Returns a string of data read from the file. 726 | ''' 727 | self.fp.seek(offset) 728 | return self.fp.read(size) 729 | def _write_to_file(self, offset, data): 730 | ''' 731 | Write data to the ELF file. 732 | 733 | @offset - Seek to this file offset before writing 734 | @data - Data to write to the file 735 | 736 | Returns None. 737 | ''' 738 | self.fp.seek(offset) 739 | self.fp.write(data) 740 | # Shouldn't need the flush since the file is opened 741 | # in unbuffered mode, but it can't hurt...right?? 742 | self.fp.flush() 743 | def _file_overwrite(self, data): 744 | ''' 745 | Completely overwrite and re-load the ELF file on disk. 746 | 747 | @data - Overwrite the ELF file with this data. 748 | If in read-only mode, nothing will happen. 749 | 750 | Returns None. 751 | ''' 752 | # TODO: Should raise an exception if self.read_only is True? 753 | if self.read_only == False: 754 | self.fp.close() 755 | fp = open(self.elfile, "wb") 756 | fp.write(data) 757 | fp.close() 758 | self._load_elf_file() 759 | @property 760 | def size(self): 761 | self.fp.seek(0, 2) 762 | return self.fp.tell() 763 | @size.setter 764 | def size(self): 765 | return None 766 | # End of methods that should be directly accessing self.fp! 767 | 768 | # These two methods are the only ones that should be accessing 769 | # the internal _read_from_file and _write_to_file methods! 770 | def read(self, offset, size): 771 | return self._read_from_file(offset, size) 772 | def write(self, offset, data): 773 | return self._write_to_file(offset, data) 774 | 775 | # These three methods are the only ones that should be accessing 776 | # the internal _file_overwrite method! 777 | def insert(self, offset, data): 778 | ''' 779 | Insert data into the ELF file. 780 | 781 | @offset - Seek to this file offset before writing. 782 | @data - Insert this data to file. 783 | 784 | Returns None. 785 | ''' 786 | if type(data) == str: 787 | data = data.encode('latin-1') 788 | elf_file_contents = self.read(0, offset) 789 | elf_file_contents += data 790 | elf_file_contents += self.read(offset, (self.size-offset)) 791 | self._file_overwrite(elf_file_contents) 792 | def append(self, data): 793 | ''' 794 | Append data to the end of the ELF file. 795 | 796 | @data - Data to append. 797 | 798 | Returns None. 799 | ''' 800 | elf_file_contents = self.read(0, self.size) 801 | elf_file_contents += data 802 | self.write(0, elf_file_contents) 803 | self._file_overwrite(elf_file_contents) 804 | def delete(self, offset, size): 805 | ''' 806 | Remove data from the ELF file. 807 | 808 | @offset - Seek to this file offset before deleting. 809 | @size - Delete this many bytes from the file. 810 | 811 | Returns None. 812 | ''' 813 | elf_file_contents = self.read(0, offset) 814 | elf_file_contents += self.read((offset+size), (self.size - (offset+size))) 815 | self._file_overwrite(elf_file_contents) 816 | 817 | def write_string(self, offset, data): 818 | ''' 819 | Write data to the ELF file, adding a NULL terminating character. 820 | 821 | @offset - Seek to this file offset before writing. 822 | @data - Write this data to file. 823 | 824 | Returns None. 825 | ''' 826 | return self.write(offset, data + b"\x00") 827 | 828 | def read_string(self, offset, size=None): 829 | ''' 830 | Read a string of data from the ELF file. 831 | 832 | @offset - Seek to this file offset before reading. 833 | @size - Number of bytes to read. 834 | If None, this will read up to, but not including, the first NULL byte. 835 | 836 | Returns the data read from the file. 837 | ''' 838 | # Read in blocks of 1024. Better for disk I/O than doing one byte at a time. 839 | block_size = 1024 840 | i = 0 841 | data = b"" 842 | 843 | if size is None: 844 | while True: 845 | chunk = self.read(offset+i, block_size) 846 | if b"\x00" in chunk: 847 | data += chunk.split(b"\x00")[0] 848 | break 849 | elif not chunk: 850 | break 851 | else: 852 | data += chunk 853 | i += block_size 854 | else: 855 | data = self.read(offset, size) 856 | 857 | return data 858 | 859 | @property 860 | def endianess(self): 861 | # Before doing anything else, we need to know what endianess the target is 862 | if self.ELFDATA2MSB == self.header.e_ident.ei_encoding: 863 | return ">" 864 | else: 865 | return "<" 866 | @endianess.setter 867 | def endianess(self, value): 868 | # This really is for internal use to interface with the struct module. 869 | # You shouldn't be setting it! If you want to modify the ELF file's 870 | # endianess flag, access it via self.header.e_ident.ei_encoding. 871 | pass 872 | 873 | def read_byte(self, offset): 874 | return ord(self.read(offset, 1)) 875 | def write_byte(self, offset, value): 876 | self.write(offset, chr(value)) 877 | 878 | def read_half(self, offset): 879 | return struct.unpack("%sH" % self.endianess, self.read(offset, 2))[0] 880 | def write_half(self, offset, value): 881 | self.write(offset, struct.pack("%sH" % self.endianess, value)) 882 | 883 | def read_word(self, offset): 884 | return struct.unpack("%sL" % self.endianess, self.read(offset, 4))[0] 885 | def write_word(self, offset, value): 886 | self.write(offset, struct.pack("%sL" % self.endianess, value)) 887 | 888 | def read_double(self, offset): 889 | return struct.unpack("%sq" % self.endianess, self.read(offset, 8))[0] 890 | def write_double(self, offset, value): 891 | self.write(offset, struct.pack("%sq" % self.endianess, value)) 892 | 893 | def read_address(self, offset): 894 | if self.ELFCLASS64 == self.header.e_ident.ei_class: 895 | return self.read_double(offset) 896 | else: 897 | return self.read_word(offset) 898 | def write_address(self, offset, value): 899 | if self.ELFCLASS64 == self.header.e_ident.ei_class: 900 | return self.write_double(offset, value) 901 | else: 902 | return self.write_word(offset, value) 903 | 904 | 905 | 906 | 907 | -------------------------------------------------------------------------------- /src/botox/exceptions.py: -------------------------------------------------------------------------------- 1 | class BotoxException(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /src/scripts/botox: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | from botox import Botox, BotoxException 4 | 5 | try: 6 | elf_file = sys.argv[1] 7 | except IndexError as e: 8 | sys.stderr.write("Usage: %s \n" % sys.argv[0]) 9 | sys.exit(1) 10 | 11 | yn = input("WARNING: This will permanently modify %s without creating a backup. Continue? [y/N] " % elf_file) 12 | if not yn.lower().startswith('y'): 13 | print("Quitting...") 14 | sys.exit(1) 15 | 16 | try: 17 | new_entry_point = Botox(elf_file).patch() 18 | print("Patched file %s. New entry point is: 0x%.8X" % (elf_file, new_entry_point)) 19 | sys.exit(0) 20 | except BotoxException as e: 21 | sys.stderr.write(str(e) + "\n") 22 | sys.exit(2) 23 | --------------------------------------------------------------------------------