#@category CodeCut #@runtime PyGhidra # ## Copyright 2022 The Johns Hopkins University Applied Physics Laboratory LLC ## (JHU/APL). All Rights Reserved. # ## This material may be only be used, modified, or reproduced by or for ## the U.S. Government pursuant to the license rights granted under the ## clauses at DFARS 252.227-7013/7014 or FAR 52.227-14. For any other ## permission, please contact the Office of Technology Transfer at ## JHU/APL. # ## NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED "AS IS." JHU/APL ## MAKES NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF ## THE MATERIALS, INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL ## VIABILITY, AND DISCLAIMS ALL WARRANTIES IN THE MATERIAL, WHETHER ## EXPRESS OR IMPLIED, INCLUDING (BUT NOT LIMITED TO) ANY AND ALL IMPLIED ## WARRANTIES OF PERFORMANCE, MERCHANTABILITY, FITNESS FOR A PARTICULAR ## PURPOSE, AND NON-INFRINGEMENT OF INTELLECTUAL PROPERTY OR OTHER THIRD ## PARTY RIGHTS. ANY USER OF THE MATERIAL ASSUMES THE ENTIRE RISK AND ## LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL JHU/APL BE LIABLE ## TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT, CONSEQUENTIAL, ## SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO USE, ## THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST ## PROFITS. ## HAVE A NICE DAY. ## ## This script takes the boundaries found by deepcut and outputs an object file for the module selected by the user ## ##@author ##@keybinding ##@menupath ##@toolbar #@author #@category MINDSIGHT #@keybinding #@menupath #@toolbar import sys, copy, os import ghidra.app.script.GhidraScript from ghidra.program.database.module import * from ghidra.program.flatapi import * from ghidra.app.services import * from ghidra.program.model.mem import * from ghidra.program.model.lang import * from ghidra.program.model.pcode import * from ghidra.program.model.util import * from ghidra.program.model.reloc import * from ghidra.program.model.data import * from ghidra.program.model.block import * from ghidra.program.model.symbol import * from ghidra.program.model.scalar import * from ghidra.program.model.listing import * from ghidra.program.model.address import * from ghidra.program.util import ProgramLocation from ghidra.program.database.mem.FileBytes import * from ghidra.app.util import * import ghidra.app.util.bin.ByteProvider import ghidra.util.exception.CancelledException import ghidra.util.task.TaskMonitor # Need this line so we can use pyelftools script_path = parseFile(getSourceFile().getCanonicalPath()).getPath() pyelftools_path = os.path.join(os.path.dirname(script_path), "pyelftools-0.28") sys.path.append(pyelftools_path) # Pyelf imports from elftools.elf.elffile import ELFFile from elftools.elf.enums import * DEBUG = True # Will hold the symbol table symtab = None # Will hold the string table # The strtab and shstrtab being with \x00 strtab = [] # Will hold the section header string table shstrtab = '\x00' # Will hold all sections sections = [] NUM_SECTIONS = 0 # Total number of sections # Number of relocations NUM_RELOCS = 0 # Size of relocation fields RELOC_SIZE = 8 # relocations contains a nested dictionary containing details about the functions in the module # It contains the name of all functions it called as well as the address those functions were called relocations = {} # Dictionary of all symbols in the module # mapping is LOCAL: [list of local symbols], GLOBAL: [list of global symbols] symbols = {} symbols['LOCAL'] = [] symbols['GLOBAL'] = [] data_symbols = [] # These will take up an entry in the symbol table section_symbols = ['null', '.text', '.rodata', '.data', '.bss'] required_sections = ['.text', '.rel.text', '.data', '.bss', '.symtab', '.strtab', '.shstrtab'] # Total number of symbols total_symbols = len(section_symbols) # The number of entries in the rodata section rodata_entries = 0 bss_fragment = None data_fragment = None rodata_fragment = None """ Symbol Table Entry typedef struct { Elf32_Word st_name; (4 bytes) Elf32_Addr st_value; (4 bytes) Elf32_Word st_size; (4 bytes) unsigned char st_info; (1 byte) unsigned char st_other; (1 byte) Elf32_Half st_shndx; (2 bytes) } Elf32_Sym; (16 bytes total) """ SYM_SIZE = 16 # Size of symbols in symtab section """ Elf Header typedef struct { unsigned char e_ident[EI_NIDENT]; (16 bytes) Elf32_Half e_type; (2 bytes) Elf32_Half e_machine; (2 bytes) Elf32_Word e_version; (4 bytes) Elf32_Addr e_entry; (4 bytes) Elf32_Off e_phoff; (4 bytes) Elf32_Off e_shoff; (4 bytes) Elf32_Word e_flags; (4 bytes) Elf32_Half e_ehsize; (2 bytes) Elf32_Half e_phentsize; (2 bytes) Elf32_Half e_phnum; (2 bytes) Elf32_Half e_shentsize; (2 bytes) Elf32_Half e_shnum; (2 bytes) Elf32_Half e_shstrndx; (2 bytes) } Elf32_Ehdr; (52 bytes total) """ ELFHDRSZ = 52 # size of the ELF header """ typedef struct { Elf32_Word sh_name; (4 bytes) Elf32_Word sh_type; (4 bytes) Elf32_Word sh_flags; (4 bytes) Elf32_Addr sh_addr; (4 bytes) Elf32_Off sh_offset; (4 bytes) Elf32_Word sh_size; (4 bytes) Elf32_Word sh_link; (4 bytes) Elf32_Word sh_info; (4 bytes) Elf32_Word sh_addralign; (4 bytes) Elf32_Word sh_entsize; (4 bytes) } Elf32_Shdr; (40 bytes total) """ SHSIZE = 40 # size of section headers OFFSET = 0 # Offset into the file moduleName = '' elffile = None minModuleAddress = None maxModuleAddress = None moduleBytes = bytearray() rodata_bytes = bytearray() mod = None alignment = 4 # These sections are specific to an executable binary. Relocatable object files don't need these exclude_sections = ['.init', '.fini', '.eh_frame', '.stab', '.debug', '.noinit', '.exidx', '.got', '__', '.rel.ro', 'plt', 'dyn'] def SelectModule(module=""): global moduleName, minModuleAddress, maxModuleAddress, prog, mod, ns # Check if there is a program open if (currentProgram == None): popup("There is no open program") return False # Get Program prog = currentProgram # Get the symbol table st = prog.getSymbolTable() SymbolIterator = st.getAllSymbols(True) if module == "": Modulelist = {} # This loop attempts to get all modules (namespaces) within this program while(SymbolIterator.hasNext()): sym = SymbolIterator.next() ns = sym.getParentNamespace() # Want to get any namespace that isn't global or external # These are likely namespaces that were created by deepcut if (ns.getName() == u"Global") or (ns.getName() == u""): continue if (ns not in Modulelist): Modulelist[ns.getName()] = ns # There weren't any namespaces found if not Modulelist: popup("There doesn't seem to be any modules created by DeepCut (Namespaces is empty?).\nPlease run the DeepCut Analyzer before running this script.\nFor additional fidelity run GuessModuleNames from the CodeCut GUI.") return False else: # Ask user for module to export as object file Modulechoice = askChoice("Select a Module", getCategory(), Modulelist.keys(), None); print(Modulechoice + " was chosen!\n") moduleName = Modulechoice # Get min and max address of selected namespace # getBody() returns an AddressSetView which can be used to get the min and max address of the namespace mod = Modulelist[Modulechoice] asv = mod.getBody() else: moduleName = module mod = st.getNamespace(module, prog.getGlobalNamespace()) if mod: asv = mod.getBody() println("Module: %s" % mod.getName()) else: popup("Something went wrong. %s doesn't seem to be a valid module..." % mod.getName()) return False # Get the minimum and maximum address for the module minModuleAddress = asv.getMinAddress() maxModuleAddress = asv.getMaxAddress() # This module is not valid if minModuleAddress == None or maxModuleAddress == None or (minModuleAddress == maxModuleAddress): popup("The module you selected is invalid. This could be the min and/or max Address do not exist or the min and max address of the module are equal to each other. Please select another module") return False else: newMax = maxModuleAddress # It's possible that the max module address does not include the # entirety of the module. This is meant to adjust the max boundary # so every byte within the module is accounted for. while not getFunctionAt(newMax.add(1)): maxModuleAddress = maxModuleAddress.add(1) newMax = maxModuleAddress println("Min module address: %s" % (str(minModuleAddress))) println("Max module address; %s" % (str(maxModuleAddress))) return True def get_symbols(): global minModuleAddress, maxModuleAddress, mod, symbols, bss_fragment, data_fragment, rodata_fragment, rodata_entries # Get the symbol table st = prog.getSymbolTable() listing = prog.getListing() bss_fragment = listing.getFragment("Program Tree", ".bss") data_fragment = listing.getFragment("Program Tree", ".data") rodata_fragment = listing.getFragment("Program Tree", ".rodata") definedData = listing.getDefinedData(rodata_fragment.getMinAddress(), True) # Grab all symbols SymbolIterator = st.getAllSymbols(True) # This loop grabs all symbols referenced by the module, both internal symbols and external symbols # External symbols are symbols that are referenced by an internal function but the address is # outside the boundary of the module. For example, if the min and max address is 800000-900000, # at some point a function within the module referenced a symbol whose address is greater than 900000 # or less than 800000. Internal symbols are those whose address fall within the boundary of the # min and max address. while SymbolIterator.hasNext(): sym = SymbolIterator.next() # TODO: Make this more elegant if "_init" in sym.getName() or "_fini" in sym.getName() or "_start" in sym.getName(): continue # We only want ones that were imported (symbols in the .bss_fragment section seem to land in this category) if (sym.getSource().getDisplayString() == 'Imported') or (sym.getSource().getDisplayString() == 'User Defined'): # Check if symbol has references if sym.hasMultipleReferences() or sym.hasReferences(): symRef = sym.getReferences() for ref in symRef: # Are those references in the module? if isInternalSymbol(ref): # Is this symbol in the data or bss section? If yes it's a local symbol if (bss_fragment.contains(sym.getAddress()) or data_fragment.contains(sym.getAddress())) and \ sym not in symbols['LOCAL']: println("Adding symbol: %s Address: %s Reference: %s" % (sym.getName(), sym.getAddress(), ref.getFromAddress())) symbols['LOCAL'].append(sym) if sym.getSymbolType().toString() == 'Function' and \ sym not in symbols['GLOBAL']: symbols['GLOBAL'].append(sym) # The symbol is in the bss or data section and didn't have any references but it's address is within the module boundaries if (bss_fragment.contains(sym.getAddress()) or data_fragment.contains(sym.getAddress())) and \ sym not in symbols['LOCAL'] and \ (isInternalSymbol(sym)): println("Adding symbol: %s Address: %s" % (sym.getName(), sym.getAddress())) symbols['LOCAL'].append(sym) # The symbol didn't have any references if sym not in symbols['GLOBAL']: if isInternalSymbol(sym): symbols['GLOBAL'].append(sym) else: continue # Defined strings need to be relocated but they aren't necessarily symbols # We add them to our symbol dictionary here but we will need to subtract the number of symbols we add # here to the total number of symbols. Additionally, when writing the symtab section # we'll need to ensure we don't write these symbols. for dat in definedData: refs = dat.getReferenceIteratorTo() if rodata_fragment.contains(dat.getAddress()): for ref in refs: if isInternalSymbol(ref): symbols['LOCAL'].append(dat) rodata_entries += 1 break else: continue print("Global and local symbols: ", symbols) maxModAddress = update_functions() # Updating maxModuleAddress just in case it changed in update_functions if maxModAddress.subtract(maxModuleAddress) > 0: maxModuleAddress = maxModAddress println("Minimum Module Address: %s" % minModuleAddress) println("Maximum Module Address: %s" % maxModuleAddress) """ This function checks if the symbol is internal to the current module @param symbol: The symbol we want to determine is internal or external @return: True if the symbol is internal; false otherwise """ def isInternalSymbol(symbol): # Check if reference is within module if "reference" in str(type(symbol)).lower(): if symbol.getFromAddress().compareTo(minModuleAddress) >= 0 and \ symbol.getFromAddress().compareTo(maxModuleAddress) <= 0: return True else: return False # Check if symbol is within module elif symbol.getAddress().compareTo(minModuleAddress) >= 0 and \ symbol.getAddress().compareTo(maxModuleAddress) <= 0: return True else: return False def update_functions(): # This is the current upward bounds of the module # It's possible this will not change so we save it here newMaxAddress = maxModuleAddress for sym in symbols['GLOBAL']: if isInternalSymbol(sym): if sym.getSymbolType().toString() == 'Function': println("Symbol name: %s\n" % sym.getName()) func = getFunctionAt(sym.getAddress()) funcBody = func.getBody() maxAddr = funcBody.getMaxAddress() println("Max address: %s" % (str(maxAddr))) newMax = maxAddr # Currently, Ghidra does not include all bytes for function that include data elements # We're accounting for this by checking if the next byte is a new function # If it is a new function then Ghidra already has the proper function size. If not, # than Ghidra will misrepresent the size of the function leading to incorrect processing # later on. while not getFunctionAt(newMax.add(1)): maxAddr = maxAddr.add(1) newMax = maxAddr println("newMax: %s" % (str(newMax))) # The previos loop resulted in a new maximum address for the current function # Let's update the function to include the new bytes if newMax.subtract(funcBody.getMaxAddress()) > 0: println("Need to update the address set for this function\n") # Build an address set using the entry point for the function # and the new max address newAddrSet = AddressSet(sym.getAddress(), newMax) # Modify the body to include the new address set func.setBody(newAddrSet) # println("newMax - newMaxAddress: %d" % newMax.subtract(newMaxAddress)) # This check probably isn't completely necessary. Before entering this function we already # modified the boundary for the module. However, there is still a chance that # there are some new bytes not accounted for. We check if the new address is greater # than the address we already have for the module boundary. If it is greater than we # update the maximum address, otherwise we continue on if newMax.subtract(newMaxAddress) > 0: println("Updating module boundary!") newMaxAddress = newMax else: continue else: println("This function is fine") return newMaxAddress """ Taken from https://stackoverflow.com/a/20793663 Python 2.7 does not have a to_bytes method for integers :( @param n: The number to convert to bytes @param length: How many bits should be used to represent the integer @param endianess: What endianess should be used? Supports little and big endian """ def to_bytes(n, length, endianess='little'): h = '%x' % n s = '{:0{}x}'.format(n, length*2).decode('hex') return s if endianess == 'big' else s[::-1] """ This function writes the ELF header to a file object. A lot of the entries in the ELF header are unchanged or only slightly modified. Each entry is commented out and any changes are called out with a reason for the change @param obj_file: The new object file being created. @param elffile: The elffile object of the current ELF file being analyzed. @var ENUM: The ENUM_X_YYYY are enumerations used by the pyelftools library. """ def write_elf_header(obj_file): println("Writing the ELF Header!") # Write the raw e_ident field to the file # This should not change in any considerable way so just copy the field # from the original ELF executable obj_file.write(elffile.e_ident_raw) # The next byte is going to determine the type of file (i.e. executable or relocatable) # The pyelftools library enumerates lots of useful ELF values obj_file.write(to_bytes(ENUM_E_TYPE['ET_REL'], 2, 'little')) # Now let's write the machine type obj_file.write(to_bytes(ENUM_E_MACHINE[elffile.header['e_machine']], 2, 'little')) # Write the version obj_file.write(to_bytes(ENUM_E_VERSION['EV_CURRENT'], 4, 'little')) # Write the entry point # This will be zero since it's an object file e_entry = 0 obj_file.write(to_bytes(e_entry, 4, 'little')) # Write start of program headers: # This will be zero since an object file does not have program headers e_phoff = 0 obj_file.write(to_bytes(e_phoff, 4, 'little')) # Start of section headers # This member holds the section header table's file offset in bytes # For simplicity, I place the section header table immediately after the # end of the last section written e_shoff = OFFSET println("Section Header Offset: %d" % e_shoff) obj_file.write(to_bytes(e_shoff, 4, 'little')) # Write flags obj_file.write(to_bytes(elffile.header['e_flags'], 4, 'little')) # Write Header Size # The header size should always be the same obj_file.write(to_bytes(elffile.header['e_ehsize'], 2, 'little')) # Write program header size e_phentsize = 0 # No program headers in a relocatable object file obj_file.write(to_bytes(e_phentsize, 2, 'little')) # Write Number of program headers e_phnum = 0 obj_file.write(to_bytes(e_phnum, 2, 'little')) # Write size of section headers obj_file.write(to_bytes(elffile.header['e_shentsize'], 2, 'little')) # Write number of section headers e_shnum = NUM_SECTIONS println("Number of section: %d" % NUM_SECTIONS) obj_file.write(to_bytes(e_shnum, 2, 'little')) # Write Section header string table index e_shstrndx = [i for i,v in enumerate(sections) if v.name == '.shstrtab'][0] obj_file.write(to_bytes(e_shstrndx, 2, 'little')) """ This function writes the section header data to the newly created object file It takes a section that may have been modified. @param obj_file: A file descriptor representing the new object file being created @param section: A section in the ELF file """ def write_section_headers(obj_file, section): println("Writing section header: %s" % section.name) # Write sh_name obj_file.write(to_bytes(section.header['sh_name'], 4, 'little')) # Write sh_type obj_file.write(to_bytes(ENUM_SH_TYPE_ARM[section.header['sh_type']], 4, 'little')) # Write sh_flags obj_file.write(to_bytes(section.header['sh_flags'], 4, 'little')) # Write sh_addr obj_file.write(to_bytes(section.header['sh_addr'], 4, 'little')) # Write sh_offset obj_file.write(to_bytes(section.header['sh_offset'], 4, 'little')) # Write sh_size obj_file.write(to_bytes(section.header['sh_size'], 4, 'little')) # Write sh_link obj_file.write(to_bytes(section.header['sh_link'], 4, 'little')) # Write sh_info obj_file.write(to_bytes(section.header['sh_info'], 4, 'little')) # Write sh_addralign obj_file.write(to_bytes(section.header['sh_addralign'], 4, 'little')) # Write sh_entsize obj_file.write(to_bytes(section.header['sh_entsize'], 4, 'little')) """ This function gets the size of the data, rodata, or bss sections. Since these sections size depend on the presence of variables or defined strings, it's possible the size will be either zero or the sum of all data that resides in their repsective sections @param section: The section we want to calculate the size @return: The size of the section or zero if it has no size """ def get_section_size(section): global rodata_bytes if section == '.bss': bss_section_size = 0 for sym in symbols['LOCAL']: if bss_fragment.contains(sym.getAddress()): dat = getDataAt(sym.getAddress()) bss_section_size += dat.getLength() println("bss_section_size: %d" % bss_section_size) return bss_section_size elif section == '.data': data_section_size = 0 for sym in symbols['LOCAL']: if data_fragment.contains(sym.getAddress()): println("Name: %s" % sym) dat = getDataAt(sym.getAddress()) if dat.getLength() % alignment != 0: diff = (alignment - (dat.getLength() % alignment)) size = diff + dat.getLength() else: size = dat.getLength() data_section_size += size println("data_section_size: %d" % data_section_size) return data_section_size else: return 0 """ This function performs any necessary modifications to the section headers. Only the .text, .data, .rodata, .bss, .strtab, .shstrtab, and .symtab require specific modifications. This is because these sections are likely to change from the executable. For example, the .text section is going to be MUCH smaller in the unlinked object file compared to the full executable file because there's far less code. This is similar for the other sections. The other section headers, only the sh_offset and sh_name field will be changed since we copy the data from those sections directly to our new unlinked object file. Here is a description of the fields and how they will be modified and how they are created: sh_name This field is an index into the string table section['sh_name'] = strtab.find(section.name) sh_size This is the size of the section. For the .text section it's simply the size of the moduleBytes variable. For .data and .bss the size is a sum of the size of all variables in the respective section. For example, if the .data section has two integers and a char, the size of this section would be 9 (sizeof(int) + sizeof(int) + sizeof(char)) The .rodata section is simply the length of all strings in the section. For example, if the .rodata section contained only one string ("I am in the .rodata section"), the size would be len("I am in the .rodata section") Currently, the size for these sections are set to zero, but an update will come to provide accurate sizes if necessary. The .rel.text section's size is (number of relocations * size of each relocation). In our case the size of each relocation is 8 bytes. Similarly, .symtab section's size is the (number of symbols * symbol size), where the size of each symbol is 16 bytes. sh_offset This is the offset of where the section data is in the file. For example, if the data for .rel.text section begins 128 bytes into the file, this field will be hex(128) sh_info The sh_info field for .rel.text holds the index for the .text section. For the .symtab section, this field holds the symbol index for the first non-local symbol. In our unlinked object file, the index should always going to be one. Therefore, it's hard coded. In the future I'd like to avoid hardcoding this value. For the .rel.text section, I update this field using the following statement: [j for j,v in enumerate(sections) if v.name == '.text'][0] The sections variable is a list that contains pyelftools section objects. This would grab the index where the '.text' section resides. sh_link This field is only relevant for the .rel.text section and the .symtab section. For the .rel.text section, this field represents the index of the symbol table section. Given that the .rel.text section handles relocations, it makes sense why this section would need a link to the symbol table. For the .symtab section, this field represents the index for the string table. Again, because symbol's in the .symtab section don't contain names but indices into the .strtab section, it makes sense why this section needs a link to the string table section. This field is updated using a similar statement to the sh_info field. For .rel.text we use: [j for j,v in enumerate(sections) if v.name == '.symtab'][0] + 1 We add one here because we're assuming the executable does not have a .rel.text section and it needs to be added. Because we're adding a section, the index of the .symtab section will be incremented by one. For .symtab we use [j for j,v in enumerate(sections) if v.name == '.strtab'][0] + 1 sh_addr This is the address at which the section's first byte should reside in memory. Since this is a relocatable object file, this field can safely be hardcoded to zero. """ def modify_section_headers(): global OFFSET, section_symbols, rodata_entries # The offset will be accumulated # The initial offset for the sections will be the size of the # ELF Header because that's where we'll begin writing section our data OFFSET = ELFHDRSZ if DEBUG: println("----------------------------- Modifying section headers --------------------------------") for i, section in enumerate(sections): if DEBUG: println("Number of section: %d" % len(sections)) println("Section name: %s OFFSET: %d" % (section.name, OFFSET)) println("Index: %d section name: %s" % (i, sections[i].name)) if section.name == '.text': sections[i].header['sh_name'] = shstrtab.find(section.name) sections[i].header['sh_size'] = len(moduleBytes) sections[i].header['sh_addr'] = 0 # All section headers will have addr set to zero sections[i].header['sh_offset'] = OFFSET sections[i].header['sh_addralign'] = 4 OFFSET += sections[i].header['sh_size'] # Update offset for next section header if DEBUG: println("%s size: %d Section Offset: %d" % (section.name, sections[i].header['sh_size'], sections[i].header['sh_offset'])) println("OFFSET: %d" % OFFSET) elif section.name == '.rel.text': section.header['sh_offset'] = OFFSET section.header['sh_name'] = shstrtab.find('.rel.text') section.header['sh_type'] = 'SHT_REL' section.header['sh_addr'] = 0 section.header['sh_flags'] = 0x40 section.header['sh_size'] = NUM_RELOCS * RELOC_SIZE section.header['sh_entsize'] = 0x8 section.header['sh_link'] = [i for i,v in enumerate(sections) if v.name == '.symtab'][0] section.header['sh_info'] = [i for i,v in enumerate(sections) if v.name == '.text'][0] section.header['sh_addralign'] = 4 OFFSET += section.header['sh_size'] if DEBUG: println("%s size: %s Section Offset: %d Num Relocaions: %d" % (section.name, section.header['sh_size'], section.header['sh_offset'], NUM_RELOCS)) println("OFFSET: %d" % OFFSET) elif section.name == '.symtab': sections[i].header['sh_name'] = shstrtab.find(section.name) sections[i].header['sh_size'] = total_symbols * SYM_SIZE sections[i].header['sh_addr'] = 0 # The sh_info section points to the first non local symbol in the symbol table. # In order to get this we take the section symbols (which are local) and all # local symbols we found earlier and subtract that from the rodata entries that may be # present. sections[i].header['sh_info'] = len(section_symbols) + (len(symbols['LOCAL']) - rodata_entries) sections[i].header['sh_link'] = [j for j,v in enumerate(sections) if v.name == '.strtab'][0] sections[i].header['sh_offset'] = OFFSET sections[i].header['sh_addralign'] = 4 OFFSET += sections[i].header['sh_size'] # Update offset for next section header if DEBUG: println("%s size: %d Section Offset: %d Total Symbols: %d" % (section.name, sections[i].header['sh_size'], sections[i].header['sh_offset'], total_symbols)) println("OFFSET: %d" % OFFSET) elif section.name == '.strtab': sections[i].header['sh_name'] = shstrtab.find(section.name) sections[i].header['sh_size'] = len(strtab) sections[i].header['sh_offset'] = OFFSET sections[i].header['sh_addr'] = 0 sections[i].header['sh_addralign'] = 1 OFFSET += sections[i].header['sh_size'] if DEBUG: println("%s size: %d Section Offset: %d" % (section.name, sections[i].header['sh_size'], sections[i].header['sh_offset'])) println("OFFSET: %d" % OFFSET) elif section.name == '.shstrtab': sections[i].header['sh_name'] = shstrtab.find(section.name) sections[i].header['sh_size'] = len(shstrtab) sections[i].header['sh_offset'] = OFFSET sections[i].header['sh_addr'] = 0 sections[i].header['sh_addralign'] = 1 OFFSET += sections[i].header['sh_size'] if DEBUG: println("shstrtab: %s length: %d" % (shstrtab, len(shstrtab))) println("section %s size: %d Section Offset: %d" % (section.name, sections[i].header['sh_size'], sections[i].header['sh_offset'])) println("OFFSET: %d" % OFFSET) elif section.name == '.rodata': sections[i].header['sh_name'] = shstrtab.find(section.name) sections[i].header['sh_size'] = len(rodata_bytes) # Size will be sum of length all strings in rodata section sections[i].header['sh_offset'] = OFFSET sections[i].header['sh_addr'] = 0 sections[i].header['sh_addralign'] = 4 OFFSET += sections[i].header['sh_size'] if DEBUG: println("rodata: %s length: %d" % (rodata_bytes, len(rodata_bytes))) println("%s size: %d Section Offset: %d" % (section.name, sections[i].header['sh_size'], sections[i].header['sh_offset'])) println("OFFSET: %d" % OFFSET) elif section.name == '.data': sections[i].header['sh_name'] = shstrtab.find(section.name) sections[i].header['sh_size'] = get_section_size(section.name) # Size will be sum of all data element's size (i.e. sizeof (int) + sizeof(char) + sizeof(int), assuming there are two integers and a char in the .data section) sections[i].header['sh_offset'] = OFFSET sections[i].header['sh_addr'] = 0 sections[i].header['sh_addralign'] = 4 OFFSET += sections[i].header['sh_size'] if DEBUG: println("%s size: %d Section Offset: %d" % (section.name, sections[i].header['sh_size'], sections[i].header['sh_offset'])) println("OFFSET: %d" % OFFSET) elif section.name == '.bss': sections[i].header['sh_name'] = shstrtab.find(section.name) sections[i].header['sh_size'] = get_section_size(section.name) # Size will be sum of all data element's size (i.e. sizeof (int) + sizeof(char) + sizeof(int), assuming there are two integers and a char in the .data section) sections[i].header['sh_offset'] = OFFSET sections[i].header['sh_addr'] = 0 sections[i].header['sh_addralign'] = 1 if DEBUG: println("%s size: %d Section Offset: %d" % (section.name, sections[i].header['sh_size'], sections[i].header['sh_offset'])) println("OFFSET: %d" % OFFSET) # The bss section doesn't actually take up any space in the ELF file, so we don't need to update the # OFFSET with the size of this section. else: if DEBUG: println("Section name in modifying headers: %s" % section.name) println("Section size: %d" % section.header['sh_size']) sections[i].header['sh_name'] = shstrtab.find(section.name) sections[i].header['sh_offset'] = 0 if section.name == '' else OFFSET sections[i].header['sh_addr'] = 0 OFFSET += sections[i].header['sh_size'] if DEBUG: println("%s size: %d Section Offset: %d" % (section.name, sections[i].header['sh_size'], sections[i].header['sh_offset'])) println("OFFSET: %d" % OFFSET) if DEBUG: println("----------------------------- Done modifying section headers --------------------------------") """ This function performs the first pass to gather the sections that will be modified and exported. Loop through each section and append it to a list of sections if the section name is not equal to: .init* .fini* .eh_frame* .stab* .debug* .noinit* These sections aren't necessary for an object file so they're ignored This function also grabs the symbol table, the section header string table, and the string table @param elffile: The ELFfile object """ def first_pass(): global sections, NUM_SECTIONS, shstrtab, elffile # Open the program file # In Python getProgramFile() returns a java.io.File object :/ # so we need to add getPath() to get the full path including the filename try: f = open(getProgramFile().getPath(), 'rb') except AttributeError as e: selectedFile = askFile("Select Executable File", "Open") println("Path: %s" % selectedFile) f = open(selectedFile.getPath(), 'rb') # Create ELF object elffile = ELFFile(f) # Holds the element the relocation resides in the sections list # If a relocation section is found (i.e. .plt* or .rel*) the # RELOC_ELEM global variable will be set to the value of reloc_elem # Otherwise, RELOC_ELEM remains negative one which is an indicator # the ELF file has no relocation section available and one must # be created reloc_elem = 0 # Get all sections in ELF file excluding those listed in this functions's description section_names = '' for sec in elffile.iter_sections(): reloc_elem += 1 res = [ele for ele in exclude_sections if (ele in sec.name)] if bool(res): continue # Build section header string table section excluding the NULL Section if not sec.name == '': shstrtab += sec.name + '\x00' sections.append(sec) # Some required sections might not be in the binary. Let's check for those and add them for missing_section in required_sections: if missing_section not in shstrtab: println("We're missing a section %s! Adding it in" % missing_section) println("s: %s" % missing_section) if missing_section == '.rel.text': shstrtab = shstrtab.replace('.text', '.rel.text') else: # Adding missing section to the section header string table shstrtab += missing_section + '\x00' # Copying the .text section to modify it to conform to a .rel.text section sec = copy.deepcopy(sections[[i for i,v in enumerate(sections) if v.name == '.text'][0]]) # Insert this section into the list of sections sec.name = missing_section sections.insert([i for i,v in enumerate(sections) if v.name == '.text'][0] + 1, sec) NUM_SECTIONS = len(sections) # Adding one because we're adding a relocation section if DEBUG: println("Number of sections: %d" % NUM_SECTIONS) print("SHSTRTAB: ", shstrtab) """ This function writes the data of a section to the file. @param obj_file: A file descriptor representing the object file being created @param moduleBytes: The bytes in the module. This is retrieved in the output_obj_file function @param section: The section being written """ def write_section(obj_file, section): global section_symbols if DEBUG: println("Writing section: %s!" % section.name) if section.name == '.text': obj_file.write(moduleBytes) elif section.name == '.rel.text': # print(relocations) # print(sorted(relocations)) for rel in relocations.keys(): # Write offset obj_file.write(to_bytes(int(relocations[rel]['offset'], 16), 4, 'little')) # Write info obj_file.write(to_bytes(relocations[rel]['info'], 4, 'little')) elif section.name == '.symtab': # null, .text, .data, .rodata, and .bss should all have entries in the symbol table st_name = 0 st_value = 0 st_size = 0 st_info = 0 st_other = 0 st_shndx = 0 for l in section_symbols: if l == 'null': # Write the NULL symbol if DEBUG: println("Writing the null section symbol") obj_file.write(to_bytes(st_name, 4, 'little')) obj_file.write(to_bytes(st_value, 4, 'little')) obj_file.write(to_bytes(st_size, 4, 'little')) obj_file.write(to_bytes(st_info, 1, 'little')) obj_file.write(to_bytes(st_other, 1, 'little')) obj_file.write(to_bytes(st_shndx, 2, 'little')) else: if DEBUG: println("Writing the %s section symbol" % l) # Write the symbol for a section bind = ENUM_ST_INFO_BIND['STB_LOCAL'] info_type = ENUM_ST_INFO_TYPE['STT_SECTION'] # The calculation for st_info was taken from https://refspecs.linuxfoundation.org/elf/elf.pdf st_info = (((bind)<<4) + ((info_type)&0xf)) st_shndx = [i for i,v in enumerate(sections) if v.name == l][0] obj_file.write(to_bytes(st_name, 4, 'little')) obj_file.write(to_bytes(st_value, 4, 'little')) obj_file.write(to_bytes(st_size, 4, 'little')) obj_file.write(to_bytes(st_info, 1, 'little')) obj_file.write(to_bytes(st_other, 1, 'little')) obj_file.write(to_bytes(st_shndx, 2, 'little')) for s in symbols: if s == 'GLOBAL': st_value = 0 for sym in symbols[s]: if sym.name == '': continue # Write st_name st_name = strtab.find(sym.name + '\x00') obj_file.write(to_bytes(st_name, 4, 'little')) if isInternalSymbol(sym): st_value = write_section.offset bind = ENUM_ST_INFO_BIND['STB_GLOBAL'] info_type = ENUM_ST_INFO_TYPE['STT_FUNC'] # The calculation for st_info was taken from https://refspecs.linuxfoundation.org/elf/elf.pdf st_info = (((bind)<<4) + ((info_type)&0xf)) f = getFunctionAt(sym.getAddress()) body = f.getBody() st_size = len(getBytes(body.getMinAddress(), body.getMaxAddress().subtract(body.getMinAddress()) + 1)) st_shndx = [i for i,v in enumerate(sections) if v.name == '.text'][0] else: bind = ENUM_ST_INFO_BIND['STB_GLOBAL'] info_type = ENUM_ST_INFO_TYPE['STT_NOTYPE'] st_info = (((bind)<<4) + ((info_type)&0xf)) st_name = strtab.find(sym.name + '\x00') st_shndx = 0 st_size = 0 st_value = 0 # Write st_value # st_value is essentially an offset from the first symbol onward if DEBUG: println("Global symbol name: %s value: %d" % (sym.name, st_value)) obj_file.write(to_bytes(st_value, 4, 'little')) write_section.offset += st_size # Write st_size obj_file.write(to_bytes(st_size, 4, 'little')) # Write st_info obj_file.write(to_bytes(st_info, 1, 'little')) # Write st_other # currently holds 0 and has no defined meaning obj_file.write(to_bytes(st_other, 1, 'little')) # Write st_shndx # Which section is this symbol defined? obj_file.write(to_bytes(st_shndx, 2, 'little')) else: data_st_value = 0 bss_st_value = 0 for sym in symbols[s]: try: if sym.name == '': continue if DEBUG: println("Local Symbol name: %s" % sym.name) println("Local Symbol Address: %s" % str(sym.getAddress())) bind = ENUM_ST_INFO_BIND['STB_LOCAL'] if bss_fragment.contains(sym.getAddress()): info_type = ENUM_ST_INFO_TYPE['STT_OBJECT'] st_info = (((bind)<<4) + ((info_type)&0xf)) st_shndx = [i for i,v in enumerate(sections) if v.name == '.bss'][0] else: info_type = ENUM_ST_INFO_TYPE['STT_OBJECT'] st_info = (((bind)<<4) + ((info_type)&0xf)) st_shndx = [i for i,v in enumerate(sections) if v.name == '.data'][0] st_name = strtab.find(sym.name + '\x00') if DEBUG: println("st_name: %d" % st_name) println("st_shndx: %s" % st_shndx) dat = getDataAt(sym.getAddress()) st_size = dat.getLength() #write_section.offset += st_size except AttributeError as e: if DEBUG: println("This is not a symbol") continue # Write st_name obj_file.write(to_bytes(st_name, 4, 'little')) if bss_fragment.contains(sym.getAddress()): # Write st_value obj_file.write(to_bytes(bss_st_value, 4, 'little')) bss_st_value += st_size else: # Write st_value obj_file.write(to_bytes(data_st_value, 4, 'little')) data_st_value += st_size # print(sym.name, " st_value ", 0) # Write st_size obj_file.write(to_bytes(st_size, 4, 'little')) # Write st_info obj_file.write(to_bytes(st_info, 1, 'little')) # Write st_other # currently holds 0 and has no defined meaning obj_file.write(to_bytes(st_other, 1, 'little')) # Write st_shndx # Which section is this symbol defined? # Because this is an external symbol the section is undefined or 0 obj_file.write(to_bytes(st_shndx, 2, 'little')) elif section.name == '.strtab': obj_file.write(strtab) if DEBUG: println("Wrote strtab section %d bytes" % len(strtab)) elif section.name == '.shstrtab': obj_file.write(shstrtab) if DEBUG: println("Wrote shstrtab section %d bytes" % len(shstrtab)) elif section.name == '.rodata': if DEBUG: println("Writing %d bytes" % len(rodata_bytes)) obj_file.write(rodata_bytes) elif section.name == '.data': for sym in symbols['LOCAL']: if data_fragment.contains(sym.getAddress()): dat = getDataAt(sym.getAddress()) if dat.getLength() % alignment != 0: diff = (alignment - (dat.getLength() % alignment)) size = diff + dat.getLength() else: size = dat.getLength() if DEBUG: println("Symbol: %s" % sym.getName()) println("Symbol Address: %s" % sym.getAddress()) println("Data Address: %s" % dat.getAddress()) println("Writing %d bytes" % size) println("Data Type: %s" % dat.getDataType().getDisplayName()) println("TESTING: %s" % dat.getValue()) println("Data Bytes: %s" % getBytes(dat.getAddress(), dat.getLength())) #obj_file.write(to_bytes(dat.getValue().getValue(), size, 'little')) obj_file.write(getBytes(dat.getAddress(), size)) elif section.name == '.bss': # The .bss section doesn't actually take up any bytes in the object file so no need to write anything :) pass else: sec = elffile.get_section_by_name(section.name) obj_file.write(sec.data()) if DEBUG: println("Wrote %d amount" % len(sec.data())) """ This function is a wrapper that calls the other functions necessary for unlinking the object file. The function creates the name of the resulting unlinked object file and opens the current program executable so pyelftools can process it. Pyelftools is used to grab the sections in the executable so they can be modified by other functions. The actual data is modified and written by other helper functions. The object is created by first gathering all sections we'll want to modify and export, writing the ELF header, writing the individual sections, and finally, writing the section headers. This is a bit different than how a compiler would structure an object file, but the order does not matter so long as the linker knows where to find everything. In other words, we have to make sure the offsets in the ELF and section headers are accurate. Because of this constraint, it was easiest to create the file in this manner because each section being written can be used by a later portion being written. @param moduleBytes: Bytes of the module selected by the user """ def unlink_object_file(): # The resulting object file # TODO: Update to use Ghidra API to get filename # fname = moduleName + '_unlinked.o' # path = currentProgram.getExecutablePath() # fname = path[:path.rindex('/')+1] + fname fname = output_arg + '/' + moduleName + '_unlinked.o' println("File name: %s" % fname) # fname = fname.getPath() obj_file = open(fname, 'wb') # Modify the section headers modify_section_headers() # The section headers have been modified, let's write the ELF headers to a file write_elf_header(obj_file) # Let's now write the section for sec in sections: write_section(obj_file, sec) # Finally let's write the section headers for sec in sections: write_section_headers(obj_file, sec) # The unlinked object file is now complete, close the file obj_file.close() """ This function updates the module bytes at the given offset with the specific bytes """ def update_module_bytes(offset, newBytes): global moduleBytes moduleBytes[offset:offset+4] = newBytes """ This function grabs the text section for the module by getting the bytes between boundary1 and boundary2 """ def get_module_bytes(): global moduleBytes try: # Added one at the end because a byte was being cut off if I just subtracted maxModuleAddress from minModuleAddress # Would like to try and find a more elegant way but this works for now moduleBytes = getBytes(minModuleAddress, (maxModuleAddress.subtract(minModuleAddress) + 1)) except MemoryAccessException: # TODO Auto-generated catch block println("Problem getting bytes within module... ") e.printStackTrace() """ This function places all of the relocations in a relocation dictionary. Additionally, it modifies the original bytes such that they are consistent with what a normal compiler would do. For example, for external function calls, the compiler places the following bytes at the address where the external function was called: FF FF FE EB, where EB is the call instruction and the other three bytes are dummy values. The string table consists of all the symbol names (i.e. function/variable names). This "table" is constructed here and later used for writing the strtab section. """ def get_relocations(): global relocations, NUM_RELOCS, sections, strtab, moduleBytes, total_symbols, rodata_bytes bss_offset = 0 data_offset = 0 rodata_offset = 0 last_local_symbol = 0 newBytes = b'\xff\xff\xfe\xeb' boundary1, boundary2 = minModuleAddress, maxModuleAddress # This attempts to gather most relocations. # Iterate through the dictionary of symbols for key, lst in symbols.items(): # Iterate through each symbol for sym in lst: if DEBUG: println("-----------------------------------") if not rodata_fragment.contains(sym.getAddress()): strtab.append(sym.getName()) if DEBUG: println("Not a rodata symbol Symbol Name: %s" % sym.getName()) symbol_type = sym.getSymbolType().toString() # Does this symbol have any references if sym.hasReferences() or sym.hasMultipleReferences(): symRef = sym.getReferences() if DEBUG: println("Symbol has references...") # Iterate through each symbols references for ref in symRef: # println("Reference address: %s" % ref.getFromAddress()) if ref.getFromAddress().toString() == 'Entry Point': continue # Is the reference within the boundary? If so, a relocation should occur if isInternalSymbol(ref): if DEBUG: println("Reference is within module boundary") # Here, the offset is the offset the relocation should occur # That is, the address where the symbol is referenced minus # the lower bound of the module offset = ref.getFromAddress().subtract(boundary1) # key for relocation will be relocation key = "relocation" + str(NUM_RELOCS) # Create the info field in the relocation by appending a number # and a hexadecimal representation of the R_ARM_CALL enumerated data type # The info field in the relocation table is composed of the index into # the symbol table followed by the type of relocation (i.e. R_ARM_CALL) # The [2:] is necessary so we only grab the numbers and omit the '0x' # TODO: modify this so that it checks what type of symbol this is # The type of symbol will change the info field since this will be a # different type of relocation func_call = ENUM_RELOC_TYPE_ARM['R_ARM_CALL'] data_reloc = ENUM_RELOC_TYPE_ARM['R_ARM_ABS32'] if symbol_type == 'Function': if DEBUG: println("Symbol is a function") NUM_RELOCS += 1 if len(relocations.values()) >= 1: # If the symbol already exists set the info field to match the other relocation # entry for the same symbol for d in relocations.values(): if d['name'] == sym.getName(): info = d['info'] else: reloc_type = func_call t = hex(reloc_type) reloc_type = t[2:] if len(t) == 4 else '0' + hex(reloc_type)[2:] info = hex(total_symbols)[2:] + reloc_type info = int(info, 16) # Change into an integer so we can call to_bytes on it later else: reloc_type = func_call t = hex(reloc_type) reloc_type = t[2:] if len(t) == 4 else '0' + hex(reloc_type)[2:] info = hex(total_symbols)[2:] + reloc_type info = int(info, 16) # Change into an integer so we can call to_bytes on it later else: # The symbol is not a function if DEBUG: println("Symbol is not a function") if ref.getReferenceType().toString() != 'DATA': continue NUM_RELOCS += 1 dat = getDataAt(sym.getAddress()) if DEBUG: println("Symbol: %s Address: %s" % (sym.getName(), sym.getAddress())) println("Value: %s Length: %s" % (dat.getValue(), dat.getLength())) if len(relocations.values()) >= 1: # If the symbol already exists set the info field to match the other relocation # entry for the same symbol for d in relocations.values(): if d['name'] == sym.getName(): info = d['info'] else: reloc_type = data_reloc t = hex(reloc_type) reloc_type = t[2:] if len(t) == 4 else '0' + hex(reloc_type)[2:] if bss_fragment.contains(sym.getAddress()): info = str(4) else: info = str(3) info += reloc_type if DEBUG: println("Info bss or data: %s" % info) info = int(info, 16) # Change into an integer so we can call to_bytes on it later else: reloc_type = data_reloc t = hex(reloc_type) reloc_type = t[2:] if len(t) == 4 else '0' + hex(reloc_type)[2:] if bss_fragment.contains(sym.getAddress()): info = str(4) else: info = str(3) info += reloc_type if DEBUG: println("Info bss or data: %s" % info) info = int(info, 16) # Change into an integer so we can call to_bytes on it later relocations[key] = {'name': sym.getName(), 'offset': hex(offset).lstrip("0x").rstrip("L"), 'addr': ref.getFromAddress(), 'info': info} # Modify the bytes at the offset with the bytes \xfe\xff\xff\xeb if symbol_type == 'Function': #moduleBytes[offset:offset+4] = newBytes update_module_bytes(offset, newBytes) else: if bss_fragment.contains(sym.getAddress()): #moduleBytes[offset:offset+4] = to_bytes(bss_offset, 4, 'little') update_module_bytes(offset, to_bytes(bss_offset, 4, 'little')) bss_offset += 4 else: #moduleBytes[offset:offset+4] = to_bytes(data_offset, 4, 'little') update_module_bytes(offset, to_bytes(data_offset, 4, 'little')) data_offset += 4 total_symbols += 1 # Probably not best to handle this as an exception but this is the case where we have to perform # a relocation on the rodata section. These aren't symbols like the other elements in the symbols # dictionary so we have to treat them a little differently else: if DEBUG: println("This is in the rodata section") refs = sym.getReferenceIteratorTo() for ref in refs: # if ref.getFromAddress().compareTo(minModuleAddress) >= 0 and \ # ref.getFromAddress().compareTo(maxModuleAddress) <= 0: if isInternalSymbol(ref): if ref.getReferenceType().toString() != 'DATA': continue # This is a string that's too small to be recognized by Ghidra and the # analyst didn't manually type it as a string short_str = False if 'undefined' in sym.getDataType().getDisplayName(): short_str = True str_array = bytearray() str_array.append(getByte(sym.getAddress())) n = sym.getAddress().next() while getByte(n) != 0: str_array.append(getByte(n)) n = n.next() if DEBUG: println("str_array: %s" % str_array) rodata_bytes += str_array offset = ref.getFromAddress().subtract(boundary1) # The original bytes written here is the offset into the rodata section # The offset is updated later on update_module_bytes(offset, to_bytes(rodata_offset, 4, 'little')) info = str(2) # Would like to get away from hard coding this reloc_type = data_reloc t = hex(reloc_type) reloc_type = t[2:] if len(t) == 4 else '0' + hex(reloc_type)[2:] info += reloc_type info = int(info, 16) if short_str: key = "relocation" + str(NUM_RELOCS) NUM_RELOCS += 1 relocations[key] = {'name': str_array, 'offset': hex(offset).lstrip("0x").rstrip("L"), 'addr': ref.getFromAddress(), 'info': info} rodata_offset += len(str_array) else: # Using getBytes will allow us to get all the bytes from the start address to the end # We add one to the length to include the null byte at the end of the byte array. We # add 1 yet again because after each entry in the rodata section, there's a null byte # dividing each entry in the rodata section. So the offset will be the length of the # current string plus that extra null byte and that's where the next if isinstance(sym.getValue(), unicode) or isinstance(sym.getValue(), str): key = "relocation" + str(NUM_RELOCS) NUM_RELOCS += 1 relocations[key] = {'name': sym.getValue(), 'offset': hex(offset).lstrip("0x").rstrip("L"), 'addr': ref.getFromAddress(), 'info': info} newString = getBytes(sym.getAddress(), len(sym.getValue()) + 1) if DEBUG: println("newString Data: %s" % newString) println("newString length BEFORE: %d" % len(newString)) if len(newString) % alignment != 0: if DEBUG: println("This string is not 4 byte aligned. Need to add some bytes") diff = len(newString) % alignment padding = alignment - diff pad = to_bytes(0, padding, 'little') if DEBUG: println("Length of pad: %d" % len(pad)) println("Pad bytes: %s" % pad) println("Type of element: %s" % type(newString[0])) println("element: %s" % newString[0]) for s in pad: newString.append(ord(s)) # newString.append(to_bytes(0, padding, 'little')) # rodata_bytes += to_bytes(0, padding, 'little') if DEBUG: println("newString length AFTER: %d" % len(newString)) println("newString: %s" % newString) rodata_bytes += (newString) if DEBUG: println("rodata_bytes: %s" % rodata_bytes) rodata_offset += len(newString) else: if DEBUG: println("Address: %s" % sym.getAddress()) println("Value: %s" % sym.getValue()) # rodata_offset += 4 # rodata_bytes += to_bytes(0, 4, 'little') # println("Rodata offset: %s" % hex(rodata_offset)) if DEBUG: println("-----------------------------------") if DEBUG: println("----------------------------------------------------------------------------------------------------------------------") print(relocations) # println("Total Symbols: %d " % total_symbols) println("----------------------------------------------------------------------------------------------------------------------") strtab = '\x00' + '\x00'.join(strtab) + '\x00' if DEBUG: print("Strtab: " , strtab) # get script args args = getScriptArgs() module_arg = args[0] if len(args) > 0 else "" output_arg = args[1] if len(args) > 1 else os.path.dirname(parseFile(getSourceFile().getCanonicalPath()).getPath()) print("ELF Output: %s %s" % (module_arg, output_arg)) # executable_arg = args[2] if len(args) > 2 else write_section.offset = 0 if SelectModule(module_arg): # Perform first pass of ELF file to gather all the sections we'll modify and export. first_pass() get_symbols() get_module_bytes() get_relocations() unlink_object_file()