def DecodeElf(data, location): """Decode an ELF file and return information about it Args: data: Data from ELF file location: Start address of data to return Returns: ElfInfo object containing information about the decoded ELF file """ file_size = len(data) with io.BytesIO(data) as fd: elf = ELFFile(fd) data_start = 0xffffffff data_end = 0 mem_end = 0 virt_to_phys = 0 for i in range(elf.num_segments()): segment = elf.get_segment(i) if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']: skipped = 1 # To make code-coverage see this line continue start = segment['p_paddr'] mend = start + segment['p_memsz'] rend = start + segment['p_filesz'] data_start = min(data_start, start) data_end = max(data_end, rend) mem_end = max(mem_end, mend) if not virt_to_phys: virt_to_phys = segment['p_paddr'] - segment['p_vaddr'] output = bytearray(data_end - data_start) for i in range(elf.num_segments()): segment = elf.get_segment(i) if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']: skipped = 1 # To make code-coverage see this line continue start = segment['p_paddr'] offset = 0 if start < location: offset = location - start start = location # A legal ELF file can have a program header with non-zero length # but zero-length file size and a non-zero offset which, added # together, are greater than input->size (i.e. the total file size). # So we need to not even test in the case that p_filesz is zero. # Note: All of this code is commented out since we don't have a test # case for it. size = segment['p_filesz'] #if not size: #continue #end = segment['p_offset'] + segment['p_filesz'] #if end > file_size: #raise ValueError('Underflow copying out the segment. File has %#x bytes left, segment end is %#x\n', #file_size, end) output[start - data_start:start - data_start + size] = (segment.data()[offset:]) return ElfInfo(output, data_start, elf.header['e_entry'] + virt_to_phys, mem_end - data_start)
def main(): parser = argparse.ArgumentParser() parser.add_argument("--name", required=True) parser.add_argument("elf_file") args = parser.parse_args() elf = ELFFile(open(args.elf_file, "r+b")) assert elf.num_segments() < 6 # struct bootelf_header { # #define BOOTELF_MAGIC "\xaa\xbbBOOT\xe1\xff" # uint8_t magic[8]; # uint8_t name[16]; # uint64_t entry; # uint8_t num_mappings; # uint8_t padding[7]; # struct bootelf_mapping mappings[]; # } __packed; header = struct.pack("8s16sQB7x", BOOTELF_MAGIC, args.name.encode("ascii"), elf.header.e_entry, elf.num_segments()) for segment in elf.iter_segments(): assert segment.header.p_vaddr > 0 assert segment.header.p_type == 'PT_LOAD' assert segment.header.p_offset % PAGE_SIZE == 0 assert segment.header.p_memsz == segment.header.p_filesz or \ segment.header.p_filesz == 0 # struct bootelf_mapping { # uint64_t vaddr; # uint32_t offset; # uint16_t num_pages; # uint8_t type; // 'R': readonly, 'W': writable, 'X': executable. # uint8_t zeroed; // If it's non-zero value, the pages are filled with zeros. # } __packed; vaddr = segment.header.p_vaddr offset = segment.header.p_offset num_pages = (segment.header.p_memsz + PAGE_SIZE - 1) // PAGE_SIZE zeroed = segment.header.p_filesz == 0 header += struct.pack("QIHcb", vaddr, offset, num_pages, b'W', # TODO: Support so-called W^X 1 if zeroed else 0) with open(args.elf_file, "r+b") as f: for offset in [0x1000, 0x10000]: f.seek(offset) if f.read(8) == BOOTELF_PRE_MAGIC: break f.seek(offset) assert f.read(8) == BOOTELF_PRE_MAGIC f.seek(offset) f.write(header)
def read_segments(filename): elffile = ELFFile(open(filename, 'rb')) segments = list() for segment_idx in range(elffile.num_segments()): segments.insert(segment_idx, dict()) segments[segment_idx]['segment'] = elffile.get_segment(segment_idx) return segments
def read_segments(data): """Read segments from an ELF file Args: data (bytes): Contents of file Returns: tuple: list of segments, each: int: Segment number (0 = first) int: Start address of segment in memory bytes: Contents of segment int: entry address for image Raises: ValueError: elftools is not available """ if not ELF_TOOLS: raise ValueError('Python elftools package is not available') with io.BytesIO(data) as inf: try: elf = ELFFile(inf) except ELFError as err: raise ValueError(err) entry = elf.header['e_entry'] segments = [] for i in range(elf.num_segments()): segment = elf.get_segment(i) if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']: skipped = 1 # To make code-coverage see this line continue start = segment['p_offset'] rend = start + segment['p_filesz'] segments.append((i, segment['p_paddr'], data[start:rend])) return segments, entry
def open(io): elf_o = ELFFile(io) info('parsed elf file with %s sections and %s segments' % (elf_o.num_sections(), elf_o.num_segments())) arch = sefi.arch.from_elf_machine_arch(elf_o.get_machine_arch()) info(' elf file arch is %s' % (arch)) return (elf_o, arch)
def dump_segments(self): with open(self.filename, "rb") as f: elf = ELFFile(f) self.structs = elf.structs segments = [] for i in range(elf.num_segments()): seg = elf.get_segment(i) segments.append(seg.header) return segments
def test_hello(self): with open(os.path.join('test', 'testfiles', 'simple_gcc.elf.arm'), 'rb') as f: elf = ELFFile(f) self.assertEqual(elf.get_machine_arch(), 'ARM') # Check some other properties of this ELF file derived from readelf self.assertEqual(elf['e_entry'], 0x8018) self.assertEqual(elf.num_sections(), 14) self.assertEqual(elf.num_segments(), 2)
def open(io): elf_o = ELFFile(io) info('parsed elf file with %s sections and %s segments' % (elf_o.num_sections(), elf_o.num_segments()) ) arch = sefi.arch.from_elf_machine_arch(elf_o.get_machine_arch()) info(' elf file arch is %s' % (arch)) return (elf_o, arch)
def test_hello(self): with open(os.path.join('test', 'testfiles_for_unittests', 'simple_gcc.elf.mips'), 'rb') as f: elf = ELFFile(f) self.assertEqual(elf.get_machine_arch(), 'MIPS') # Check some other properties of this ELF file derived from readelf self.assertEqual(elf['e_entry'], 0x0) self.assertEqual(elf.num_sections(), 25) self.assertEqual(elf.num_segments(), 0)
def test_hello(self): with open(os.path.join('test', 'testfiles_for_unittests', 'simple_gcc.elf.arm'), 'rb') as f: elf = ELFFile(f) self.assertEqual(elf.get_machine_arch(), 'ARM') # Check some other properties of this ELF file derived from readelf self.assertEqual(elf['e_entry'], 0x8018) self.assertEqual(elf.num_sections(), 14) self.assertEqual(elf.num_segments(), 2)
def test_disasm_against_objdump(objdump_path, binary_path): # TODO: code repetition from test_disasm_standalone, encapsulate inner functionality. start_time = time.time() total_inst = 0 match_inst = 0 print(('Processing file:', binary_path)) elf_file = ELFFile(open(binary_path, 'rb')) if elf_file.num_segments() == 0: print('There are no program headers in this file.') return objdump = ObjdumpWrapper(objdump_path) disasm = HexagonDisassembler(objdump_compatible=True) for segment in elf_file.iter_segments(): if segment['p_flags'] & P_FLAGS.PF_X: print("Offset: {:x}".format(segment['p_offset'])) print("VirtAddr: {:x}".format(segment['p_vaddr'])) print("FileSiz: {:x}".format(segment['p_filesz'])) segment_data = segment.data() data_pos = 0 while data_pos + INST_SIZE <= len(segment_data): addr = segment['p_vaddr'] + data_pos inst_as_int = struct.unpack( '<I', segment_data[data_pos:data_pos + 4])[0] disasm_output = disasm.disasm_one_inst(inst_as_int, addr).text.strip() objdump_output = objdump.disasm_packet_raw( segment_data[data_pos:min(data_pos + 4 * 4, segment_data)], addr).strip() if (objdump_output != disasm_output): print("[{:08x}] {:s}".format(addr, objdump_output)) print("[{:08x}] {:s}".format(addr, disasm_output)) print() else: match_inst += 1 data_pos += 4 total_inst += 1 elapsed_time = time.time() - start_time print("Elapsed time: {0:.2f}".format(elapsed_time)) print('Match: {0:.2f}%'.format(match_inst / total_inst * 100))
def get_bl31_segments_info(bl31_file_name): """ Get load offset, physical offset, file size from bl31 elf file program headers. """ with open(bl31_file_name) as bl31_file: bl31 = ELFFile(bl31_file) num = bl31.num_segments() print 'Number of Segments : %d' % bl31.num_segments() for i in range(num): print 'Segment %d' % i seg = bl31.get_segment(i) ptype = seg[ELF_SEG_P_TYPE] poffset = seg[ELF_SEG_P_OFFSET] pmemsz = seg[ELF_SEG_P_MEMSZ] pfilesz = seg[ELF_SEG_P_FILESZ] print 'type: %s\nfilesz: %08x\nmemsz: %08x\noffset: %08x' % (ptype, pfilesz, pmemsz, poffset) paddr = seg[ELF_SEG_P_PADDR] print 'paddr: %08x' % paddr
def generate_atf_binary(bl31_file_name): with open(bl31_file_name) as bl31_file: bl31 = ELFFile(bl31_file) num = bl31.num_segments() for i in range(num): seg = bl31.get_segment(i) if ('PT_LOAD' == seg.__getitem__(ELF_SEG_P_TYPE)): paddr = seg.__getitem__(ELF_SEG_P_PADDR) file_name = 'bl31_0x%08x.bin' % paddr with open(file_name, "wb") as atf: atf.write(seg.data())
def generate_atf_fit_dts(fit_file_name, bl31_file_name, uboot_file_name, dtbs_file_name): """ Generate FIT script for ATF image. """ if fit_file_name != sys.stdout: fit_file = open(fit_file_name, "wb") else: fit_file = sys.stdout num_load_seg = 0 p_paddr = 0xFFFFFFFF with open(uboot_file_name, 'rb') as uboot_file: uboot = ELFFile(uboot_file) for i in range(uboot.num_segments()): seg = uboot.get_segment(i) if ('PT_LOAD' == seg.__getitem__(ELF_SEG_P_TYPE)): p_paddr = seg.__getitem__(ELF_SEG_P_PADDR) num_load_seg = num_load_seg + 1 assert (p_paddr != 0xFFFFFFFF and num_load_seg == 1) fit_file.write(DT_HEADER % p_paddr) with open(bl31_file_name, 'rb') as bl31_file: bl31 = ELFFile(bl31_file) elf_entry = bl31.header['e_entry'] for i in range(bl31.num_segments()): seg = bl31.get_segment(i) if ('PT_LOAD' == seg.__getitem__(ELF_SEG_P_TYPE)): paddr = seg.__getitem__(ELF_SEG_P_PADDR) p = seg.__getitem__(ELF_SEG_P_PADDR) append_atf_node(fit_file, i + 1, paddr, elf_entry) atf_cnt = i + 1 append_fdt_node(fit_file, dtbs_file_name) fit_file.write('%s\n' % DT_IMAGES_NODE_END) append_conf_node(fit_file, dtbs_file_name, atf_cnt) fit_file.write('%s\n' % DT_END) if fit_file_name != sys.stdout: fit_file.close()
def get_bl31_segments_info(bl31_file_name): """ Get load offset, physical offset, file size from bl31 elf file program headers. """ with open(bl31_file_name) as bl31_file: bl31 = ELFFile(bl31_file) num = bl31.num_segments() print('Number of Segments : %d' % bl31.num_segments()) for i in range(num): print('Segment %d' % i) seg = bl31.get_segment(i) ptype = seg[ELF_SEG_P_TYPE] poffset = seg[ELF_SEG_P_OFFSET] pmemsz = seg[ELF_SEG_P_MEMSZ] pfilesz = seg[ELF_SEG_P_FILESZ] print('type: %s\nfilesz: %08x\nmemsz: %08x\noffset: %08x' % (ptype, pfilesz, pmemsz, poffset)) paddr = seg[ELF_SEG_P_PADDR] print('paddr: %08x' % paddr)
def generate_atf_binary(bl31_file_name): with open(bl31_file_name) as bl31_file: bl31 = ELFFile(bl31_file) num = bl31.num_segments() for i in range(num): seg = bl31.get_segment(i) if ('PT_LOAD' == seg.__getitem__(ELF_SEG_P_TYPE)): paddr = seg.__getitem__(ELF_SEG_P_PADDR) file_name = 'bl31_0x%08x.bin' % paddr with open(file_name, "wb") as atf: atf.write(seg.data());
def test_disasm_against_objdump(objdump_path, binary_path): # TODO: code repetition from test_disasm_standalone, encapsulate inner functionality. start_time = time.time() total_inst = 0 match_inst = 0 print(('Processing file:', binary_path)) elf_file = ELFFile(open(binary_path, 'rb')) if elf_file.num_segments() == 0: print('There are no program headers in this file.') return objdump = ObjdumpWrapper(objdump_path) disasm = HexagonDisassembler(objdump_compatible=True) for segment in elf_file.iter_segments(): if segment['p_flags'] & P_FLAGS.PF_X: print("Offset: {:x}".format(segment['p_offset'])) print("VirtAddr: {:x}".format(segment['p_vaddr'])) print("FileSiz: {:x}".format(segment['p_filesz'])) segment_data = segment.data() data_pos = 0 while data_pos + INST_SIZE <= len(segment_data): addr = segment['p_vaddr'] + data_pos inst_as_int = struct.unpack('<I', segment_data[data_pos: data_pos + 4])[0] disasm_output = disasm.disasm_one_inst(inst_as_int, addr).text.strip() objdump_output = objdump.disasm_packet_raw( segment_data[data_pos: min(data_pos + 4 * 4, segment_data)], addr).strip() if (objdump_output != disasm_output): print("[{:08x}] {:s}".format(addr, objdump_output)) print("[{:08x}] {:s}".format(addr, disasm_output)) print() else: match_inst += 1 data_pos += 4 total_inst += 1 elapsed_time = time.time() - start_time print("Elapsed time: {0:.2f}".format(elapsed_time)) print('Match: {0:.2f}%'.format(match_inst / total_inst * 100))
def generate_atf_fit_dts(fit_file_name, bl31_file_name, uboot_file_name, dtbs_file_name): """ Generate FIT script for ATF image. """ if fit_file_name != sys.stdout: fit_file = open(fit_file_name, "wb") else: fit_file = sys.stdout num_load_seg = 0 p_paddr = 0xFFFFFFFF with open(uboot_file_name, 'rb') as uboot_file: uboot = ELFFile(uboot_file) for i in range(uboot.num_segments()): seg = uboot.get_segment(i) if ('PT_LOAD' == seg.__getitem__(ELF_SEG_P_TYPE)): p_paddr = seg.__getitem__(ELF_SEG_P_PADDR) num_load_seg = num_load_seg + 1 assert (p_paddr != 0xFFFFFFFF and num_load_seg == 1) fit_file.write(DT_HEADER % p_paddr) with open(bl31_file_name, 'rb') as bl31_file: bl31 = ELFFile(bl31_file) elf_entry = bl31.header['e_entry'] for i in range(bl31.num_segments()): seg = bl31.get_segment(i) if ('PT_LOAD' == seg.__getitem__(ELF_SEG_P_TYPE)): paddr = seg.__getitem__(ELF_SEG_P_PADDR) p= seg.__getitem__(ELF_SEG_P_PADDR) append_atf_node(fit_file, i+1, paddr, elf_entry) atf_cnt = i+1 append_fdt_node(fit_file, dtbs_file_name) fit_file.write('%s\n' % DT_IMAGES_NODE_END) append_conf_node(fit_file, dtbs_file_name, atf_cnt) fit_file.write('%s\n' % DT_END) if fit_file_name != sys.stdout: fit_file.close()
def generate_atf_fit_dts_bl31(fit_file, bl31_file_name, dtbs_file_name): with open(bl31_file_name, 'rb') as bl31_file: bl31 = ELFFile(bl31_file) elf_entry = bl31.header['e_entry'] segments = bl31.num_segments() for i in range(segments): seg = bl31.get_segment(i) if seg.__getitem__(ELF_SEG_P_TYPE) == 'PT_LOAD': paddr = seg.__getitem__(ELF_SEG_P_PADDR) append_bl31_node(fit_file, i + 1, paddr, elf_entry) append_fdt_node(fit_file, dtbs_file_name) fit_file.write(DT_IMAGES_NODE_END) append_conf_node(fit_file, dtbs_file_name, segments)
def generate_atf_fit_dts_bl31(fit_file, bl31_file_name, dtbs_file_name): with open(bl31_file_name, 'rb') as bl31_file: bl31 = ELFFile(bl31_file) elf_entry = bl31.header['e_entry'] segments = bl31.num_segments() for i in range(segments): seg = bl31.get_segment(i) if seg.__getitem__(ELF_SEG_P_TYPE) == 'PT_LOAD': paddr = seg.__getitem__(ELF_SEG_P_PADDR) append_bl31_node(fit_file, i + 1, paddr, elf_entry) append_fdt_node(fit_file, dtbs_file_name) fit_file.write(DT_IMAGES_NODE_END) append_conf_node(fit_file, dtbs_file_name, segments)
def generate_atf_fit_dts_uboot(fit_file, uboot_file_name): num_load_seg = 0 p_paddr = 0xFFFFFFFF with open(uboot_file_name, 'rb') as uboot_file: uboot = ELFFile(uboot_file) for i in range(uboot.num_segments()): seg = uboot.get_segment(i) if seg.__getitem__(ELF_SEG_P_TYPE) == 'PT_LOAD': p_paddr = seg.__getitem__(ELF_SEG_P_PADDR) num_load_seg = num_load_seg + 1 assert (p_paddr != 0xFFFFFFFF and num_load_seg == 1) fit_file.write(DT_UBOOT % p_paddr)
def generate_atf_fit_dts_uboot(fit_file, uboot_file_name): num_load_seg = 0 p_paddr = 0xFFFFFFFF with open(uboot_file_name, 'rb') as uboot_file: uboot = ELFFile(uboot_file) for i in range(uboot.num_segments()): seg = uboot.get_segment(i) if seg.__getitem__(ELF_SEG_P_TYPE) == 'PT_LOAD': p_paddr = seg.__getitem__(ELF_SEG_P_PADDR) num_load_seg = num_load_seg + 1 assert (p_paddr != 0xFFFFFFFF and num_load_seg == 1) fit_file.write(DT_UBOOT % p_paddr)
def test_disasm_standalone(binary_path, timeout=None): profile = cProfile.Profile() profile.enable() start_time = time.time() print(('Processing file:', binary_path)) elf_file = ELFFile(open(binary_path, 'rb')) if elf_file.num_segments() == 0: print('There are no program headers in this file.') return disasm = HexagonDisassembler() total_inst = 0 for segment in elf_file.iter_segments(): if segment['p_flags'] & P_FLAGS.PF_X: print("Offset: {:x}".format(segment['p_offset'])) print("VirtAddr: {:x}".format(segment['p_vaddr'])) print("FileSiz: {:x}".format(segment['p_filesz'])) segment_data = segment.data() data_pos = 0 while data_pos + INST_SIZE <= len(segment_data): addr = segment['p_vaddr'] + data_pos inst_as_int = struct.unpack( '<I', segment_data[data_pos:data_pos + 4])[0] dis = disasm.disasm_one_inst(inst_as_int, addr) print("[{:08x}] {:s}".format(addr, dis.text)) data_pos += 4 total_inst += 1 if timeout and (time.time() - start_time) > timeout: break profile.disable() prof_stats = pstats.Stats(profile) prof_stats.strip_dirs().sort_stats('cumulative').print_stats(20) print("Total instructions: " + str(total_inst)) elapsed_time = time.time() - start_time print("Elapsed time: " + str(elapsed_time))
def test_disasm_standalone(binary_path, timeout = None): profile = cProfile.Profile() profile.enable() start_time = time.time() print(('Processing file:', binary_path)) elf_file = ELFFile(open(binary_path, 'rb')) if elf_file.num_segments() == 0: print('There are no program headers in this file.') return disasm = HexagonDisassembler() total_inst = 0 for segment in elf_file.iter_segments(): if segment['p_flags'] & P_FLAGS.PF_X: print("Offset: {:x}".format(segment['p_offset'])) print("VirtAddr: {:x}".format(segment['p_vaddr'])) print("FileSiz: {:x}".format(segment['p_filesz'])) segment_data = segment.data() data_pos = 0 while data_pos + INST_SIZE <= len(segment_data): addr = segment['p_vaddr'] + data_pos inst_as_int = struct.unpack('<I', segment_data[data_pos: data_pos + 4])[0] dis = disasm.disasm_one_inst(inst_as_int, addr) print("[{:08x}] {:s}".format(addr, dis.text)) data_pos += 4 total_inst += 1 if timeout and (time.time() - start_time) > timeout: break profile.disable() prof_stats = pstats.Stats(profile) prof_stats.strip_dirs().sort_stats('cumulative').print_stats(20) print("Total instructions: " + str(total_inst)) elapsed_time = time.time() - start_time print("Elapsed time: " + str(elapsed_time))
def test_basic(self): with open(os.path.join('test', 'testfiles_for_unittests', 'simple_gcc.elf.mips'), 'rb') as f: elf = ELFFile(f) self.assertEqual(elf.get_machine_arch(), 'MIPS') # Check some other properties of this ELF file derived from readelf self.assertEqual(elf['e_entry'], 0x0) self.assertEqual(elf.num_sections(), 25) self.assertEqual(elf.num_segments(), 0) # Test that Mips-specific section types work; these types are # available only when the file is identified as MIPS in the # e_machine header field. sec9 = elf.get_section(9) self.assertEqual(sec9['sh_type'], 'SHT_MIPS_DWARF')
def test_basic(self): with open( os.path.join('test', 'testfiles_for_unittests', 'simple_gcc.elf.mips'), 'rb') as f: elf = ELFFile(f) self.assertEqual(elf.get_machine_arch(), 'MIPS') # Check some other properties of this ELF file derived from readelf self.assertEqual(elf['e_entry'], 0x0) self.assertEqual(elf.num_sections(), 25) self.assertEqual(elf.num_segments(), 0) # Test that Mips-specific section types work; these types are # available only when the file is identified as MIPS in the # e_machine header field. sec9 = elf.get_section(9) self.assertEqual(sec9['sh_type'], 'SHT_MIPS_DWARF')
def elf_to_dol(elf_path, dol_path): from elftools.elf.elffile import ELFFile with open(elf_path, 'rb') as elf_file, open(dol_path, 'wb') as dol_file: elf = ELFFile(elf_file) num_segments = elf.num_segments() dol_file.write(bytes([0x00] * 0x100)) idx = 0 for i in range(num_segments): segment = elf.get_segment(i) if not segment_is_text(segment): continue write_segment_to_dol(idx, segment, dol_file) idx += 1 idx = 7 for i in range(num_segments): segment = elf.get_segment(i) if not segment_is_data(segment): continue write_segment_to_dol(idx, segment, dol_file) idx += 1 bss_start = 0 bss_end = 0 for i in range(num_segments): segment = elf.get_segment(i) if not segment_is_bss(segment): continue if bss_start == 0: bss_start = segment["p_vaddr"] bss_end = segment["p_vaddr"] + segment["p_memsz"] write_to_dol_header(dol_file, 0xd8, bss_start) bss_size = bss_end - bss_start write_to_dol_header(dol_file, 0xdc, bss_size) write_to_dol_header(dol_file, 0xe0, elf["e_entry"])
def _get_buffer_range_elf(self, elf: ELFFile, address: int): PT_LOAD = {} if not elf.num_segments(): raise LookupError( 'The elftools parser did not find any segments in this file.') for segment in elf.iter_segments(): if segment.header.p_type == 'PT_LOAD': PT_LOAD[segment.header.p_vaddr] = segment self.log_info( F'Found PT_LOAD segment with base address 0x{segment.header.p_vaddr:x}' ) if not PT_LOAD: raise LookupError( F'Could not find any PT_LOAD segment containing 0x{address:x}.' ) addr = self._rebase(address, min(PT_LOAD)) for segment in elf.iter_segments(): begin = segment.header.p_vaddr size = segment.header.p_memsz delta = addr - begin if delta in range(size + 1): offset = segment.header.p_offset return offset + delta, offset + segment.header.p_filesz raise CompartmentNotFound(address)
class Elf(object): def __init__(self, fileobj): self.elffile = ELFFile(fileobj) self.output = sys.stdout # our code starts here :-) def network(self): ret = "None" for section in self.elffile.iter_sections(): if not isinstance(section, SymbolTableSection): continue if section['sh_entsize'] == 0: print("\nSymbol table '%s' has a sh_entsize " \ "of zero!" % (bytes2str(section.name)), file=sys.stderr) continue for _, symbol in enumerate(section.iter_symbols()): # first match IP_PATTERNS for pattern in IP_PATTERNS: if re.match(pattern, bytes2str(symbol.name)): return "network-ip" # then match LOCAL_PATTERNS for pattern in LOCAL_PATTERNS: if re.match(pattern, bytes2str(symbol.name)): ret = "network-local" break return ret def fortify(self): """ NA : FORTIFY_SOURCE was not applicable Enabled : unsafe and _chk functions were found Disabled : only unsafe functions were found (_chk functions missing)""" ret = "NA" for section in self.elffile.iter_sections(): if not isinstance(section, SymbolTableSection): continue if section['sh_entsize'] == 0: print("\nSymbol table '%s' has a sh_entsize " \ "of zero!" % (bytes2str(section.name)), file=sys.stderr) continue for _, symbol in enumerate(section.iter_symbols()): for pattern in UNSAFE_FUNCTIONS: if re.match(pattern, bytes2str(symbol.name)): if ret == "NA": ret = "Disabled" break if ret == "Disabled": # afename = "__" + bytes2str(symbol.name) + "_chk" for _, symbol in enumerate(section.iter_symbols()): # first look for corresponding _chk symbol symbolstr = bytes2str(symbol.name) if (symbolstr.startswith("__") and symbolstr.endswith("_chk")) or \ symbolstr.endswith(" __chk_fail"): ret = "Enabled" break return ret def canary(self): for section in self.elffile.iter_sections(): if not isinstance(section, SymbolTableSection): continue if section['sh_entsize'] == 0: print ("\nSymbol table '%s' has a sh_entsize " \ "of zero!" % (bytes2str(section.name)), file=sys.stderr) continue for _, symbol in enumerate(section.iter_symbols()): if re.match(STACK_CHK, bytes2str(symbol.name)): return "Enabled" return "Disabled" def dynamic_tags(self, key="DT_RPATH"): for section in self.elffile.iter_sections(): if not isinstance(section, DynamicSection): continue for tag in section.iter_tags(): if tag.entry.d_tag == key: return "Enabled" return "Disabled" def program_headers(self): pflags = P_FLAGS() if self.elffile.num_segments() == 0: # print('There are no program headers in this file.', \ # file=sys.stderr) return for segment in self.elffile.iter_segments(): if re.search("GNU_STACK", segment['p_type']): if segment['p_flags'] & pflags.PF_X: return "Disabled" return "Enabled" def relro(self): if self.elffile.num_segments() == 0: # print('There are no program headers in this file.', \ # file=sys.stderr) return have_relro = False for segment in self.elffile.iter_segments(): if re.search("GNU_RELRO", segment['p_type']): have_relro = True break if self.dynamic_tags("DT_BIND_NOW") == "Enabled" and have_relro: return "Enabled" if have_relro: return "Partial" return "Disabled" def pie(self): header = self.elffile.header if self.dynamic_tags("EXEC") == "Enabled": return "Disabled" if "ET_DYN" in header['e_type']: if self.dynamic_tags("DT_DEBUG") == "Enabled": return "Enabled" else: return "DSO" return "Disabled" def getdeps(self): deps=[] if self.elffile.num_segments() == 0: return deps for segment in self.elffile.iter_segments(): if re.search("PT_DYNAMIC", segment['p_type']): # this file uses dynamic linking, so read the dynamic section # and find DT_SONAME tag for section in self.elffile.iter_sections(): if not isinstance(section, DynamicSection): continue for tag in section.iter_tags(): if tag.entry.d_tag == 'DT_NEEDED': deps.append(bytes2str(tag.needed)) break return deps
class Elf(object): def __init__(self, fileobj): self.elffile = ELFFile(fileobj) self.output = sys.stdout # our code starts here :-) def network(self): ret = "None" for section in self.elffile.iter_sections(): if not isinstance(section, SymbolTableSection): continue if section['sh_entsize'] == 0: print( "\nSymbol table '%s' has a sh_entsize " "of zero!" % (bytes2str(section.name)), file=sys.stderr) continue for _, symbol in enumerate(section.iter_symbols()): # first match IP_PATTERNS for pattern in IP_PATTERNS: if re.match(pattern, bytes2str(symbol.name)): return "network-ip" # then match LOCAL_PATTERNS for pattern in LOCAL_PATTERNS: if re.match(pattern, bytes2str(symbol.name)): ret = "network-local" break return ret def _strings(self): stream = self.elffile.stream epos = stream.tell() stream.seek(0, 0) data = stream.read() stream.seek(epos, 0) ret = [] # XXX avoid calling eu-strings import subprocess p = subprocess.Popen( "eu-strings", shell=True, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE) out = p.communicate(input=data)[0] for line in out.splitlines(): if re.match(b"^/tmp/.+", line) and "XXX" not in line: ret.append(line) return ret def tempstuff(self): tmp_strings = self._strings() # if there are no /tmp references, just return if len(tmp_strings) == 0: return "None" for section in self.elffile.iter_sections(): if not isinstance(section, SymbolTableSection): continue if section['sh_entsize'] == 0: print( "\nSymbol table '%s' has a sh_entsize " "of zero!" % (bytes2str(section.name)), file=sys.stderr) continue for _, symbol in enumerate(section.iter_symbols()): for pattern in TMP_FUNCTIONS: if re.match(pattern, bytes2str(symbol.name)): return "None" return "$".join(tmp_strings) # XXX implement this def chroot_without_chdir(self): """ Check for apps that use chroot(2) without using chdir(2). Inspired by http://people.redhat.com/sgrubb/security/find-chroot """ pass def fortify(self): """ Check if source code was compiled with FORTIFY_SOURCE. Enabled : no unsafe functions were found OR all were translated to _chk versions Partial : unprotected unsafe functions were found TODO ==== * Print summary report like checksec.sh does * Drop CSV output support (it is too restrictive) * "addr2line" like feature for unprotected unsafe functions """ unsafe_list = [] for section in self.elffile.iter_sections(): if not isinstance(section, SymbolTableSection): continue if section['sh_entsize'] == 0: print( "\nSymbol table '%s' has a sh_entsize " "of zero!" % (bytes2str(section.name)), file=sys.stderr) continue for _, symbol in enumerate(section.iter_symbols()): for pattern in UNSAFE_FUNCTIONS: if re.match(pattern + "$", bytes2str(symbol.name)): unsafe_list.append(bytes2str(symbol.name)) if len(unsafe_list) == 0: return "Enabled" else: return "Partial$" + "$".join(unsafe_list) def canary(self): for section in self.elffile.iter_sections(): if not isinstance(section, SymbolTableSection): continue if section['sh_entsize'] == 0: print( "\nSymbol table '%s' has a sh_entsize " "of zero!" % (bytes2str(section.name)), file=sys.stderr) continue for _, symbol in enumerate(section.iter_symbols()): if bytes2str(symbol.name) in STACK_CHK: return "Enabled" return "Disabled" def dynamic_tags(self, key="DT_RPATH"): for section in self.elffile.iter_sections(): if not isinstance(section, DynamicSection): continue for tag in section.iter_tags(): if tag.entry.d_tag == key: return "Enabled" return "Disabled" def program_headers(self): pflags = P_FLAGS() if self.elffile.num_segments() == 0: # print('There are no program headers in this file.', \ # file=sys.stderr) return found = False for segment in self.elffile.iter_segments(): if re.search("GNU_STACK", str(segment['p_type'])): found = True if segment['p_flags'] & pflags.PF_X: return "Disabled" if found: return "Enabled" return "Disabled" def relro(self): if self.elffile.num_segments() == 0: # print('There are no program headers in this file.', \ # file=sys.stderr) return have_relro = False for segment in self.elffile.iter_segments(): if re.search("GNU_RELRO", str(segment['p_type'])): have_relro = True break if self.dynamic_tags("DT_BIND_NOW") == "Enabled" and have_relro: return "Enabled" if have_relro: return "Partial" return "Disabled" def pie(self): header = self.elffile.header if self.dynamic_tags("EXEC") == "Enabled": return "Disabled" if "ET_DYN" in header['e_type']: if self.dynamic_tags("DT_DEBUG") == "Enabled": return "Enabled" else: return "DSO" return "Disabled" def getdeps(self): deps = [] if self.elffile.num_segments() == 0: return deps for segment in self.elffile.iter_segments(): if re.search("PT_DYNAMIC", str(segment['p_type'])): # this file uses dynamic linking, so read the dynamic section # and find DT_SONAME tag for section in self.elffile.iter_sections(): if not isinstance(section, DynamicSection): continue for tag in section.iter_tags(): if tag.entry.d_tag == 'DT_NEEDED': deps.append(bytes2str(tag.needed)) break return deps
class Elf(object): def __init__(self, fileobj): self.elffile = ELFFile(fileobj) self.output = sys.stdout # our code starts here :-) def network(self): ret = "None" for section in self.elffile.iter_sections(): if not isinstance(section, SymbolTableSection): continue if section['sh_entsize'] == 0: print("\nSymbol table '%s' has a sh_entsize " \ "of zero!" % (bytes2str(section.name)), file=sys.stderr) continue for _, symbol in enumerate(section.iter_symbols()): # first match IP_PATTERNS for pattern in IP_PATTERNS: if re.match(pattern, bytes2str(symbol.name)): return "network-ip" # then match LOCAL_PATTERNS for pattern in LOCAL_PATTERNS: if re.match(pattern, bytes2str(symbol.name)): ret = "network-local" break return ret def _strings(self): stream = self.elffile.stream epos = stream.tell() stream.seek(0, 0) data = stream.read() stream.seek(epos, 0) ret = [] # XXX avoid calling eu-strings import subprocess p = subprocess.Popen("eu-strings", shell=True, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE) out = p.communicate(input=data)[0] for line in out.splitlines(): if re.match("^/tmp/.+", line) and "XXX" not in line: ret.append(line) return ret def tempstuff(self): tmp_strings = self._strings() # if there are no /tmp references, just return if len(tmp_strings) == 0: return "None" for section in self.elffile.iter_sections(): if not isinstance(section, SymbolTableSection): continue if section['sh_entsize'] == 0: print("\nSymbol table '%s' has a sh_entsize " \ "of zero!" % (bytes2str(section.name)), file=sys.stderr) continue for _, symbol in enumerate(section.iter_symbols()): for pattern in TMP_FUNCTIONS: if re.match(pattern, bytes2str(symbol.name)): return "None" return "$".join(tmp_strings) # XXX implement this def chroot_without_chdir(self): """ This functions looks for apps that use chroot(2) without using chdir(2). Inspired by http://people.redhat.com/sgrubb/security/find-chroot """ pass def fortify(self): """ NA : FORTIFY_SOURCE was not applicable Enabled : unsafe and _chk functions were found Disabled : only unsafe functions were found (_chk functions missing)""" ret = "NA" for section in self.elffile.iter_sections(): if not isinstance(section, SymbolTableSection): continue if section['sh_entsize'] == 0: print("\nSymbol table '%s' has a sh_entsize " \ "of zero!" % (bytes2str(section.name)), file=sys.stderr) continue for _, symbol in enumerate(section.iter_symbols()): for pattern in UNSAFE_FUNCTIONS: if re.match(pattern, bytes2str(symbol.name)): if ret == "NA": ret = "Disabled" break if ret == "Disabled": # afename = "__" + bytes2str(symbol.name) + "_chk" for _, symbol in enumerate(section.iter_symbols()): # first look for corresponding _chk symbol symbolstr = bytes2str(symbol.name) if (symbolstr.startswith("__") and symbolstr.endswith("_chk")) or \ symbolstr.endswith(" __chk_fail"): ret = "Enabled" break return ret def canary(self): for section in self.elffile.iter_sections(): if not isinstance(section, SymbolTableSection): continue if section['sh_entsize'] == 0: print ("\nSymbol table '%s' has a sh_entsize " \ "of zero!" % (bytes2str(section.name)), file=sys.stderr) continue for _, symbol in enumerate(section.iter_symbols()): if re.match(STACK_CHK, bytes2str(symbol.name)): return "Enabled" return "Disabled" def dynamic_tags(self, key="DT_RPATH"): for section in self.elffile.iter_sections(): if not isinstance(section, DynamicSection): continue for tag in section.iter_tags(): if tag.entry.d_tag == key: return "Enabled" return "Disabled" def program_headers(self): pflags = P_FLAGS() if self.elffile.num_segments() == 0: # print('There are no program headers in this file.', \ # file=sys.stderr) return for segment in self.elffile.iter_segments(): if re.search("GNU_STACK", segment['p_type']): if segment['p_flags'] & pflags.PF_X: return "Disabled" return "Enabled" def relro(self): if self.elffile.num_segments() == 0: # print('There are no program headers in this file.', \ # file=sys.stderr) return have_relro = False for segment in self.elffile.iter_segments(): if re.search("GNU_RELRO", segment['p_type']): have_relro = True break if self.dynamic_tags("DT_BIND_NOW") == "Enabled" and have_relro: return "Enabled" if have_relro: return "Partial" return "Disabled" def pie(self): header = self.elffile.header if self.dynamic_tags("EXEC") == "Enabled": return "Disabled" if "ET_DYN" in header['e_type']: if self.dynamic_tags("DT_DEBUG") == "Enabled": return "Enabled" else: return "DSO" return "Disabled" def getdeps(self): deps=[] if self.elffile.num_segments() == 0: return deps for segment in self.elffile.iter_segments(): if re.search("PT_DYNAMIC", segment['p_type']): # this file uses dynamic linking, so read the dynamic section # and find DT_SONAME tag for section in self.elffile.iter_sections(): if not isinstance(section, DynamicSection): continue for tag in section.iter_tags(): if tag.entry.d_tag == 'DT_NEEDED': deps.append(bytes2str(tag.needed)) break return deps
from elftools.elf.elffile import ELFFile if __name__ == '__main__': if len(sys.argv) < 2: print( "You must provide this script with an elf binary file you want to examine" ) exit(1) print(f"Mapping between segments and sections in the file {sys.argv[1]}") elffile = ELFFile(open(sys.argv[1], 'rb')) segments = list() for segment_idx in range(elffile.num_segments()): segments.insert(segment_idx, dict()) segments[segment_idx]['segment'] = elffile.get_segment(segment_idx) segments[segment_idx]['sections'] = list() for section_idx in range(elffile.num_sections()): section = elffile.get_section(section_idx) for segment in segments: if segment['segment'].section_in_segment(section): segment['sections'].append(section) for segment in segments: seg_head = segment['segment'].header print("Segment:") print( f"Type: {seg_head.p_type}\nOffset: {hex(seg_head.p_offset)}\nVirtual address: {hex(seg_head.p_vaddr)}\nPhysical address: {(seg_head.p_paddr)}\nSize in file: {hex(seg_head.p_filesz)}\nSize in memory: {hex(seg_head.p_memsz)}\n"
def load(cls, fo): ef = ELFFile(fo) if ef.header['e_type'] != 'ET_EXEC': raise RuntimeError( 'not an ELF executable file (type {})'.format(ef.header['e_type'])) if ef.header['e_machine'] != 'EM_68K': raise RuntimeError('not an M68K ELF file') if ef.num_segments() != 2: raise RuntimeError('wrong number of segments in ELF file') # Look at segments for text and data; note that we # expect to see exactly two segments, one RX, one RW, # for text and data respectively, with data immediately # following text in memory. textSegment = ef.get_segment(0) textAddress = textSegment['p_vaddr'] textSize = textSegment['p_filesz'] dataSegment = ef.get_segment(1) dataAddress = dataSegment['p_vaddr'] dataSize = dataSegment['p_filesz'] # Look for BSS sections bssAddress = None bssLimit = None for section in ef.iter_sections(): if (section['sh_type'] == 'SHT_NOBITS') and (section['sh_flags'] & SH_FLAGS.SHF_ALLOC): secStart = section['sh_addr'] secLimit = section['sh_addr'] + section['sh_size'] # track low BSS address if bssAddress is None: bssAddress = secStart elif secStart < bssAddress: bssAddress = secStart # track BSS limit if bssLimit is None: bssLimit = secLimit elif secLimit > bssLimit: bssLimit = secLimit if bssAddress is None: bssAddress = dataAddress + dataSize bssSize = 0 else: bssSize = bssLimit - bssAddress # extend text to cover the gap created by data segment alignment dataGap = dataAddress - (textAddress + textSize) if dataGap < 0: raise RuntimeError('data segment before text') textSize += dataGap # extend data to cover the gap created by BSS alignment bssGap = bssAddress - (dataAddress + dataSize) if bssGap < 0: raise RuntimeError('BSS before data segment (0x{:x} inside/before 0x{:x}/0x{:x}'.format( bssAddress, dataAddress, dataSize)) dataSize += bssGap # sanity-check the text and data segments if (textSegment['p_type'] != 'PT_LOAD') or (dataSegment['p_type'] != 'PT_LOAD'): raise RuntimeError('expected two PT_LOAD segments') if (textSegment['p_flags'] & cls.P_FLAGS_MASK) != cls.P_FLAGS_RX: raise RuntimeError('text segment is not RX') if textAddress != 0: raise RuntimeError('text segment is not at 0') if (dataSegment['p_flags'] & cls.P_FLAGS_MASK) != cls.P_FLAGS_RW: raise RuntimeError('data segment is not RW') if dataAddress != textAddress + textSize: raise RuntimeError('data segment @ 0x{:x} does not follow text 0x{:x}/0x{:x}'.format( dataAddress, textAddress, textSize)) text = textSegment.data().ljust(textSize, '\0') data = dataSegment.data().ljust(dataSize, '\0') if len(text) != textSize: raise RuntimeError('text size mismatch') if len(data) != dataSize: raise RuntimeError('data size mismatch') print('text 0x{:x} data 0x{:x} bss 0x{:x}'.format(textSize, dataSize, bssSize)) # look for relocations relocs = dict() for section in ef.iter_sections(): if isinstance(section, RelocationSection): # what section do these relocations affect? relocSection = ef.get_section(section['sh_info']) if not (relocSection['sh_flags'] & SH_FLAGS.SHF_ALLOC): #print('Not relocating {}'.format(relocSection.name)) continue #print('Relocate: {} using {}'.format(relocSection.name, section.name)) symtab = ef.get_section(section['sh_link']) for reloc in section.iter_relocations(): relAddress = reloc['r_offset'] if relAddress >= (len(text) + len(data)): raise RuntimeError('relocation outside known space') if not reloc.is_RELA(): raise RuntimeError('unexpected REL reloc') relType = reloc['r_info_type'] # get the symbol table entry the reloc refers to if reloc['r_info_sym'] >= symtab.num_symbols(): raise RuntimeError( 'symbol reference in relocation out of bounds') relTarget = symtab.get_symbol(reloc['r_info_sym'])['st_value'] # It looks like we can ignore the addend, as it's already # present in the object file... relAddend = reloc['r_addend'] # Sort out what we're going to do with this relocation... if relType == R_68K_32: pass elif relType == R_68K_NONE: #print('ignoring none-reloc @ 0x{:x}'.format(relAddress)) continue elif relType == R_68K_PC32: #print('ignoring PC32 reloc @ 0x{:x} -> 0x{:x}+{:x}'.format(relAddress, relTarget, relAddend)) continue elif relType == R_68K_PC16: #print('ignoring PC16 reloc @ 0x{:x} -> 0x{:x}+{:x}'.format(relAddress, relTarget, relAddend)) continue elif relType == R_68K_PC8: #print('ignoring PC8 reloc @ 0x{:x} -> 0x{:x}+{:x}'.format(relAddress, relTarget, relAddend)) continue else: raise RuntimeError('unexpected relocation type {} @ 0x{:x} -> 0x{:x}+x'.format(relType, relAddress, relTarget, relAddend)) #print('RELA address 0x{:08x} target 0x{:x} type {} addend 0x{:x}'.format(relAddress, relTarget, relType, relAddend)) if relTarget < len(text): relType |= R_TEXT if relAddend > len(text): raise RuntimeError('addend outside of text section') elif relTarget < (len(text) + len(data)): relType |= R_DATA if relAddend > len(data): raise RuntimeError('addend outside of data section') elif relTarget <= (len(text) + len(data) + bssSize): # note, <= to allow pointer to _end, which is immediately *after* the BSS relType |= R_BSS if relAddend > bssSize: raise RuntimeError('addend outside of bss section') else: raise RuntimeError( 'relocation target not in known space') #print(' -> type 0x{:03x}'.format(relType)) if relAddress < len(text): inSeg = text segOffset = relAddress elif relAddress < (len(text) + len(data)): inSeg = data segOffset = relAddress - len(text) else: raise RuntimeError('relocation not in known space') unRelocated = struct.unpack('>L', inSeg[segOffset:segOffset+4])[0] #print(' unrelocated: 0x{:x}'.format(unRelocated)) if unRelocated != (relTarget + relAddend): raise RuntimeError("unrelocated field 0x{:x} != target 0x{:x} + addend 0x{:x}".format(unRelocated, relTarget, relAddend)) relocs[relAddress] = relType return cls(text=text, data=data, bssSize=bssSize, relocs=relocs)
import msvcrt from elftools.common import * from elftools.elf.elffile import ELFFile with open('jnilibs/libnative-lib.so', 'rb') as f: elf = ELFFile(f) print(type(elf)) for seg in elf.iter_segments(): print(seg['p_type'], hex(seg['p_offset']), hex(seg['p_vaddr']), hex(seg['p_paddr'])) exit() sec_num = elf.num_sections() print('sec_num:' + str(sec_num)) seg_num = elf.num_segments() print('seg_num:' + str(seg_num)) for i in range(0, sec_num, 1): section = elf.get_section(i) print(section) print(type(section)) print(section.data_alignment, section.data_size, str(section.data(), encoding='ISO-8859-1')) msvcrt.getch() # for i in range(0, seg_num, 1): # segment = elf.get_segment(i) # print(str(segment.data(), encoding='ISO-8859-1')) # print(segment) # print(segment.data_alignment, section.data_size, str(section.data(), encoding='ISO-8859-1'))
from Crypto.PublicKey import DSA import re def slugify(value): """ Normalizes string, converts to lowercase, removes non-alpha characters, and converts spaces to hyphens. """ import unicodedata value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') value=value.decode('utf-8') value = re.sub('[^\w\s-]', '_', value).strip() value = re.sub('[-\s]+', '_', value) return value fl=open(sys.argv[1], 'rb') elffile = ELFFile(fl) print("Segments: {}".format(elffile.num_segments()) ) dprint=False class Segment(object): virtMem=None offset=None sz=None data=None def __init__(self,segm,fl): self.virtMem=segm['p_vaddr'] self.offset=segm['p_offset'] self.sz=segm['p_memsz'] fl.seek(self.offset) self.data=fl.read(self.sz) #print(data)
class ReadElf(object): """ display_* methods are used to emit output into the output stream """ def __init__(self, file, output): """ file: stream object with the ELF file to read output: output stream to write to """ self.elffile = ELFFile(file) self.output = output # Lazily initialized if a debug dump is requested self._dwarfinfo = None def display_file_header(self): """ Display the ELF file header """ self._emitline('ELF Header:') self._emit(' Magic: ') self._emitline(' '.join('%2.2x' % byte2int(b) for b in self.elffile.e_ident_raw)) header = self.elffile.header e_ident = header['e_ident'] self._emitline(' Class: %s' % describe_ei_class(e_ident['EI_CLASS'])) self._emitline(' Data: %s' % describe_ei_data(e_ident['EI_DATA'])) self._emitline(' Version: %s' % describe_ei_version(e_ident['EI_VERSION'])) self._emitline(' OS/ABI: %s' % describe_ei_osabi(e_ident['EI_OSABI'])) self._emitline(' ABI Version: %d' % e_ident['EI_ABIVERSION']) self._emitline(' Type: %s' % describe_e_type(header['e_type'])) self._emitline(' Machine: %s' % describe_e_machine(header['e_machine'])) self._emitline(' Version: %s' % describe_e_version_numeric(header['e_version'])) self._emitline(' Entry point address: %s' % self._format_hex(header['e_entry'])) self._emit(' Start of program headers: %s' % header['e_phoff']) self._emitline(' (bytes into file)') self._emit(' Start of section headers: %s' % header['e_shoff']) self._emitline(' (bytes into file)') self._emitline(' Flags: %s' % self._format_hex(header['e_flags'])) self._emitline(' Size of this header: %s (bytes)' % header['e_ehsize']) self._emitline(' Size of program headers: %s (bytes)' % header['e_phentsize']) self._emitline(' Number of program headers: %s' % header['e_phnum']) self._emitline(' Size of section headers: %s (bytes)' % header['e_shentsize']) self._emitline(' Number of section headers: %s' % header['e_shnum']) self._emitline(' Section header string table index: %s' % header['e_shstrndx']) def display_program_headers(self, show_heading=True): """ Display the ELF program headers. If show_heading is True, displays the heading for this information (Elf file type is...) """ self._emitline() if self.elffile.num_segments() == 0: self._emitline('There are no program headers in this file.') return elfheader = self.elffile.header if show_heading: self._emitline('Elf file type is %s' % describe_e_type(elfheader['e_type'])) self._emitline('Entry point is %s' % self._format_hex(elfheader['e_entry'])) # readelf weirness - why isn't e_phoff printed as hex? (for section # headers, it is...) self._emitline( 'There are %s program headers, starting at offset %s' % (elfheader['e_phnum'], elfheader['e_phoff'])) self._emitline() self._emitline('Program Headers:') # Now comes the table of program headers with their attributes. Note # that due to different formatting constraints of 32-bit and 64-bit # addresses, there are some conditions on elfclass here. # # First comes the table heading # if self.elffile.elfclass == 32: self._emitline( ' Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align' ) else: self._emitline( ' Type Offset VirtAddr PhysAddr' ) self._emitline( ' FileSiz MemSiz Flags Align' ) # Now the entries # for segment in self.elffile.iter_segments(): self._emit(' %-14s ' % describe_p_type(segment['p_type'])) if self.elffile.elfclass == 32: self._emitline( '%s %s %s %s %s %-3s %s' % (self._format_hex(segment['p_offset'], fieldsize=6), self._format_hex(segment['p_vaddr'], fullhex=True), self._format_hex(segment['p_paddr'], fullhex=True), self._format_hex(segment['p_filesz'], fieldsize=5), self._format_hex(segment['p_memsz'], fieldsize=5), describe_p_flags(segment['p_flags']), self._format_hex(segment['p_align']))) else: # 64 self._emitline( '%s %s %s' % (self._format_hex(segment['p_offset'], fullhex=True), self._format_hex(segment['p_vaddr'], fullhex=True), self._format_hex(segment['p_paddr'], fullhex=True))) self._emitline(' %s %s %-3s %s' % ( self._format_hex(segment['p_filesz'], fullhex=True), self._format_hex(segment['p_memsz'], fullhex=True), describe_p_flags(segment['p_flags']), # lead0x set to False for p_align, to mimic readelf. # No idea why the difference from 32-bit mode :-| self._format_hex(segment['p_align'], lead0x=False))) if isinstance(segment, InterpSegment): self._emitline(' [Requesting program interpreter: %s]' % bytes2str(segment.get_interp_name())) # Sections to segments mapping # if self.elffile.num_sections() == 0: # No sections? We're done return self._emitline('\n Section to Segment mapping:') self._emitline(' Segment Sections...') for nseg, segment in enumerate(self.elffile.iter_segments()): self._emit(' %2.2d ' % nseg) for section in self.elffile.iter_sections(): if (not section.is_null() and segment.section_in_segment(section)): self._emit('%s ' % bytes2str(section.name)) self._emitline('') def display_section_headers(self, show_heading=True): """ Display the ELF section headers """ elfheader = self.elffile.header if show_heading: self._emitline( 'There are %s section headers, starting at offset %s' % (elfheader['e_shnum'], self._format_hex(elfheader['e_shoff']))) self._emitline('\nSection Header%s:' % ('s' if elfheader['e_shnum'] > 1 else '')) # Different formatting constraints of 32-bit and 64-bit addresses # if self.elffile.elfclass == 32: self._emitline( ' [Nr] Name Type Addr Off Size ES Flg Lk Inf Al' ) else: self._emitline( ' [Nr] Name Type Address Offset' ) self._emitline( ' Size EntSize Flags Link Info Align' ) # Now the entries # for nsec, section in enumerate(self.elffile.iter_sections()): self._emit( ' [%2u] %-17.17s %-15.15s ' % (nsec, bytes2str( section.name), describe_sh_type(section['sh_type']))) if self.elffile.elfclass == 32: self._emitline( '%s %s %s %s %3s %2s %3s %2s' % (self._format_hex( section['sh_addr'], fieldsize=8, lead0x=False), self._format_hex( section['sh_offset'], fieldsize=6, lead0x=False), self._format_hex( section['sh_size'], fieldsize=6, lead0x=False), self._format_hex( section['sh_entsize'], fieldsize=2, lead0x=False), describe_sh_flags( section['sh_flags']), section['sh_link'], section['sh_info'], section['sh_addralign'])) else: # 64 self._emitline( ' %s %s' % (self._format_hex( section['sh_addr'], fullhex=True, lead0x=False), self._format_hex(section['sh_offset'], fieldsize=16 if section['sh_offset'] > 0xffffffff else 8, lead0x=False))) self._emitline( ' %s %s %3s %2s %3s %s' % (self._format_hex( section['sh_size'], fullhex=True, lead0x=False), self._format_hex( section['sh_entsize'], fullhex=True, lead0x=False), describe_sh_flags( section['sh_flags']), section['sh_link'], section['sh_info'], section['sh_addralign'])) self._emitline('Key to Flags:') self._emit( ' W (write), A (alloc), X (execute), M (merge), S (strings)') if self.elffile['e_machine'] in ('EM_X86_64', 'EM_L10M'): self._emitline(', l (large)') else: self._emitline() self._emitline( ' I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)' ) self._emitline( ' O (extra OS processing required) o (OS specific), p (processor specific)' ) def display_symbol_tables(self): """ Display the symbol tables contained in the file """ for section in self.elffile.iter_sections(): if not isinstance(section, SymbolTableSection): continue if section['sh_entsize'] == 0: self._emitline( "\nSymbol table '%s' has a sh_entsize of zero!" % (bytes2str(section.name))) continue self._emitline("\nSymbol table '%s' contains %s entries:" % (bytes2str(section.name), section.num_symbols())) if self.elffile.elfclass == 32: self._emitline( ' Num: Value Size Type Bind Vis Ndx Name') else: # 64 self._emitline( ' Num: Value Size Type Bind Vis Ndx Name' ) for nsym, symbol in enumerate(section.iter_symbols()): # symbol names are truncated to 25 chars, similarly to readelf self._emitline( '%6d: %s %5d %-7s %-6s %-7s %4s %.25s' % (nsym, self._format_hex(symbol['st_value'], fullhex=True, lead0x=False), symbol['st_size'], describe_symbol_type(symbol['st_info']['type']), describe_symbol_bind(symbol['st_info']['bind']), describe_symbol_visibility( symbol['st_other']['visibility']), describe_symbol_shndx( symbol['st_shndx']), bytes2str(symbol.name))) def display_relocations(self): """ Display the relocations contained in the file """ has_relocation_sections = False for section in self.elffile.iter_sections(): if not isinstance(section, RelocationSection): continue has_relocation_sections = True self._emitline( "\nRelocation section '%s' at offset %s contains %s entries:" % (bytes2str(section.name), self._format_hex( section['sh_offset']), section.num_relocations())) if section.is_RELA(): self._emitline( " Offset Info Type Sym. Value Sym. Name + Addend" ) else: self._emitline( " Offset Info Type Sym.Value Sym. Name") # The symbol table section pointed to in sh_link symtable = self.elffile.get_section(section['sh_link']) for rel in section.iter_relocations(): hexwidth = 8 if self.elffile.elfclass == 32 else 12 self._emit( '%s %s %-17.17s' % (self._format_hex( rel['r_offset'], fieldsize=hexwidth, lead0x=False), self._format_hex( rel['r_info'], fieldsize=hexwidth, lead0x=False), describe_reloc_type(rel['r_info_type'], self.elffile))) if rel['r_info_sym'] == 0: self._emitline() continue symbol = symtable.get_symbol(rel['r_info_sym']) # Some symbols have zero 'st_name', so instead what's used is # the name of the section they point at if symbol['st_name'] == 0: symsec = self.elffile.get_section(symbol['st_shndx']) symbol_name = symsec.name else: symbol_name = symbol.name self._emit(' %s %s%22.22s' % (self._format_hex( symbol['st_value'], fullhex=True, lead0x=False), ' ' if self.elffile.elfclass == 32 else '', bytes2str(symbol_name))) if section.is_RELA(): self._emit(' %s %x' % ('+' if rel['r_addend'] >= 0 else '-', abs(rel['r_addend']))) self._emitline() if not has_relocation_sections: self._emitline('\nThere are no relocations in this file.') def display_hex_dump(self, section_spec): """ Display a hex dump of a section. section_spec is either a section number or a name. """ section = self._section_from_spec(section_spec) if section is None: self._emitline("Section '%s' does not exist in the file!" % (section_spec)) return self._emitline("\nHex dump of section '%s':" % bytes2str(section.name)) self._note_relocs_for_section(section) addr = section['sh_addr'] data = section.data() dataptr = 0 while dataptr < len(data): bytesleft = len(data) - dataptr # chunks of 16 bytes per line linebytes = 16 if bytesleft > 16 else bytesleft self._emit(' %s ' % self._format_hex(addr, fieldsize=8)) for i in range(16): if i < linebytes: self._emit('%2.2x' % byte2int(data[dataptr + i])) else: self._emit(' ') if i % 4 == 3: self._emit(' ') for i in range(linebytes): c = data[dataptr + i:dataptr + i + 1] if byte2int(c[0]) >= 32 and byte2int(c[0]) < 0x7f: self._emit(bytes2str(c)) else: self._emit(bytes2str(b'.')) self._emitline() addr += linebytes dataptr += linebytes self._emitline() def display_string_dump(self, section_spec): """ Display a strings dump of a section. section_spec is either a section number or a name. """ section = self._section_from_spec(section_spec) if section is None: self._emitline("Section '%s' does not exist in the file!" % (section_spec)) return self._emitline("\nString dump of section '%s':" % bytes2str(section.name)) found = False data = section.data() dataptr = 0 while dataptr < len(data): while (dataptr < len(data) and not (32 <= byte2int(data[dataptr]) <= 127)): dataptr += 1 if dataptr >= len(data): break endptr = dataptr while endptr < len(data) and byte2int(data[endptr]) != 0: endptr += 1 found = True self._emitline(' [%6x] %s' % (dataptr, bytes2str(data[dataptr:endptr]))) dataptr = endptr if not found: self._emitline(' No strings found in this section.') else: self._emitline() def display_debug_dump(self, dump_what): """ Dump a DWARF section """ self._init_dwarfinfo() if self._dwarfinfo is None: return set_global_machine_arch(self.elffile.get_machine_arch()) if dump_what == 'info': self._dump_debug_info() elif dump_what == 'decodedline': self._dump_debug_line_programs() elif dump_what == 'frames': self._dump_debug_frames() elif dump_what == 'frames-interp': self._dump_debug_frames_interp() else: self._emitline('debug dump not yet supported for "%s"' % dump_what) def _format_hex(self, addr, fieldsize=None, fullhex=False, lead0x=True): """ Format an address into a hexadecimal string. fieldsize: Size of the hexadecimal field (with leading zeros to fit the address into. For example with fieldsize=8, the format will be %08x If None, the minimal required field size will be used. fullhex: If True, override fieldsize to set it to the maximal size needed for the elfclass lead0x: If True, leading 0x is added """ s = '0x' if lead0x else '' if fullhex: fieldsize = 8 if self.elffile.elfclass == 32 else 16 if fieldsize is None: field = '%x' else: field = '%' + '0%sx' % fieldsize return s + field % addr def _section_from_spec(self, spec): """ Retrieve a section given a "spec" (either number or name). Return None if no such section exists in the file. """ try: num = int(spec) if num < self.elffile.num_sections(): return self.elffile.get_section(num) else: return None except ValueError: # Not a number. Must be a name then return self.elffile.get_section_by_name(str2bytes(spec)) def _note_relocs_for_section(self, section): """ If there are relocation sections pointing to the givne section, emit a note about it. """ for relsec in self.elffile.iter_sections(): if isinstance(relsec, RelocationSection): info_idx = relsec['sh_info'] if self.elffile.get_section(info_idx) == section: self._emitline( ' Note: This section has relocations against it, but these have NOT been applied to this dump.' ) return def _init_dwarfinfo(self): """ Initialize the DWARF info contained in the file and assign it to self._dwarfinfo. Leave self._dwarfinfo at None if no DWARF info was found in the file """ if self._dwarfinfo is not None: return if self.elffile.has_dwarf_info(): self._dwarfinfo = self.elffile.get_dwarf_info() else: self._dwarfinfo = None def _dump_debug_info(self): """ Dump the debugging info section. """ self._emitline('Contents of the .debug_info section:\n') # Offset of the .debug_info section in the stream section_offset = self._dwarfinfo.debug_info_sec.global_offset for cu in self._dwarfinfo.iter_CUs(): self._emitline(' Compilation Unit @ offset %s:' % self._format_hex(cu.cu_offset)) self._emitline(' Length: %s (%s)' % (self._format_hex( cu['unit_length']), '%s-bit' % cu.dwarf_format())) self._emitline(' Version: %s' % cu['version']), self._emitline(' Abbrev Offset: %s' % cu['debug_abbrev_offset']), self._emitline(' Pointer Size: %s' % cu['address_size']) # The nesting depth of each DIE within the tree of DIEs must be # displayed. To implement this, a counter is incremented each time # the current DIE has children, and decremented when a null die is # encountered. Due to the way the DIE tree is serialized, this will # correctly reflect the nesting depth # die_depth = 0 for die in cu.iter_DIEs(): if die.is_null(): die_depth -= 1 continue self._emitline( ' <%s><%x>: Abbrev Number: %s (%s)' % (die_depth, die.offset, die.abbrev_code, die.tag)) for attr in itervalues(die.attributes): name = attr.name # Unknown attribute values are passed-through as integers if isinstance(name, int): name = 'Unknown AT value: %x' % name self._emitline( ' <%2x> %-18s: %s' % (attr.offset, name, describe_attr_value(attr, die, section_offset))) if die.has_children: die_depth += 1 self._emitline() def _dump_debug_line_programs(self): """ Dump the (decoded) line programs from .debug_line The programs are dumped in the order of the CUs they belong to. """ self._emitline( 'Decoded dump of debug contents of section .debug_line:\n') for cu in self._dwarfinfo.iter_CUs(): lineprogram = self._dwarfinfo.line_program_for_CU(cu) cu_filename = '' if len(lineprogram['include_directory']) > 0: cu_filename = '%s/%s' % ( bytes2str(lineprogram['include_directory'][0]), bytes2str(lineprogram['file_entry'][0].name)) else: cu_filename = bytes2str(lineprogram['file_entry'][0].name) self._emitline('CU: %s:' % cu_filename) self._emitline( 'File name Line number Starting address' ) # Print each state's file, line and address information. For some # instructions other output is needed to be compatible with # readelf. for entry in lineprogram.get_entries(): state = entry.state if state is None: # Special handling for commands that don't set a new state if entry.command == DW_LNS_set_file: file_entry = lineprogram['file_entry'][entry.args[0] - 1] if file_entry.dir_index == 0: # current directory self._emitline('\n./%s:[++]' % (bytes2str(file_entry.name))) else: self._emitline( '\n%s/%s:' % (bytes2str(lineprogram['include_directory'][ file_entry.dir_index - 1]), bytes2str(file_entry.name))) elif entry.command == DW_LNE_define_file: self._emitline( '%s:' % (bytes2str(lineprogram['include_directory'] [entry.args[0].dir_index]))) elif not state.end_sequence: # readelf doesn't print the state after end_sequence # instructions. I think it's a bug but to be compatible # I don't print them too. self._emitline( '%-35s %11d %18s' % (bytes2str( lineprogram['file_entry'][state.file - 1].name), state.line, '0' if state.address == 0 else self._format_hex(state.address))) if entry.command == DW_LNS_copy: # Another readelf oddity... self._emitline() def _dump_debug_frames(self): """ Dump the raw frame information from .debug_frame """ if not self._dwarfinfo.has_CFI(): return self._emitline('Contents of the .debug_frame section:') for entry in self._dwarfinfo.CFI_entries(): if isinstance(entry, CIE): self._emitline( '\n%08x %08x %08x CIE' % (entry.offset, entry['length'], entry['CIE_id'])) self._emitline(' Version: %d' % entry['version']) self._emitline(' Augmentation: "%s"' % bytes2str(entry['augmentation'])) self._emitline(' Code alignment factor: %u' % entry['code_alignment_factor']) self._emitline(' Data alignment factor: %d' % entry['data_alignment_factor']) self._emitline(' Return address column: %d' % entry['return_address_register']) self._emitline() else: # FDE self._emitline( '\n%08x %08x %08x FDE cie=%08x pc=%08x..%08x' % (entry.offset, entry['length'], entry['CIE_pointer'], entry.cie.offset, entry['initial_location'], entry['initial_location'] + entry['address_range'])) self._emit(describe_CFI_instructions(entry)) self._emitline() def _dump_debug_frames_interp(self): """ Dump the interpreted (decoded) frame information from .debug_frame """ if not self._dwarfinfo.has_CFI(): return self._emitline('Contents of the .debug_frame section:') for entry in self._dwarfinfo.CFI_entries(): if isinstance(entry, CIE): self._emitline('\n%08x %08x %08x CIE "%s" cf=%d df=%d ra=%d' % (entry.offset, entry['length'], entry['CIE_id'], bytes2str(entry['augmentation']), entry['code_alignment_factor'], entry['data_alignment_factor'], entry['return_address_register'])) ra_regnum = entry['return_address_register'] else: # FDE self._emitline( '\n%08x %08x %08x FDE cie=%08x pc=%08x..%08x' % (entry.offset, entry['length'], entry['CIE_pointer'], entry.cie.offset, entry['initial_location'], entry['initial_location'] + entry['address_range'])) ra_regnum = entry.cie['return_address_register'] # Print the heading row for the decoded table self._emit(' LOC') self._emit(' ' if entry.structs.address_size == 4 else ' ') self._emit(' CFA ') # Decode the table nad look at the registers it describes. # We build reg_order here to match readelf's order. In particular, # registers are sorted by their number, and the register matching # ra_regnum is always listed last with a special heading. decoded_table = entry.get_decoded() reg_order = sorted( ifilter(lambda r: r != ra_regnum, decoded_table.reg_order)) # Headings for the registers for regnum in reg_order: self._emit('%-6s' % describe_reg_name(regnum)) self._emitline('ra ') # Now include ra_regnum in reg_order to print its values similarly # to the other registers. reg_order.append(ra_regnum) for line in decoded_table.table: self._emit( self._format_hex(line['pc'], fullhex=True, lead0x=False)) self._emit(' %-9s' % describe_CFI_CFA_rule(line['cfa'])) for regnum in reg_order: if regnum in line: s = describe_CFI_register_rule(line[regnum]) else: s = 'u' self._emit('%-6s' % s) self._emitline() self._emitline() def _emit(self, s=''): """ Emit an object to output """ self.output.write(str(s)) def _emitline(self, s=''): """ Emit an object to output, followed by a newline """ self.output.write(str(s) + '\n')
class ELF(_header): _backend = None _elf = None _versioninfo = None def __init__(self, path, filetype, stream=None, backend=None): if ELFFile is None: raise INSTALLerror( "Install the ELFFile module to use the ELF backend!") super(ELF, self).__init__(path, filetype) self._backend = backend if stream is None: f = open(path, 'rb') self._elf = ELFFile(f) else: self._elf = ELFFile(stream) bindata = self._elf.stream.read() self.fileMd5 = hashlib.md5(bindata).hexdigest() self.fileSha1 = hashlib.sha1(bindata).hexdigest() self.fileSha256 = hashlib.sha256(bindata).hexdigest() self.fileSha512 = hashlib.sha512(bindata).hexdigest() del bindata self._elf.stream.seek(0) self.bin_data = self._elf.stream.read() self._elf.stream.seek(0) self.arch_str = self._elf.header.e_machine self._entry = self._elf.header.e_entry if (self._elf.little_endian): self.endness = "Iend_LE" else: self.endness = "Iend_BE" self.set_arch(ArchSelector().search(self.arch_str, self.endness)) def read_addr(self, addr): return addr def get_elf_header(self): result = {} header = self._elf.header if (hasattr(header, 'e_ident')): e_ident = header['e_ident'] EI_MAG = '' for MAG in e_ident['EI_MAG']: EI_MAG += str(hex(MAG)).replace("0x", '') result['EI_MAG'] = EI_MAG result['EI_CLASS'] = describe_ei_class(e_ident['EI_CLASS']) result['EI_DATA'] = describe_ei_data(e_ident['EI_DATA']) result['EI_VERSION'] = describe_ei_version(e_ident['EI_VERSION']) result['EI_OSABI'] = describe_ei_osabi(e_ident['EI_OSABI']) if (hasattr(header, 'e_type')): result['e_type'] = describe_e_type(header['e_type']) if (hasattr(header, 'e_machine')): result['e_machine'] = describe_e_machine(header['e_machine']) if (hasattr(header, 'e_version')): result['e_version'] = header['e_version'] if (hasattr(header, 'e_entry')): result['e_entry'] = header['e_entry'] if (hasattr(header, 'e_phoff')): result['e_phoff'] = header['e_phoff'] if (hasattr(header, 'e_shoff')): result['e_shoff'] = header['e_shoff'] if (hasattr(header, 'e_flags')): result['e_flags'] = header['e_flags'] if (hasattr(header, 'e_ehsize')): result['e_ehsize'] = header['e_ehsize'] if (hasattr(header, 'e_phentsize')): result['e_phentsize'] = header['e_phentsize'] if (hasattr(header, 'e_phnum')): result['e_phnum'] = header['e_phnum'] if (hasattr(header, 'e_shentsize')): result['e_shentsize'] = header['e_shentsize'] if (hasattr(header, 'e_shnum')): result['e_shnum'] = header['e_shnum'] if (hasattr(header, 'e_shstrndx')): result['e_shstrndx'] = header['e_shstrndx'] return result def is_section(self, addr): for nsec, section in enumerate(self._elf.iter_sections()): if addr >= (section['sh_addr']) and addr <= (section['sh_addr'] + section['sh_size']): section.Name = section.name return section def get_sections(self): sections = [] for nsec, section in enumerate(self._elf.iter_sections()): result = {} result['nsec'] = nsec result['name'] = section.name result['sh_type'] = describe_sh_type(section['sh_type']) if self._elf.elfclass == 32: result['sh_addr'] = section['sh_addr'] result['shoffset'] = section['sh_offset'] result['sh_size'] = section['sh_size'] result['sh_entsize'] = section['sh_entsize'] result['sh_flags'] = describe_sh_flags(section['sh_flags']) result['sh_link'] = section['sh_link'] result['sh_info'] = section['sh_info'] result['sh_addralign'] = section['sh_addralign'] else: # 64 result['sh_addr'] = section['sh_addr'] result['sh_offset'] = section['sh_offset'] result['sh_size'] = section['sh_size'] result['sh_entsize'] = section['sh_entsize'] result['sh_flags'] = describe_sh_flags(section['sh_flags']) result['sh_link'] = section['sh_link'], section['sh_info'] result['sh_addralign'] = section['sh_addralign'] # Dynamic Section if isinstance(section, DynamicSection): result['special_type'] = 'dynamic' result['dynamic'] = [] has_dynamic_sections = True for tag in section.iter_tags(): dynamic = {} if tag.entry.d_tag == 'DT_NEEDED': parsed = 'Shared library: [%s]' % tag.needed elif tag.entry.d_tag == 'DT_RPATH': parsed = 'Library rpath: [%s]' % tag.rpath elif tag.entry.d_tag == 'DT_RUNPATH': parsed = 'Library runpath: [%s]' % tag.runpath elif tag.entry.d_tag == 'DT_SONAME': parsed = 'Library soname: [%s]' % tag.soname elif tag.entry.d_tag.endswith(('SZ', 'ENT')): parsed = '%i (bytes)' % tag['d_val'] elif tag.entry.d_tag.endswith(('NUM', 'COUNT')): parsed = '%i' % tag['d_val'] elif tag.entry.d_tag == 'DT_PLTREL': s = describe_dyn_tag(tag.entry.d_val) if s.startswith('DT_'): s = s[3:] parsed = '%s' % s else: parsed = '%#x' % tag['d_val'] dynamic['tag'] = ENUM_D_TAG.get( tag.entry.d_tag, tag.entry.d_tag) dynamic['tag_type'] = tag.entry.d_tag[3:] dynamic['tag_value'] = parsed result['dynamic'].append(dynamic) #Relocation Section if isinstance(section, RelocationSection): result['special_type'] = 'relocation' result['relocation'] = [] has_relocation_sections = True # The symbol table section pointed to in sh_link symtable = self._elf.get_section(section['sh_link']) for rel in section.iter_relocations(): relocation = {} relocation['r_offset'] = rel['r_offset'] relocation['r_info'] = rel['r_info'] relocation['r_info_type'] = describe_reloc_type( rel['r_info_type'], self._elf) if rel['r_info_sym'] == 0: continue symbol = symtable.get_symbol(rel['r_info_sym']) # Some symbols have zero 'st_name', so instead what's used is # the name of the section they point at if symbol['st_name'] == 0: symsec = self._elf.get_section(symbol['st_shndx']) relocation['symbol_name'] = symbol_name = symsec.name else: symbol_name = symbol.name relocation['st_value'] = symbol['st_value'] relocation['symbol_name'] = symbol_name if section.is_RELA(): relocation['r_addend'] = rel['r_addend'] result['relocation'].append(relocation) #Symbol Section if isinstance(section, SymbolTableSection): self._init_versioninfo() if section['sh_entsize'] == 0: continue result['special_type'] = 'symbol' result['symbol'] = [] for nsym, symbol in enumerate(section.iter_symbols()): sym_dic = {} version_info = '' # readelf doesn't display version info for Solaris versioning if (section['sh_type'] == 'SHT_DYNSYM' and self._versioninfo['type'] == 'GNU'): version = self._symbol_version(nsym) if (version['name'] != symbol.name and version['index'] not in ('VER_NDX_LOCAL', 'VER_NDX_GLOBAL')): if version['filename']: # external symbol version_info = '@%(name)s (%(index)i)' % version else: # internal symbol if version['hidden']: version_info = '@%(name)s' % version else: version_info = '@@%(name)s' % version # symbol names are truncated to 25 chars, similarly to readelf sym_dic['nsym'] = nsym sym_dic['st_value'] = symbol['st_value'] sym_dic['st_size'] = symbol['st_size'] sym_dic['st_type'] = describe_symbol_type( symbol['st_info']['type']) sym_dic['bind'] = describe_symbol_bind( symbol['st_info']['bind']) sym_dic['vis'] = describe_symbol_visibility( symbol['st_other']['visibility']) sym_dic['ndx'] = describe_symbol_shndx( symbol['st_shndx']) sym_dic['name'] = symbol.name sym_dic['version'] = version_info result['symbol'].append(sym_dic) sections.append(result) return sections def get_program_header(self): header = [] if self._elf.num_segments() == 0: return [] for segment in self._elf.iter_segments(): result = {} result['p_type'] = describe_p_type(segment['p_type']) if self._elf.elfclass == 32: result['p_offset'] = segment['p_offset'] result['p_vaddr'] = segment['p_vaddr'] result['p_paddr'] = segment['p_paddr'] result['p_filesz'] = segment['p_filesz'] result['p_memsz'] = segment['p_memsz'] result['p_flags'] = describe_p_flags(segment['p_flags']) result['p_align'] = segment['p_align'] else: # 64 result['p_offset'] = segment['p_offset'] result['p_vaddr'] = segment['p_vaddr'] result['p_paddr'] = segment['p_paddr'] result['p_filesz'] = segment['p_filesz'] result['p_memsz'] = segment['p_memsz'] result['p_flags'] = describe_p_flags(segment['p_flags']) result['p_align'] = segment['p_align'] if isinstance(segment, InterpSegment): result['interp_name'] = segment.get_interp_name() result['include_section'] = [] for section in self._elf.iter_sections(): if (not section.is_null() and segment.section_in_segment(section)): result['include_section'].append(section.name) #NoteSegment if isinstance(segment, NoteSegment): result['special_type'] = 'note' result['note'] = [] for note in segment.iter_notes(): note_dic = {} note_dic['n_offset'] = note['n_offset'] note_dic['n_size'] = note['n_size'] note_dic['n_name'] = note['n_name'] note_dic['n_descsz'] = note['n_descsz'] note_dic['note'] = describe_note(note) result['note'].append(note_dic) header.append(result) return header def _init_versioninfo(self): """ Search and initialize informations about version related sections and the kind of versioning used (GNU or Solaris). """ if self._versioninfo is not None: return self._versioninfo = { 'versym': None, 'verdef': None, 'verneed': None, 'type': None } for section in self._elf.iter_sections(): if isinstance(section, GNUVerSymSection): self._versioninfo['versym'] = section elif isinstance(section, GNUVerDefSection): self._versioninfo['verdef'] = section elif isinstance(section, GNUVerNeedSection): self._versioninfo['verneed'] = section elif isinstance(section, DynamicSection): for tag in section.iter_tags(): if tag['d_tag'] == 'DT_VERSYM': self._versioninfo['type'] = 'GNU' break if not self._versioninfo['type'] and (self._versioninfo['verneed'] or self._versioninfo['verdef']): self._versioninfo['type'] = 'Solaris' def _symbol_version(self, nsym): """ Return a dict containing information on the or None if no version information is available """ self._init_versioninfo() symbol_version = dict.fromkeys(('index', 'name', 'filename', 'hidden')) if (not self._versioninfo['versym'] or nsym >= self._versioninfo['versym'].num_symbols()): return None symbol = self._versioninfo['versym'].get_symbol(nsym) index = symbol.entry['ndx'] if not index in ('VER_NDX_LOCAL', 'VER_NDX_GLOBAL'): index = int(index) if self._versioninfo['type'] == 'GNU': # In GNU versioning mode, the highest bit is used to # store wether the symbol is hidden or not if index & 0x8000: index &= ~0x8000 symbol_version['hidden'] = True if (self._versioninfo['verdef'] and index <= self._versioninfo['verdef'].num_versions()): _, verdaux_iter = \ self._versioninfo['verdef'].get_version(index) symbol_version['name'] = next(verdaux_iter).name else: verneed, vernaux = \ self._versioninfo['verneed'].get_version(index) symbol_version['name'] = vernaux.name symbol_version['filename'] = verneed.name symbol_version['index'] = index return symbol_version def Header(self): header = {} header['header'] = self.get_elf_header() header['sections'] = self.get_sections() header['program'] = self.get_program_header() return header
def process_file(filename): print('Processing file:', filename) with open(filename, 'rb') as f: elffile = ELFFile(f) # The provided linkerscript sorts the sections in the appropriate order assert (elffile.num_segments() == 1) segment = elffile.get_segment(0) symtab = elffile.get_section_by_name('.symtab') # A table of exported callbacks where each entry is made of two u32 # values, the first is the event ID and the second is the address of the # callback function exports_sym = symtab.get_symbol_by_name('Exports')[0] exports_addr = exports_sym['st_value'] exports_size = exports_sym['st_size'] assert ((exports_addr & 3) == 0) assert ((exports_size & 7) == 0) # The text and data sections are merged together, the bss is allocated # and zeroed by the loader. The 4 takes into account the extra pointer # to the application class that's inserted by the loader. code_size = segment['p_filesz'] - 4 bss_size = segment['p_memsz'] - code_size # Address of the funalizer function finish_sym = symtab.get_symbol_by_name('_finish')[0] finish_addr = finish_sym['st_value'] asset0 = struct.pack( '<16I', 0x1000, # Fixed? 1, # Version code_size, # Code + Data size 0, # Same but for libs 4, # Space for the Klass pointer bss_size, # Size of the zeroed area code_size, # Offset for the Klass pointer exports_addr, # Offset for the exports array 0xffffffff, # Same but for libs 0xffffffff, # Init array start 0xffffffff, # Fini array start 0, # Init array len (in words) 0, # Fini array len (in words) elffile['e_entry'], # Entry point finish_addr, # Exit point 0x2B0B3ED5) # Used to check if the .sig file is valid # Make it writable, we may perform some modification later asset1 = bytearray(segment.data()) reloc_offsets = [] reloc_sections = [ sect for sect in elffile.iter_sections() if isinstance(sect, RelocationSection) ] # The only relocation performed by the loader is adding the address # where the app is loaded to a whole u32 for rel_section in reloc_sections: for reloc in rel_section.iter_relocations(): ty = reloc['r_info_type'] # Make sure it's not STN_UNDEF assert (reloc['r_info_sym'] != 0) if ty == ENUM_RELOC_TYPE_ARM['R_ARM_ABS32']: reloc_offsets.append(reloc['r_offset']) elif ty == ENUM_RELOC_TYPE_ARM['R_ARM_REL32'] or \ ty == ENUM_RELOC_TYPE_ARM['R_ARM_CALL']: # Already PC-independent pass else: raise RuntimeError('Unknown relocation type!') # Terminates the list reloc_offsets.append(0xffffffff) asset2 = struct.pack('<IIII', 1, len(reloc_offsets) - 1, 16 + len(reloc_offsets) * 4, 0xf0123456) for off in reloc_offsets: asset2 += struct.pack('<I', off) # The key file is simply chopped, shuffled and hashed with SHA1 together # with the "magic" word at 0x3c in the asset0. with open(filename + '.sig', 'w+b') as out: header = struct.pack( '<II', 0x1000, # Fixed ? 8) # Key slot out.write(header) out.write( b'\x63\x9a\x83\xa1\x99\x4b\xfb\x42\x56\xd4\x5e\x44\x2a\x90\x2a\x35' ) out.write( b'\xf5\xd5\xdc\xef\xb9\x80\x90\x3d\x80\x33\x94\x5f\x54\x6c\xcc\x9a' ) out.write( b'\xe8\xa7\xad\xf7\xe0\x22\x3d\x1e\x59\x30\x38\x3a\x4d\x30\x39\x3a' ) out.write( b'\x44\x30\x31\x3a\x54\x30\x39\x3a\x30\x30\xe3\xd1\xa5\xca\xbc\x94' ) out.write( b'\x75\xd1\x0c\x79\x70\x03\x1b\x10\x33\x50\x31\xad\x79\xbc') with open(filename + '.aab', 'w+b') as out: out.write(b'ABHS') header = struct.pack( '<3I', 0x1000, # Fixed ? 0x10, # Application type 3) # Number of assets out.write(header) off = 16 + 3 * 12 for i, asset in enumerate([asset0, asset1, asset2]): entry = struct.pack('<3I', i, len(asset), off) off += len(asset) out.write(entry) out.write(asset0) out.write(asset1) out.write(asset2)
class QlReadELF(object): def __init__(self, ql: Qiling, elf_stream): self.ql = ql self.elffile = ELFFile(elf_stream) self._versioninfo = None def elf_file_header(self): elf_header = {} def add_info(key, value): elf_header[key] = value header = self.elffile.header e_ident = header['e_ident'] add_info( 'Magic', ' '.join('%2.2x' % byte2int(b) for b in self.elffile.e_ident_raw)) add_info('Class', describe_ei_class(e_ident['EI_CLASS'])) add_info('Data', describe_ei_data(e_ident['EI_DATA'])) add_info('Version', e_ident['EI_VERSION']) add_info('OS/ABI', describe_ei_osabi(e_ident['EI_OSABI'])) add_info('ABI Version', e_ident['EI_ABIVERSION']) add_info('Type', describe_e_type(header['e_type'])) add_info('Machine', describe_e_machine(header['e_machine'])) add_info('Version_e', describe_e_version_numeric(header['e_version'])) add_info('Entry point address', self._format_hex(header['e_entry'])) add_info('Start of program headers', header['e_phoff']) add_info('Start of section headers', header['e_shoff']) add_info('Flags', [ self._format_hex(header['e_flags']), self.decode_flags(header['e_flags']) ]) add_info('Size of this header', header['e_ehsize']) add_info('Size of program headers', header['e_phentsize']) add_info('Number of program headers', header['e_phnum']) add_info('Size of section headers', header['e_shentsize']) add_info('Number of section headers', header['e_shnum']) add_info('Section header string table index', header['e_shstrndx']) return elf_header def elf_program_headers(self): program_headers = [] def add_info(dic): program_headers.append(dic) if self.elffile.num_segments() == 0: return None for segment in self.elffile.iter_segments(): program_hdr = {} program_hdr['Type'] = describe_p_type(segment['p_type']) program_hdr['Offset'] = self._format_hex(segment['p_offset'], fieldsize=6) program_hdr['VirtAddr'] = self._format_hex(segment['p_vaddr'], fullhex=True) program_hdr['PhysAddr'] = self._format_hex(segment['p_paddr'], fullhex=True) program_hdr['FileSiz'] = self._format_hex(segment['p_filesz'], fieldsize=5) program_hdr['MemSiz'] = self._format_hex(segment['p_memsz'], fieldsize=5) program_hdr['Flg'] = describe_p_flags(segment['p_flags']) program_hdr['Align'] = self._format_hex(segment['p_align']) add_info(program_hdr) return program_headers def elf_section_headers(self): section_headers = [] def add_info(dic): section_headers.append(dic) if self.elffile.num_sections() == 0: return None for nsec, section in enumerate(self.elffile.iter_sections()): section_hdr = {} section_hdr['index'] = nsec section_hdr['Name'] = section.name section_hdr['Type'] = describe_sh_type(section['sh_type']) section_hdr['Addr'] = self._format_hex(section['sh_addr'], fieldsize=8, lead0x=False) section_hdr['Offset'] = self._format_hex(section['sh_offset'], fieldsize=6, lead0x=False) section_hdr['Size'] = self._format_hex(section['sh_size'], fieldsize=6, lead0x=False) section_hdr['ES'] = self._format_hex(section['sh_entsize'], fieldsize=2, lead0x=False) section_hdr['Flag'] = describe_sh_flags(section['sh_flags']) section_hdr['Lk'] = section['sh_link'] section_hdr['Inf'] = section['sh_info'] section_hdr['Al'] = section['sh_addralign'] add_info(section_hdr) return section_headers def elf_symbol_tables(self): symbol_tables_list = [] def add_info(dic): symbol_tables_list.append(dic) self._init_versioninfo() symbol_tables = [ s for s in self.elffile.iter_sections() if isinstance(s, SymbolTableSection) ] if not symbol_tables and self.elffile.num_sections() == 0: return None for section in symbol_tables: if not isinstance(section, SymbolTableSection): continue if section['sh_entsize'] == 0: continue for nsym, symbol in enumerate(section.iter_symbols()): version_info = '' if (section['sh_type'] == 'SHT_DYNSYM' and self._versioninfo['type'] == 'GNU'): version = self._symbol_version(nsym) if (version['name'] != symbol.name and version['index'] not in ('VER_NDX_LOCAL', 'VER_NDX_GLOBAL')): if version['filename']: # external symbol version_info = '@%(name)s (%(index)i)' % version else: # internal symbol if version['hidden']: version_info = '@%(name)s' % version else: version_info = '@@%(name)s' % version symbol_info = {} symbol_info['index'] = nsym symbol_info['Value'] = self._format_hex(symbol['st_value'], fullhex=True, lead0x=False) symbol_info['Size'] = symbol['st_size'] symbol_info['Type'] = describe_symbol_type( symbol['st_info']['type']) symbol_info['Bind'] = describe_symbol_bind( symbol['st_info']['bind']) symbol_info['Vis'] = describe_symbol_visibility( symbol['st_other']['visibility']) symbol_info['Ndx'] = describe_symbol_shndx(symbol['st_shndx']) symbol_info['Name'] = symbol.name symbol_info['version_info'] = version_info add_info(symbol_info) return symbol_tables_list def decode_flags(self, flags): description = "" if self.elffile['e_machine'] == "EM_ARM": eabi = flags & E_FLAGS.EF_ARM_EABIMASK flags &= ~E_FLAGS.EF_ARM_EABIMASK if flags & E_FLAGS.EF_ARM_RELEXEC: description += ', relocatable executabl' flags &= ~E_FLAGS.EF_ARM_RELEXEC if eabi == E_FLAGS.EF_ARM_EABI_VER5: EF_ARM_KNOWN_FLAGS = E_FLAGS.EF_ARM_ABI_FLOAT_SOFT | E_FLAGS.EF_ARM_ABI_FLOAT_HARD | E_FLAGS.EF_ARM_LE8 | E_FLAGS.EF_ARM_BE8 description += ', Version5 EABI' if flags & E_FLAGS.EF_ARM_ABI_FLOAT_SOFT: description += ", soft-float ABI" elif flags & E_FLAGS.EF_ARM_ABI_FLOAT_HARD: description += ", hard-float ABI" if flags & E_FLAGS.EF_ARM_BE8: description += ", BE8" elif flags & E_FLAGS.EF_ARM_LE8: description += ", LE8" if flags & ~EF_ARM_KNOWN_FLAGS: description += ', <unknown>' else: description += ', <unrecognized EABI>' elif self.elffile['e_machine'] == "EM_MIPS": if flags & E_FLAGS.EF_MIPS_NOREORDER: description += ", noreorder" if flags & E_FLAGS.EF_MIPS_PIC: description += ", pic" if flags & E_FLAGS.EF_MIPS_CPIC: description += ", cpic" if (flags & E_FLAGS.EF_MIPS_ABI2): description += ", abi2" if (flags & E_FLAGS.EF_MIPS_32BITMODE): description += ", 32bitmode" if (flags & E_FLAGS_MASKS.EFM_MIPS_ABI_O32): description += ", o32" elif (flags & E_FLAGS_MASKS.EFM_MIPS_ABI_O64): description += ", o64" elif (flags & E_FLAGS_MASKS.EFM_MIPS_ABI_EABI32): description += ", eabi32" elif (flags & E_FLAGS_MASKS.EFM_MIPS_ABI_EABI64): description += ", eabi64" if (flags & E_FLAGS.EF_MIPS_ARCH) == E_FLAGS.EF_MIPS_ARCH_1: description += ", mips1" if (flags & E_FLAGS.EF_MIPS_ARCH) == E_FLAGS.EF_MIPS_ARCH_2: description += ", mips2" if (flags & E_FLAGS.EF_MIPS_ARCH) == E_FLAGS.EF_MIPS_ARCH_3: description += ", mips3" if (flags & E_FLAGS.EF_MIPS_ARCH) == E_FLAGS.EF_MIPS_ARCH_4: description += ", mips4" if (flags & E_FLAGS.EF_MIPS_ARCH) == E_FLAGS.EF_MIPS_ARCH_5: description += ", mips5" if (flags & E_FLAGS.EF_MIPS_ARCH) == E_FLAGS.EF_MIPS_ARCH_32R2: description += ", mips32r2" if (flags & E_FLAGS.EF_MIPS_ARCH) == E_FLAGS.EF_MIPS_ARCH_64R2: description += ", mips64r2" if (flags & E_FLAGS.EF_MIPS_ARCH) == E_FLAGS.EF_MIPS_ARCH_32: description += ", mips32" if (flags & E_FLAGS.EF_MIPS_ARCH) == E_FLAGS.EF_MIPS_ARCH_64: description += ", mips64" return description def _format_hex(self, addr, fieldsize=None, fullhex=False, lead0x=True, alternate=False): """ Format an address into a hexadecimal string. fieldsize: Size of the hexadecimal field (with leading zeros to fit the address into. For example with fieldsize=8, the format will be %08x If None, the minimal required field size will be used. fullhex: If True, override fieldsize to set it to the maximal size needed for the elfclass lead0x: If True, leading 0x is added alternate: If True, override lead0x to emulate the alternate hexadecimal form specified in format string with the # character: only non-zero values are prefixed with 0x. This form is used by readelf. """ if alternate: if addr == 0: lead0x = False else: lead0x = True fieldsize -= 2 s = '0x' if lead0x else '' if fullhex: fieldsize = 8 if self.elffile.elfclass == 32 else 16 if fieldsize is None: field = '%x' else: field = '%' + '0%sx' % fieldsize return s + field % addr def _init_versioninfo(self): """ Search and initialize informations about version related sections and the kind of versioning used (GNU or Solaris). """ if self._versioninfo is not None: return self._versioninfo = { 'versym': None, 'verdef': None, 'verneed': None, 'type': None } for section in self.elffile.iter_sections(): if isinstance(section, GNUVerSymSection): self._versioninfo['versym'] = section elif isinstance(section, GNUVerDefSection): self._versioninfo['verdef'] = section elif isinstance(section, GNUVerNeedSection): self._versioninfo['verneed'] = section elif isinstance(section, DynamicSection): for tag in section.iter_tags(): if tag['d_tag'] == 'DT_VERSYM': self._versioninfo['type'] = 'GNU' break if not self._versioninfo['type'] and (self._versioninfo['verneed'] or self._versioninfo['verdef']): self._versioninfo['type'] = 'Solaris' def _symbol_version(self, nsym): """ Return a dict containing information on the or None if no version information is available """ self._init_versioninfo() symbol_version = dict.fromkeys(('index', 'name', 'filename', 'hidden')) if (not self._versioninfo['versym'] or nsym >= self._versioninfo['versym'].num_symbols()): return None symbol = self._versioninfo['versym'].get_symbol(nsym) index = symbol.entry['ndx'] if not index in ('VER_NDX_LOCAL', 'VER_NDX_GLOBAL'): index = int(index) if self._versioninfo['type'] == 'GNU': # In GNU versioning mode, the highest bit is used to # store wether the symbol is hidden or not if index & 0x8000: index &= ~0x8000 symbol_version['hidden'] = True if (self._versioninfo['verdef'] and index <= self._versioninfo['verdef'].num_versions()): _, verdaux_iter = \ self._versioninfo['verdef'].get_version(index) symbol_version['name'] = next(verdaux_iter).name else: verneed, vernaux = \ self._versioninfo['verneed'].get_version(index) symbol_version['name'] = vernaux.name symbol_version['filename'] = verneed.name symbol_version['index'] = index return symbol_version
class ELFImage(object): """ Program image in the emulator """ def __init__(self, image_filename, symbols_only=False): """ Read the ELF headers and parse the executable """ self._name = os.path.basename(image_filename) self._name_cache = dict() # names are unique, entries are subdicts with 'address' and 'size' self._address_cache = dict() # addresses are not unique, entries are lists of names at that address self._symbol_index = None # sorted list of unique symbol addresses + sentinel self._symbols_only = symbols_only self._relocation = 0 self._elf = ELFFile(open(image_filename, "rb")) if self._elf.header['e_type'] != 'ET_EXEC': raise RuntimeError('not an ELF executable file') if self._elf.header['e_machine'] != 'EM_68K': raise RuntimeError('not an M68K ELF file') if self._elf.num_segments() == 0: raise RuntimeError('no segments in ELF file') # build the symbol cache for section in self._elf.iter_sections(): if isinstance(section, SymbolTableSection): self._cache_symbols(section) # look for a stack stack_size = None if not self._symbols_only: for segment in self._elf.iter_segments(): if segment['p_type'] == 'PT_GNU_STACK': stack_size = segment['p_memsz'] if stack_size is None: raise RuntimeError(f'no stack defined in {self._name} - did you forget to link with -z stack-size=VALUE?') else: try: stack_base = self._name_cache['_end']['address'] except KeyError: raise RuntimeError('no _end symbol, cannot locate stack') self._add_symbol('__STACK__', stack_base, stack_size) # sort symbol addresses to make index self._symbol_index = sorted(self._address_cache.keys()) if len(self._symbol_index) == 0: raise RuntimeError(f'no symbols in {image_filename}') def _add_symbol(self, name, address, size): self._name_cache[name] = {'address': address, 'size': size} try: self._address_cache[address].append(name) except KeyError: self._address_cache[address] = [name] def _get_loadable_sections(self): if self._symbols_only: raise RuntimeError(f'loaded for symbols-only') loadable_sections = dict() for section in self._elf.iter_sections(): if section['sh_flags'] & SH_FLAGS.SHF_ALLOC: loadable_sections[section['sh_addr']] = bytearray(section.data()) return loadable_sections def relocate(self, relocation): # find sections that we want to load loadable_sections = self._get_loadable_sections() # iterate relocation sections did_relocate = False for section in self._elf.iter_sections(): if not isinstance(section, RelocationSection): continue # do these relocates affect a loaded section? reloc_section = self._elf.get_section(section['sh_info']) if not (reloc_section['sh_flags'] & SH_FLAGS.SHF_ALLOC): continue # iterate relocations for reloc in section.iter_relocations(): if not reloc.is_RELA(): raise RuntimeError('unexpected REL reloc') # Only R_68K_32 relocations are of interest if reloc['r_info_type'] != R_68K_32: continue # find the section containing the address that needs to be fixed up reloc_address = reloc['r_offset'] for sec_base, sec_data in loadable_sections.items(): sec_offset = reloc_address - sec_base if (sec_base <= reloc_address) and (sec_offset < len(sec_data)): unrelocated_value = struct.unpack_from('>L', sec_data, sec_offset)[0] struct.pack_into('>L', sec_data, sec_offset, unrelocated_value + relocation) did_relocate = True break if not did_relocate: raise RuntimeError(f'no relocations in {self._name} - did you forget to link with --emit-relocs?') relocated_sections = dict() for sec_base, sec_data in loadable_sections.items(): relocated_sections[sec_base + relocation] = sec_data self._relocation = relocation return relocated_sections @property def entrypoint(self): if self._symbols_only: raise RuntimeError(f'loaded for symbols-only') return self._elf.header['e_entry'] + self._relocation @property def initstack(self): if self._symbols_only: raise RuntimeError(f'loaded for symbols-only') if self._stack_size is not None: end, _ = self.get_symbol_range('_end') return end + self._relocation + self._stack_size def _cache_symbols(self, section): for nsym, symbol in enumerate(section.iter_symbols()): s_name = str(symbol.name) if len(s_name) == 0: continue s_type = symbol['st_info']['type'] if s_type == 'STT_FILE': continue s_addr = symbol['st_value'] s_size = symbol['st_size'] self._name_cache[s_name] = {'address': s_addr, 'size': s_size} try: self._address_cache[s_addr].append(s_name) except KeyError: self._address_cache[s_addr] = [s_name] def get_symbol_range(self, name): try: addr = self._name_cache[name]['address'] + self._relocation size = self._name_cache[name]['size'] except KeyError: try: addr = int(name) size = 1 except Exception: raise RuntimeError(f'no symbol {name}'.format(name)) return addr, size + addr def get_symbol_name(self, address): address -= self._relocation if address in self._address_cache: return ','.join(self._address_cache[address]) index = bisect_left(self._symbol_index, address) if not index: return None symbol_address = self._symbol_index[index - 1] if address < symbol_address: return None names = list() for name in self._address_cache[symbol_address]: if (address - symbol_address) < self._name_cache[name]['size']: names.append(name) if len(names) > 0: label = ','.join(names) + f'+{address - symbol_address:#x}' return label return None
class Elf(object): def __init__(self, fileobj): self.elffile = ELFFile(fileobj) self.output = sys.stdout # our code starts here :-) def network(self): ret = "None" for section in self.elffile.iter_sections(): if not isinstance(section, SymbolTableSection): continue if section['sh_entsize'] == 0: print("\nSymbol table '%s' has a sh_entsize " "of zero!" % (bytes2str(section.name)), file=sys.stderr) continue for _, symbol in enumerate(section.iter_symbols()): # first match IP_PATTERNS for pattern in IP_PATTERNS: if re.match(pattern, bytes2str(symbol.name)): return "network-ip" # then match LOCAL_PATTERNS for pattern in LOCAL_PATTERNS: if re.match(pattern, bytes2str(symbol.name)): ret = "network-local" break return ret def _strings(self): stream = self.elffile.stream epos = stream.tell() stream.seek(0, 0) data = stream.read() stream.seek(epos, 0) ret = [] # XXX avoid calling eu-strings import subprocess p = subprocess.Popen("eu-strings", shell=True, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE) out = p.communicate(input=data)[0] for line in out.splitlines(): if re.match(b"^/tmp/.+", line) and "XXX" not in line: ret.append(line) return ret def tempstuff(self): tmp_strings = self._strings() # if there are no /tmp references, just return if len(tmp_strings) == 0: return "None" for section in self.elffile.iter_sections(): if not isinstance(section, SymbolTableSection): continue if section['sh_entsize'] == 0: print("\nSymbol table '%s' has a sh_entsize " "of zero!" % (bytes2str(section.name)), file=sys.stderr) continue for _, symbol in enumerate(section.iter_symbols()): for pattern in TMP_FUNCTIONS: if re.match(pattern, bytes2str(symbol.name)): return "None" return "$".join(tmp_strings) # XXX implement this def chroot_without_chdir(self): """ Check for apps that use chroot(2) without using chdir(2). Inspired by http://people.redhat.com/sgrubb/security/find-chroot """ pass def fortify(self): """ Check if source code was compiled with FORTIFY_SOURCE. Enabled : no unsafe functions were found OR all were translated to _chk versions Partial : unprotected unsafe functions were found TODO ==== * Print summary report like checksec.sh does * Drop CSV output support (it is too restrictive) * "addr2line" like feature for unprotected unsafe functions """ unsafe_list = [] for section in self.elffile.iter_sections(): if not isinstance(section, SymbolTableSection): continue if section['sh_entsize'] == 0: print("\nSymbol table '%s' has a sh_entsize " "of zero!" % (bytes2str(section.name)), file=sys.stderr) continue for _, symbol in enumerate(section.iter_symbols()): for pattern in UNSAFE_FUNCTIONS: if re.match(pattern + "$", bytes2str(symbol.name)): unsafe_list.append(bytes2str(symbol.name)) if len(unsafe_list) == 0: return "Enabled" else: return "Partial$" + "$".join(unsafe_list) def canary(self): for section in self.elffile.iter_sections(): if not isinstance(section, SymbolTableSection): continue if section['sh_entsize'] == 0: print("\nSymbol table '%s' has a sh_entsize " "of zero!" % (bytes2str(section.name)), file=sys.stderr) continue for _, symbol in enumerate(section.iter_symbols()): if bytes2str(symbol.name) in STACK_CHK: return "Enabled" return "Disabled" def dynamic_tags(self, key="DT_RPATH"): for section in self.elffile.iter_sections(): if not isinstance(section, DynamicSection): continue for tag in section.iter_tags(): if tag.entry.d_tag == key: return "Enabled" return "Disabled" def program_headers(self): pflags = P_FLAGS() if self.elffile.num_segments() == 0: # print('There are no program headers in this file.', \ # file=sys.stderr) return found = False for segment in self.elffile.iter_segments(): if re.search("GNU_STACK", str(segment['p_type'])): found = True if segment['p_flags'] & pflags.PF_X: return "Disabled" if found: return "Enabled" return "Disabled" def relro(self): if self.elffile.num_segments() == 0: # print('There are no program headers in this file.', \ # file=sys.stderr) return have_relro = False for segment in self.elffile.iter_segments(): if re.search("GNU_RELRO", str(segment['p_type'])): have_relro = True break if self.dynamic_tags("DT_BIND_NOW") == "Enabled" and have_relro: return "Enabled" if have_relro: return "Partial" return "Disabled" def pie(self): header = self.elffile.header if self.dynamic_tags("EXEC") == "Enabled": return "Disabled" if "ET_DYN" in header['e_type']: if self.dynamic_tags("DT_DEBUG") == "Enabled": return "Enabled" else: return "DSO" return "Disabled" def getdeps(self): deps = [] if self.elffile.num_segments() == 0: return deps for segment in self.elffile.iter_segments(): if re.search("PT_DYNAMIC", str(segment['p_type'])): # this file uses dynamic linking, so read the dynamic section # and find DT_SONAME tag for section in self.elffile.iter_sections(): if not isinstance(section, DynamicSection): continue for tag in section.iter_tags(): if tag.entry.d_tag == 'DT_NEEDED': deps.append(bytes2str(tag.needed)) break return deps
#define RESPOND_FRAME_SIZE 0x04 #define END_FLASH_SIZE 0x04 FLASH_NEW_APP_SIZE = 0X10 FLASH_WRITE_SECTOR_SIZE = 0X0C + (MAX_DATA_SIZE) RESPOND_FRAME_SIZE = 0x04 END_FLASH_SIZE = 0x04 DUMMY_BYTE = 0 ########################################################################################################## port = "COM29" ser = serialwin32.Serial(port, 9600) file = open(sys.argv[1], "rb") elf_Handler = ELFFile(file) Header = elf_Handler.header #elf file header Magic = Header.e_ident['EI_MAG'] #magic number EntryPoint = Header.e_entry #entry point program_header_number = elf_Handler.num_segments( ) #number of segments in the elf file elf_Class = elf_Handler.elfclass #elf file class segments_header = [] segments = [] Req_num = 0 #Verification function def verify_Elf_File(): if Magic[0] != 127 or Magic[1] != 69 or Magic[2] != 76 or Magic[ 3] != 70 or elf_Class != 32: return False else: return True
class ReadElf(object): """ display_* methods are used to emit output into the output stream """ def __init__(self, file, output): """ file: stream object with the ELF file to read output: output stream to write to """ self.elffile = ELFFile(file) self.output = output # Lazily initialized if a debug dump is requested self._dwarfinfo = None def display_file_header(self): """ Display the ELF file header """ self._emitline('ELF Header:') self._emit(' Magic: ') self._emitline(' '.join('%2.2x' % byte2int(b) for b in self.elffile.e_ident_raw)) header = self.elffile.header e_ident = header['e_ident'] self._emitline(' Class: %s' % describe_ei_class(e_ident['EI_CLASS'])) self._emitline(' Data: %s' % describe_ei_data(e_ident['EI_DATA'])) self._emitline(' Version: %s' % describe_ei_version(e_ident['EI_VERSION'])) self._emitline(' OS/ABI: %s' % describe_ei_osabi(e_ident['EI_OSABI'])) self._emitline(' ABI Version: %d' % e_ident['EI_ABIVERSION']) self._emitline(' Type: %s' % describe_e_type(header['e_type'])) self._emitline(' Machine: %s' % describe_e_machine(header['e_machine'])) self._emitline(' Version: %s' % describe_e_version_numeric(header['e_version'])) self._emitline(' Entry point address: %s' % self._format_hex(header['e_entry'])) self._emit(' Start of program headers: %s' % header['e_phoff']) self._emitline(' (bytes into file)') self._emit(' Start of section headers: %s' % header['e_shoff']) self._emitline(' (bytes into file)') self._emitline(' Flags: %s' % self._format_hex(header['e_flags'])) self._emitline(' Size of this header: %s (bytes)' % header['e_ehsize']) self._emitline(' Size of program headers: %s (bytes)' % header['e_phentsize']) self._emitline(' Number of program headers: %s' % header['e_phnum']) self._emitline(' Size of section headers: %s (bytes)' % header['e_shentsize']) self._emitline(' Number of section headers: %s' % header['e_shnum']) self._emitline(' Section header string table index: %s' % header['e_shstrndx']) def display_program_headers(self, show_heading=True): """ Display the ELF program headers. If show_heading is True, displays the heading for this information (Elf file type is...) """ self._emitline() if self.elffile.num_segments() == 0: self._emitline('There are no program headers in this file.') return elfheader = self.elffile.header if show_heading: self._emitline('Elf file type is %s' % describe_e_type(elfheader['e_type'])) self._emitline('Entry point is %s' % self._format_hex(elfheader['e_entry'])) # readelf weirness - why isn't e_phoff printed as hex? (for section # headers, it is...) self._emitline('There are %s program headers, starting at offset %s' % ( elfheader['e_phnum'], elfheader['e_phoff'])) self._emitline() self._emitline('Program Headers:') # Now comes the table of program headers with their attributes. Note # that due to different formatting constraints of 32-bit and 64-bit # addresses, there are some conditions on elfclass here. # # First comes the table heading # if self.elffile.elfclass == 32: self._emitline(' Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align') else: self._emitline(' Type Offset VirtAddr PhysAddr') self._emitline(' FileSiz MemSiz Flags Align') # Now the entries # for segment in self.elffile.iter_segments(): self._emit(' %-14s ' % describe_p_type(segment['p_type'])) if self.elffile.elfclass == 32: self._emitline('%s %s %s %s %s %-3s %s' % ( self._format_hex(segment['p_offset'], fieldsize=6), self._format_hex(segment['p_vaddr'], fullhex=True), self._format_hex(segment['p_paddr'], fullhex=True), self._format_hex(segment['p_filesz'], fieldsize=5), self._format_hex(segment['p_memsz'], fieldsize=5), describe_p_flags(segment['p_flags']), self._format_hex(segment['p_align']))) else: # 64 self._emitline('%s %s %s' % ( self._format_hex(segment['p_offset'], fullhex=True), self._format_hex(segment['p_vaddr'], fullhex=True), self._format_hex(segment['p_paddr'], fullhex=True))) self._emitline(' %s %s %-3s %s' % ( self._format_hex(segment['p_filesz'], fullhex=True), self._format_hex(segment['p_memsz'], fullhex=True), describe_p_flags(segment['p_flags']), # lead0x set to False for p_align, to mimic readelf. # No idea why the difference from 32-bit mode :-| self._format_hex(segment['p_align'], lead0x=False))) if isinstance(segment, InterpSegment): self._emitline(' [Requesting program interpreter: %s]' % bytes2str(segment.get_interp_name())) # Sections to segments mapping # if self.elffile.num_sections() == 0: # No sections? We're done return self._emitline('\n Section to Segment mapping:') self._emitline(' Segment Sections...') for nseg, segment in enumerate(self.elffile.iter_segments()): self._emit(' %2.2d ' % nseg) for section in self.elffile.iter_sections(): if ( not section.is_null() and segment.section_in_segment(section)): self._emit('%s ' % bytes2str(section.name)) self._emitline('') def display_section_headers(self, show_heading=True): """ Display the ELF section headers """ elfheader = self.elffile.header if show_heading: self._emitline('There are %s section headers, starting at offset %s' % ( elfheader['e_shnum'], self._format_hex(elfheader['e_shoff']))) self._emitline('\nSection Header%s:' % ( 's' if elfheader['e_shnum'] > 1 else '')) # Different formatting constraints of 32-bit and 64-bit addresses # if self.elffile.elfclass == 32: self._emitline(' [Nr] Name Type Addr Off Size ES Flg Lk Inf Al') else: self._emitline(' [Nr] Name Type Address Offset') self._emitline(' Size EntSize Flags Link Info Align') # Now the entries # for nsec, section in enumerate(self.elffile.iter_sections()): self._emit(' [%2u] %-17.17s %-15.15s ' % ( nsec, bytes2str(section.name), describe_sh_type(section['sh_type']))) if self.elffile.elfclass == 32: self._emitline('%s %s %s %s %3s %2s %3s %2s' % ( self._format_hex(section['sh_addr'], fieldsize=8, lead0x=False), self._format_hex(section['sh_offset'], fieldsize=6, lead0x=False), self._format_hex(section['sh_size'], fieldsize=6, lead0x=False), self._format_hex(section['sh_entsize'], fieldsize=2, lead0x=False), describe_sh_flags(section['sh_flags']), section['sh_link'], section['sh_info'], section['sh_addralign'])) else: # 64 self._emitline(' %s %s' % ( self._format_hex(section['sh_addr'], fullhex=True, lead0x=False), self._format_hex(section['sh_offset'], fieldsize=16 if section['sh_offset'] > 0xffffffff else 8, lead0x=False))) self._emitline(' %s %s %3s %2s %3s %s' % ( self._format_hex(section['sh_size'], fullhex=True, lead0x=False), self._format_hex(section['sh_entsize'], fullhex=True, lead0x=False), describe_sh_flags(section['sh_flags']), section['sh_link'], section['sh_info'], section['sh_addralign'])) self._emitline('Key to Flags:') self._emit(' W (write), A (alloc), X (execute), M (merge), S (strings)') if self.elffile['e_machine'] in ('EM_X86_64', 'EM_L10M'): self._emitline(', l (large)') else: self._emitline() self._emitline(' I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)') self._emitline(' O (extra OS processing required) o (OS specific), p (processor specific)') def display_symbol_tables(self): """ Display the symbol tables contained in the file """ for section in self.elffile.iter_sections(): if not isinstance(section, SymbolTableSection): continue if section['sh_entsize'] == 0: self._emitline("\nSymbol table '%s' has a sh_entsize of zero!" % ( bytes2str(section.name))) continue self._emitline("\nSymbol table '%s' contains %s entries:" % ( bytes2str(section.name), section.num_symbols())) if self.elffile.elfclass == 32: self._emitline(' Num: Value Size Type Bind Vis Ndx Name') else: # 64 self._emitline(' Num: Value Size Type Bind Vis Ndx Name') for nsym, symbol in enumerate(section.iter_symbols()): # symbol names are truncated to 25 chars, similarly to readelf self._emitline('%6d: %s %5d %-7s %-6s %-7s %4s %.25s' % ( nsym, self._format_hex(symbol['st_value'], fullhex=True, lead0x=False), symbol['st_size'], describe_symbol_type(symbol['st_info']['type']), describe_symbol_bind(symbol['st_info']['bind']), describe_symbol_visibility(symbol['st_other']['visibility']), describe_symbol_shndx(symbol['st_shndx']), bytes2str(symbol.name))) def display_relocations(self): """ Display the relocations contained in the file """ has_relocation_sections = False for section in self.elffile.iter_sections(): if not isinstance(section, RelocationSection): continue has_relocation_sections = True self._emitline("\nRelocation section '%s' at offset %s contains %s entries:" % ( bytes2str(section.name), self._format_hex(section['sh_offset']), section.num_relocations())) if section.is_RELA(): self._emitline(" Offset Info Type Sym. Value Sym. Name + Addend") else: self._emitline(" Offset Info Type Sym.Value Sym. Name") # The symbol table section pointed to in sh_link symtable = self.elffile.get_section(section['sh_link']) for rel in section.iter_relocations(): hexwidth = 8 if self.elffile.elfclass == 32 else 12 self._emit('%s %s %-17.17s' % ( self._format_hex(rel['r_offset'], fieldsize=hexwidth, lead0x=False), self._format_hex(rel['r_info'], fieldsize=hexwidth, lead0x=False), describe_reloc_type( rel['r_info_type'], self.elffile))) if rel['r_info_sym'] == 0: self._emitline() continue symbol = symtable.get_symbol(rel['r_info_sym']) # Some symbols have zero 'st_name', so instead what's used is # the name of the section they point at if symbol['st_name'] == 0: symsec = self.elffile.get_section(symbol['st_shndx']) symbol_name = symsec.name else: symbol_name = symbol.name self._emit(' %s %s%22.22s' % ( self._format_hex( symbol['st_value'], fullhex=True, lead0x=False), ' ' if self.elffile.elfclass == 32 else '', bytes2str(symbol_name))) if section.is_RELA(): self._emit(' %s %x' % ( '+' if rel['r_addend'] >= 0 else '-', abs(rel['r_addend']))) self._emitline() if not has_relocation_sections: self._emitline('\nThere are no relocations in this file.') def display_hex_dump(self, section_spec): """ Display a hex dump of a section. section_spec is either a section number or a name. """ section = self._section_from_spec(section_spec) if section is None: self._emitline("Section '%s' does not exist in the file!" % ( section_spec)) return self._emitline("\nHex dump of section '%s':" % bytes2str(section.name)) self._note_relocs_for_section(section) addr = section['sh_addr'] data = section.data() dataptr = 0 while dataptr < len(data): bytesleft = len(data) - dataptr # chunks of 16 bytes per line linebytes = 16 if bytesleft > 16 else bytesleft self._emit(' %s ' % self._format_hex(addr, fieldsize=8)) for i in range(16): if i < linebytes: self._emit('%2.2x' % byte2int(data[dataptr + i])) else: self._emit(' ') if i % 4 == 3: self._emit(' ') for i in range(linebytes): c = data[dataptr + i : dataptr + i + 1] if byte2int(c[0]) >= 32 and byte2int(c[0]) < 0x7f: self._emit(bytes2str(c)) else: self._emit(bytes2str(b'.')) self._emitline() addr += linebytes dataptr += linebytes self._emitline() def display_string_dump(self, section_spec): """ Display a strings dump of a section. section_spec is either a section number or a name. """ section = self._section_from_spec(section_spec) if section is None: self._emitline("Section '%s' does not exist in the file!" % ( section_spec)) return self._emitline("\nString dump of section '%s':" % bytes2str(section.name)) found = False data = section.data() dataptr = 0 while dataptr < len(data): while ( dataptr < len(data) and not (32 <= byte2int(data[dataptr]) <= 127)): dataptr += 1 if dataptr >= len(data): break endptr = dataptr while endptr < len(data) and byte2int(data[endptr]) != 0: endptr += 1 found = True self._emitline(' [%6x] %s' % ( dataptr, bytes2str(data[dataptr:endptr]))) dataptr = endptr if not found: self._emitline(' No strings found in this section.') else: self._emitline() def display_debug_dump(self, dump_what): """ Dump a DWARF section """ self._init_dwarfinfo() if self._dwarfinfo is None: return set_global_machine_arch(self.elffile.get_machine_arch()) if dump_what == 'info': self._dump_debug_info() elif dump_what == 'decodedline': self._dump_debug_line_programs() elif dump_what == 'frames': self._dump_debug_frames() elif dump_what == 'frames-interp': self._dump_debug_frames_interp() else: self._emitline('debug dump not yet supported for "%s"' % dump_what) def _format_hex(self, addr, fieldsize=None, fullhex=False, lead0x=True): """ Format an address into a hexadecimal string. fieldsize: Size of the hexadecimal field (with leading zeros to fit the address into. For example with fieldsize=8, the format will be %08x If None, the minimal required field size will be used. fullhex: If True, override fieldsize to set it to the maximal size needed for the elfclass lead0x: If True, leading 0x is added """ s = '0x' if lead0x else '' if fullhex: fieldsize = 8 if self.elffile.elfclass == 32 else 16 if fieldsize is None: field = '%x' else: field = '%' + '0%sx' % fieldsize return s + field % addr def _section_from_spec(self, spec): """ Retrieve a section given a "spec" (either number or name). Return None if no such section exists in the file. """ try: num = int(spec) if num < self.elffile.num_sections(): return self.elffile.get_section(num) else: return None except ValueError: # Not a number. Must be a name then return self.elffile.get_section_by_name(str2bytes(spec)) def _note_relocs_for_section(self, section): """ If there are relocation sections pointing to the givne section, emit a note about it. """ for relsec in self.elffile.iter_sections(): if isinstance(relsec, RelocationSection): info_idx = relsec['sh_info'] if self.elffile.get_section(info_idx) == section: self._emitline(' Note: This section has relocations against it, but these have NOT been applied to this dump.') return def _init_dwarfinfo(self): """ Initialize the DWARF info contained in the file and assign it to self._dwarfinfo. Leave self._dwarfinfo at None if no DWARF info was found in the file """ if self._dwarfinfo is not None: return if self.elffile.has_dwarf_info(): self._dwarfinfo = self.elffile.get_dwarf_info() else: self._dwarfinfo = None def _dump_debug_info(self): """ Dump the debugging info section. """ self._emitline('Contents of the .debug_info section:\n') # Offset of the .debug_info section in the stream section_offset = self._dwarfinfo.debug_info_sec.global_offset for cu in self._dwarfinfo.iter_CUs(): self._emitline(' Compilation Unit @ offset %s:' % self._format_hex(cu.cu_offset)) self._emitline(' Length: %s (%s)' % ( self._format_hex(cu['unit_length']), '%s-bit' % cu.dwarf_format())) self._emitline(' Version: %s' % cu['version']), self._emitline(' Abbrev Offset: %s' % cu['debug_abbrev_offset']), self._emitline(' Pointer Size: %s' % cu['address_size']) # The nesting depth of each DIE within the tree of DIEs must be # displayed. To implement this, a counter is incremented each time # the current DIE has children, and decremented when a null die is # encountered. Due to the way the DIE tree is serialized, this will # correctly reflect the nesting depth # die_depth = 0 for die in cu.iter_DIEs(): if die.is_null(): die_depth -= 1 continue self._emitline(' <%s><%x>: Abbrev Number: %s (%s)' % ( die_depth, die.offset, die.abbrev_code, die.tag)) for attr in itervalues(die.attributes): name = attr.name # Unknown attribute values are passed-through as integers if isinstance(name, int): name = 'Unknown AT value: %x' % name self._emitline(' <%2x> %-18s: %s' % ( attr.offset, name, describe_attr_value( attr, die, section_offset))) if die.has_children: die_depth += 1 self._emitline() def _dump_debug_line_programs(self): """ Dump the (decoded) line programs from .debug_line The programs are dumped in the order of the CUs they belong to. """ self._emitline('Decoded dump of debug contents of section .debug_line:\n') for cu in self._dwarfinfo.iter_CUs(): lineprogram = self._dwarfinfo.line_program_for_CU(cu) cu_filename = '' if len(lineprogram['include_directory']) > 0: cu_filename = '%s/%s' % ( bytes2str(lineprogram['include_directory'][0]), bytes2str(lineprogram['file_entry'][0].name)) else: cu_filename = bytes2str(lineprogram['file_entry'][0].name) self._emitline('CU: %s:' % cu_filename) self._emitline('File name Line number Starting address') # Print each state's file, line and address information. For some # instructions other output is needed to be compatible with # readelf. for entry in lineprogram.get_entries(): state = entry.state if state is None: # Special handling for commands that don't set a new state if entry.command == DW_LNS_set_file: file_entry = lineprogram['file_entry'][entry.args[0] - 1] if file_entry.dir_index == 0: # current directory self._emitline('\n./%s:[++]' % ( bytes2str(file_entry.name))) else: self._emitline('\n%s/%s:' % ( bytes2str(lineprogram['include_directory'][file_entry.dir_index - 1]), bytes2str(file_entry.name))) elif entry.command == DW_LNE_define_file: self._emitline('%s:' % ( bytes2str(lineprogram['include_directory'][entry.args[0].dir_index]))) elif not state.end_sequence: # readelf doesn't print the state after end_sequence # instructions. I think it's a bug but to be compatible # I don't print them too. self._emitline('%-35s %11d %18s' % ( bytes2str(lineprogram['file_entry'][state.file - 1].name), state.line, '0' if state.address == 0 else self._format_hex(state.address))) if entry.command == DW_LNS_copy: # Another readelf oddity... self._emitline() def _dump_debug_frames(self): """ Dump the raw frame information from .debug_frame """ if not self._dwarfinfo.has_CFI(): return self._emitline('Contents of the .debug_frame section:') for entry in self._dwarfinfo.CFI_entries(): if isinstance(entry, CIE): self._emitline('\n%08x %08x %08x CIE' % ( entry.offset, entry['length'], entry['CIE_id'])) self._emitline(' Version: %d' % entry['version']) self._emitline(' Augmentation: "%s"' % bytes2str(entry['augmentation'])) self._emitline(' Code alignment factor: %u' % entry['code_alignment_factor']) self._emitline(' Data alignment factor: %d' % entry['data_alignment_factor']) self._emitline(' Return address column: %d' % entry['return_address_register']) self._emitline() else: # FDE self._emitline('\n%08x %08x %08x FDE cie=%08x pc=%08x..%08x' % ( entry.offset, entry['length'], entry['CIE_pointer'], entry.cie.offset, entry['initial_location'], entry['initial_location'] + entry['address_range'])) self._emit(describe_CFI_instructions(entry)) self._emitline() def _dump_debug_frames_interp(self): """ Dump the interpreted (decoded) frame information from .debug_frame """ if not self._dwarfinfo.has_CFI(): return self._emitline('Contents of the .debug_frame section:') for entry in self._dwarfinfo.CFI_entries(): if isinstance(entry, CIE): self._emitline('\n%08x %08x %08x CIE "%s" cf=%d df=%d ra=%d' % ( entry.offset, entry['length'], entry['CIE_id'], bytes2str(entry['augmentation']), entry['code_alignment_factor'], entry['data_alignment_factor'], entry['return_address_register'])) ra_regnum = entry['return_address_register'] else: # FDE self._emitline('\n%08x %08x %08x FDE cie=%08x pc=%08x..%08x' % ( entry.offset, entry['length'], entry['CIE_pointer'], entry.cie.offset, entry['initial_location'], entry['initial_location'] + entry['address_range'])) ra_regnum = entry.cie['return_address_register'] # Print the heading row for the decoded table self._emit(' LOC') self._emit(' ' if entry.structs.address_size == 4 else ' ') self._emit(' CFA ') # Decode the table nad look at the registers it describes. # We build reg_order here to match readelf's order. In particular, # registers are sorted by their number, and the register matching # ra_regnum is always listed last with a special heading. decoded_table = entry.get_decoded() reg_order = sorted(ifilter( lambda r: r != ra_regnum, decoded_table.reg_order)) # Headings for the registers for regnum in reg_order: self._emit('%-6s' % describe_reg_name(regnum)) self._emitline('ra ') # Now include ra_regnum in reg_order to print its values similarly # to the other registers. reg_order.append(ra_regnum) for line in decoded_table.table: self._emit(self._format_hex( line['pc'], fullhex=True, lead0x=False)) self._emit(' %-9s' % describe_CFI_CFA_rule(line['cfa'])) for regnum in reg_order: if regnum in line: s = describe_CFI_register_rule(line[regnum]) else: s = 'u' self._emit('%-6s' % s) self._emitline() self._emitline() def _emit(self, s=''): """ Emit an object to output """ self.output.write(str(s)) def _emitline(self, s=''): """ Emit an object to output, followed by a newline """ self.output.write(str(s) + '\n')
class ReadElf(object): """ display_* methods are used to emit output into the output stream """ def __init__(self, file, output): """ file: stream object with the ELF file to read output: output stream to write to """ self.elffile = ELFFile(file) self.output = output # Lazily initialized if a debug dump is requested self._dwarfinfo = None self._versioninfo = None def display_file_header(self): """ Display the ELF file header """ self._emitline('ELF Header:') self._emit(' Magic: ') self._emitline(' '.join('%2.2x' % byte2int(b) for b in self.elffile.e_ident_raw)) header = self.elffile.header e_ident = header['e_ident'] self._emitline(' Class: %s' % describe_ei_class(e_ident['EI_CLASS'])) self._emitline(' Data: %s' % describe_ei_data(e_ident['EI_DATA'])) self._emitline(' Version: %s' % describe_ei_version(e_ident['EI_VERSION'])) self._emitline(' OS/ABI: %s' % describe_ei_osabi(e_ident['EI_OSABI'])) self._emitline(' ABI Version: %d' % e_ident['EI_ABIVERSION']) self._emitline(' Type: %s' % describe_e_type(header['e_type'])) self._emitline(' Machine: %s' % describe_e_machine(header['e_machine'])) self._emitline(' Version: %s' % describe_e_version_numeric(header['e_version'])) self._emitline(' Entry point address: %s' % self._format_hex(header['e_entry'])) self._emit(' Start of program headers: %s' % header['e_phoff']) self._emitline(' (bytes into file)') self._emit(' Start of section headers: %s' % header['e_shoff']) self._emitline(' (bytes into file)') self._emitline(' Flags: %s%s' % (self._format_hex(header['e_flags']), self.decode_flags(header['e_flags']))) self._emitline(' Size of this header: %s (bytes)' % header['e_ehsize']) self._emitline(' Size of program headers: %s (bytes)' % header['e_phentsize']) self._emitline(' Number of program headers: %s' % header['e_phnum']) self._emitline(' Size of section headers: %s (bytes)' % header['e_shentsize']) self._emitline(' Number of section headers: %s' % header['e_shnum']) self._emitline(' Section header string table index: %s' % header['e_shstrndx']) def decode_flags(self, flags): description = "" if self.elffile['e_machine'] == "EM_ARM": if flags & E_FLAGS.EF_ARM_HASENTRY: description += ", has entry point" version = flags & E_FLAGS.EF_ARM_EABIMASK if version == E_FLAGS.EF_ARM_EABI_VER5: description += ", Version5 EABI" return description def display_program_headers(self, show_heading=True): """ Display the ELF program headers. If show_heading is True, displays the heading for this information (Elf file type is...) """ self._emitline() if self.elffile.num_segments() == 0: self._emitline('There are no program headers in this file.') return elfheader = self.elffile.header if show_heading: self._emitline('Elf file type is %s' % describe_e_type(elfheader['e_type'])) self._emitline('Entry point is %s' % self._format_hex(elfheader['e_entry'])) # readelf weirness - why isn't e_phoff printed as hex? (for section # headers, it is...) self._emitline('There are %s program headers, starting at offset %s' % ( elfheader['e_phnum'], elfheader['e_phoff'])) self._emitline() self._emitline('Program Headers:') # Now comes the table of program headers with their attributes. Note # that due to different formatting constraints of 32-bit and 64-bit # addresses, there are some conditions on elfclass here. # # First comes the table heading # if self.elffile.elfclass == 32: self._emitline(' Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align') else: self._emitline(' Type Offset VirtAddr PhysAddr') self._emitline(' FileSiz MemSiz Flags Align') # Now the entries # for segment in self.elffile.iter_segments(): self._emit(' %-14s ' % describe_p_type(segment['p_type'])) if self.elffile.elfclass == 32: self._emitline('%s %s %s %s %s %-3s %s' % ( self._format_hex(segment['p_offset'], fieldsize=6), self._format_hex(segment['p_vaddr'], fullhex=True), self._format_hex(segment['p_paddr'], fullhex=True), self._format_hex(segment['p_filesz'], fieldsize=5), self._format_hex(segment['p_memsz'], fieldsize=5), describe_p_flags(segment['p_flags']), self._format_hex(segment['p_align']))) else: # 64 self._emitline('%s %s %s' % ( self._format_hex(segment['p_offset'], fullhex=True), self._format_hex(segment['p_vaddr'], fullhex=True), self._format_hex(segment['p_paddr'], fullhex=True))) self._emitline(' %s %s %-3s %s' % ( self._format_hex(segment['p_filesz'], fullhex=True), self._format_hex(segment['p_memsz'], fullhex=True), describe_p_flags(segment['p_flags']), # lead0x set to False for p_align, to mimic readelf. # No idea why the difference from 32-bit mode :-| self._format_hex(segment['p_align'], lead0x=False))) if isinstance(segment, InterpSegment): self._emitline(' [Requesting program interpreter: %s]' % bytes2str(segment.get_interp_name())) # Sections to segments mapping # if self.elffile.num_sections() == 0: # No sections? We're done return self._emitline('\n Section to Segment mapping:') self._emitline(' Segment Sections...') for nseg, segment in enumerate(self.elffile.iter_segments()): self._emit(' %2.2d ' % nseg) for section in self.elffile.iter_sections(): if ( not section.is_null() and segment.section_in_segment(section)): self._emit('%s ' % bytes2str(section.name)) self._emitline('') def display_section_headers(self, show_heading=True): """ Display the ELF section headers """ elfheader = self.elffile.header if show_heading: self._emitline('There are %s section headers, starting at offset %s' % ( elfheader['e_shnum'], self._format_hex(elfheader['e_shoff']))) self._emitline('\nSection Header%s:' % ( 's' if elfheader['e_shnum'] > 1 else '')) # Different formatting constraints of 32-bit and 64-bit addresses # if self.elffile.elfclass == 32: self._emitline(' [Nr] Name Type Addr Off Size ES Flg Lk Inf Al') else: self._emitline(' [Nr] Name Type Address Offset') self._emitline(' Size EntSize Flags Link Info Align') # Now the entries # for nsec, section in enumerate(self.elffile.iter_sections()): self._emit(' [%2u] %-17.17s %-15.15s ' % ( nsec, bytes2str(section.name), describe_sh_type(section['sh_type']))) if self.elffile.elfclass == 32: self._emitline('%s %s %s %s %3s %2s %3s %2s' % ( self._format_hex(section['sh_addr'], fieldsize=8, lead0x=False), self._format_hex(section['sh_offset'], fieldsize=6, lead0x=False), self._format_hex(section['sh_size'], fieldsize=6, lead0x=False), self._format_hex(section['sh_entsize'], fieldsize=2, lead0x=False), describe_sh_flags(section['sh_flags']), section['sh_link'], section['sh_info'], section['sh_addralign'])) else: # 64 self._emitline(' %s %s' % ( self._format_hex(section['sh_addr'], fullhex=True, lead0x=False), self._format_hex(section['sh_offset'], fieldsize=16 if section['sh_offset'] > 0xffffffff else 8, lead0x=False))) self._emitline(' %s %s %3s %2s %3s %s' % ( self._format_hex(section['sh_size'], fullhex=True, lead0x=False), self._format_hex(section['sh_entsize'], fullhex=True, lead0x=False), describe_sh_flags(section['sh_flags']), section['sh_link'], section['sh_info'], section['sh_addralign'])) self._emitline('Key to Flags:') self._emit(' W (write), A (alloc), X (execute), M (merge), S (strings)') if self.elffile['e_machine'] in ('EM_X86_64', 'EM_L10M'): self._emitline(', l (large)') else: self._emitline() self._emitline(' I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)') self._emitline(' O (extra OS processing required) o (OS specific), p (processor specific)') def display_symbol_tables(self): """ Display the symbol tables contained in the file """ self._init_versioninfo() for section in self.elffile.iter_sections(): if not isinstance(section, SymbolTableSection): continue if section['sh_entsize'] == 0: self._emitline("\nSymbol table '%s' has a sh_entsize of zero!" % ( bytes2str(section.name))) continue self._emitline("\nSymbol table '%s' contains %s entries:" % ( bytes2str(section.name), section.num_symbols())) if self.elffile.elfclass == 32: self._emitline(' Num: Value Size Type Bind Vis Ndx Name') else: # 64 self._emitline(' Num: Value Size Type Bind Vis Ndx Name') for nsym, symbol in enumerate(section.iter_symbols()): version_info = '' # readelf doesn't display version info for Solaris versioning if (section['sh_type'] == 'SHT_DYNSYM' and self._versioninfo['type'] == 'GNU'): version = self._symbol_version(nsym) if (version['name'] != bytes2str(symbol.name) and version['index'] not in ('VER_NDX_LOCAL', 'VER_NDX_GLOBAL')): if version['filename']: # external symbol version_info = '@%(name)s (%(index)i)' % version else: # internal symbol if version['hidden']: version_info = '@%(name)s' % version else: version_info = '@@%(name)s' % version # symbol names are truncated to 25 chars, similarly to readelf self._emitline('%6d: %s %5d %-7s %-6s %-7s %4s %.25s%s' % ( nsym, self._format_hex( symbol['st_value'], fullhex=True, lead0x=False), symbol['st_size'], describe_symbol_type(symbol['st_info']['type']), describe_symbol_bind(symbol['st_info']['bind']), describe_symbol_visibility(symbol['st_other']['visibility']), describe_symbol_shndx(symbol['st_shndx']), bytes2str(symbol.name), version_info)) def display_dynamic_tags(self): """ Display the dynamic tags contained in the file """ has_dynamic_sections = False for section in self.elffile.iter_sections(): if not isinstance(section, DynamicSection): continue has_dynamic_sections = True self._emitline("\nDynamic section at offset %s contains %s entries:" % ( self._format_hex(section['sh_offset']), section.num_tags())) self._emitline(" Tag Type Name/Value") padding = 20 + (8 if self.elffile.elfclass == 32 else 0) for tag in section.iter_tags(): if tag.entry.d_tag == 'DT_NEEDED': parsed = 'Shared library: [%s]' % bytes2str(tag.needed) elif tag.entry.d_tag == 'DT_RPATH': parsed = 'Library rpath: [%s]' % bytes2str(tag.rpath) elif tag.entry.d_tag == 'DT_RUNPATH': parsed = 'Library runpath: [%s]' % bytes2str(tag.runpath) elif tag.entry.d_tag == 'DT_SONAME': parsed = 'Library soname: [%s]' % bytes2str(tag.soname) elif (tag.entry.d_tag.endswith('SZ') or tag.entry.d_tag.endswith('ENT')): parsed = '%i (bytes)' % tag['d_val'] elif (tag.entry.d_tag.endswith('NUM') or tag.entry.d_tag.endswith('COUNT')): parsed = '%i' % tag['d_val'] elif tag.entry.d_tag == 'DT_PLTREL': s = describe_dyn_tag(tag.entry.d_val) if s.startswith('DT_'): s = s[3:] parsed = '%s' % s else: parsed = '%#x' % tag['d_val'] self._emitline(" %s %-*s %s" % ( self._format_hex(ENUM_D_TAG.get(tag.entry.d_tag, tag.entry.d_tag), fullhex=True, lead0x=True), padding, '(%s)' % (tag.entry.d_tag[3:],), parsed)) if not has_dynamic_sections: # readelf only prints this if there is at least one segment if self.elffile.num_segments(): self._emitline("\nThere is no dynamic section in this file.") def display_relocations(self): """ Display the relocations contained in the file """ has_relocation_sections = False for section in self.elffile.iter_sections(): if not isinstance(section, RelocationSection): continue has_relocation_sections = True self._emitline("\nRelocation section '%s' at offset %s contains %s entries:" % ( bytes2str(section.name), self._format_hex(section['sh_offset']), section.num_relocations())) if section.is_RELA(): self._emitline(" Offset Info Type Sym. Value Sym. Name + Addend") else: self._emitline(" Offset Info Type Sym.Value Sym. Name") # The symbol table section pointed to in sh_link symtable = self.elffile.get_section(section['sh_link']) for rel in section.iter_relocations(): hexwidth = 8 if self.elffile.elfclass == 32 else 12 self._emit('%s %s %-17.17s' % ( self._format_hex(rel['r_offset'], fieldsize=hexwidth, lead0x=False), self._format_hex(rel['r_info'], fieldsize=hexwidth, lead0x=False), describe_reloc_type( rel['r_info_type'], self.elffile))) if rel['r_info_sym'] == 0: self._emitline() continue symbol = symtable.get_symbol(rel['r_info_sym']) # Some symbols have zero 'st_name', so instead what's used is # the name of the section they point at if symbol['st_name'] == 0: symsec = self.elffile.get_section(symbol['st_shndx']) symbol_name = symsec.name else: symbol_name = symbol.name self._emit(' %s %s%22.22s' % ( self._format_hex( symbol['st_value'], fullhex=True, lead0x=False), ' ' if self.elffile.elfclass == 32 else '', bytes2str(symbol_name))) if section.is_RELA(): self._emit(' %s %x' % ( '+' if rel['r_addend'] >= 0 else '-', abs(rel['r_addend']))) self._emitline() if not has_relocation_sections: self._emitline('\nThere are no relocations in this file.') def display_version_info(self): """ Display the version info contained in the file """ self._init_versioninfo() if not self._versioninfo['type']: self._emitline("\nNo version information found in this file.") return for section in self.elffile.iter_sections(): if isinstance(section, GNUVerSymSection): self._print_version_section_header( section, 'Version symbols', lead0x=False) num_symbols = section.num_symbols() # Symbol version info are printed four by four entries for idx_by_4 in range(0, num_symbols, 4): self._emit(' %03x:' % idx_by_4) for idx in range(idx_by_4, min(idx_by_4 + 4, num_symbols)): symbol_version = self._symbol_version(idx) if symbol_version['index'] == 'VER_NDX_LOCAL': version_index = 0 version_name = '(*local*)' elif symbol_version['index'] == 'VER_NDX_GLOBAL': version_index = 1 version_name = '(*global*)' else: version_index = symbol_version['index'] version_name = '(%(name)s)' % symbol_version visibility = 'h' if symbol_version['hidden'] else ' ' self._emit('%4x%s%-13s' % ( version_index, visibility, version_name)) self._emitline() elif isinstance(section, GNUVerDefSection): self._print_version_section_header( section, 'Version definition', indent=2) offset = 0 for verdef, verdaux_iter in section.iter_versions(): verdaux = next(verdaux_iter) name = verdaux.name if verdef['vd_flags']: flags = describe_ver_flags(verdef['vd_flags']) # Mimic exactly the readelf output flags += ' ' else: flags = 'none' self._emitline(' %s: Rev: %i Flags: %s Index: %i' ' Cnt: %i Name: %s' % ( self._format_hex(offset, fieldsize=6, alternate=True), verdef['vd_version'], flags, verdef['vd_ndx'], verdef['vd_cnt'], bytes2str(name))) verdaux_offset = ( offset + verdef['vd_aux'] + verdaux['vda_next']) for idx, verdaux in enumerate(verdaux_iter, start=1): self._emitline(' %s: Parent %i: %s' % (self._format_hex(verdaux_offset, fieldsize=4), idx, bytes2str(verdaux.name))) verdaux_offset += verdaux['vda_next'] offset += verdef['vd_next'] elif isinstance(section, GNUVerNeedSection): self._print_version_section_header(section, 'Version needs') offset = 0 for verneed, verneed_iter in section.iter_versions(): self._emitline(' %s: Version: %i File: %s Cnt: %i' % ( self._format_hex(offset, fieldsize=6, alternate=True), verneed['vn_version'], bytes2str(verneed.name), verneed['vn_cnt'])) vernaux_offset = offset + verneed['vn_aux'] for idx, vernaux in enumerate(verneed_iter, start=1): if vernaux['vna_flags']: flags = describe_ver_flags(vernaux['vna_flags']) # Mimic exactly the readelf output flags += ' ' else: flags = 'none' self._emitline( ' %s: Name: %s Flags: %s Version: %i' % ( self._format_hex(vernaux_offset, fieldsize=4), bytes2str(vernaux.name), flags, vernaux['vna_other'])) vernaux_offset += vernaux['vna_next'] offset += verneed['vn_next'] def display_hex_dump(self, section_spec): """ Display a hex dump of a section. section_spec is either a section number or a name. """ section = self._section_from_spec(section_spec) if section is None: self._emitline("Section '%s' does not exist in the file!" % ( section_spec)) return self._emitline("\nHex dump of section '%s':" % bytes2str(section.name)) self._note_relocs_for_section(section) addr = section['sh_addr'] data = section.data() dataptr = 0 while dataptr < len(data): bytesleft = len(data) - dataptr # chunks of 16 bytes per line linebytes = 16 if bytesleft > 16 else bytesleft self._emit(' %s ' % self._format_hex(addr, fieldsize=8)) for i in range(16): if i < linebytes: self._emit('%2.2x' % byte2int(data[dataptr + i])) else: self._emit(' ') if i % 4 == 3: self._emit(' ') for i in range(linebytes): c = data[dataptr + i : dataptr + i + 1] if byte2int(c[0]) >= 32 and byte2int(c[0]) < 0x7f: self._emit(bytes2str(c)) else: self._emit(bytes2str(b'.')) self._emitline() addr += linebytes dataptr += linebytes self._emitline() def display_string_dump(self, section_spec): """ Display a strings dump of a section. section_spec is either a section number or a name. """ section = self._section_from_spec(section_spec) if section is None: self._emitline("Section '%s' does not exist in the file!" % ( section_spec)) return self._emitline("\nString dump of section '%s':" % bytes2str(section.name)) found = False data = section.data() dataptr = 0 while dataptr < len(data): while ( dataptr < len(data) and not (32 <= byte2int(data[dataptr]) <= 127)): dataptr += 1 if dataptr >= len(data): break endptr = dataptr while endptr < len(data) and byte2int(data[endptr]) != 0: endptr += 1 found = True self._emitline(' [%6x] %s' % ( dataptr, bytes2str(data[dataptr:endptr]))) dataptr = endptr if not found: self._emitline(' No strings found in this section.') else: self._emitline() def display_debug_dump(self, dump_what): """ Dump a DWARF section """ self._init_dwarfinfo() if self._dwarfinfo is None: return set_global_machine_arch(self.elffile.get_machine_arch()) if dump_what == 'info': self._dump_debug_info() elif dump_what == 'decodedline': self._dump_debug_line_programs() elif dump_what == 'frames': self._dump_debug_frames() elif dump_what == 'frames-interp': self._dump_debug_frames_interp() else: self._emitline('debug dump not yet supported for "%s"' % dump_what) def _format_hex(self, addr, fieldsize=None, fullhex=False, lead0x=True, alternate=False): """ Format an address into a hexadecimal string. fieldsize: Size of the hexadecimal field (with leading zeros to fit the address into. For example with fieldsize=8, the format will be %08x If None, the minimal required field size will be used. fullhex: If True, override fieldsize to set it to the maximal size needed for the elfclass lead0x: If True, leading 0x is added alternate: If True, override lead0x to emulate the alternate hexadecimal form specified in format string with the # character: only non-zero values are prefixed with 0x. This form is used by readelf. """ if alternate: if addr == 0: lead0x = False else: lead0x = True fieldsize -= 2 s = '0x' if lead0x else '' if fullhex: fieldsize = 8 if self.elffile.elfclass == 32 else 16 if fieldsize is None: field = '%x' else: field = '%' + '0%sx' % fieldsize return s + field % addr def _print_version_section_header(self, version_section, name, lead0x=True, indent=1): """ Print a section header of one version related section (versym, verneed or verdef) with some options to accomodate readelf little differences between each header (e.g. indentation and 0x prefixing). """ if hasattr(version_section, 'num_versions'): num_entries = version_section.num_versions() else: num_entries = version_section.num_symbols() self._emitline("\n%s section '%s' contains %s entries:" % (name, bytes2str(version_section.name), num_entries)) self._emitline('%sAddr: %s Offset: %s Link: %i (%s)' % ( ' ' * indent, self._format_hex( version_section['sh_addr'], fieldsize=16, lead0x=lead0x), self._format_hex( version_section['sh_offset'], fieldsize=6, lead0x=True), version_section['sh_link'], bytes2str( self.elffile.get_section(version_section['sh_link']).name) ) ) def _init_versioninfo(self): """ Search and initialize informations about version related sections and the kind of versioning used (GNU or Solaris). """ if self._versioninfo is not None: return self._versioninfo = {'versym': None, 'verdef': None, 'verneed': None, 'type': None} for section in self.elffile.iter_sections(): if isinstance(section, GNUVerSymSection): self._versioninfo['versym'] = section elif isinstance(section, GNUVerDefSection): self._versioninfo['verdef'] = section elif isinstance(section, GNUVerNeedSection): self._versioninfo['verneed'] = section elif isinstance(section, DynamicSection): for tag in section.iter_tags(): if tag['d_tag'] == 'DT_VERSYM': self._versioninfo['type'] = 'GNU' break if not self._versioninfo['type'] and ( self._versioninfo['verneed'] or self._versioninfo['verdef']): self._versioninfo['type'] = 'Solaris' def _symbol_version(self, nsym): """ Return a dict containing information on the or None if no version information is available """ self._init_versioninfo() symbol_version = dict.fromkeys(('index', 'name', 'filename', 'hidden')) if (not self._versioninfo['versym'] or nsym >= self._versioninfo['versym'].num_symbols()): return None symbol = self._versioninfo['versym'].get_symbol(nsym) index = symbol.entry['ndx'] if not index in ('VER_NDX_LOCAL', 'VER_NDX_GLOBAL'): index = int(index) if self._versioninfo['type'] == 'GNU': # In GNU versioning mode, the highest bit is used to # store wether the symbol is hidden or not if index & 0x8000: index &= ~0x8000 symbol_version['hidden'] = True if (self._versioninfo['verdef'] and index <= self._versioninfo['verdef'].num_versions()): _, verdaux_iter = \ self._versioninfo['verdef'].get_version(index) symbol_version['name'] = bytes2str(next(verdaux_iter).name) else: verneed, vernaux = \ self._versioninfo['verneed'].get_version(index) symbol_version['name'] = bytes2str(vernaux.name) symbol_version['filename'] = bytes2str(verneed.name) symbol_version['index'] = index return symbol_version def _section_from_spec(self, spec): """ Retrieve a section given a "spec" (either number or name). Return None if no such section exists in the file. """ try: num = int(spec) if num < self.elffile.num_sections(): return self.elffile.get_section(num) else: return None except ValueError: # Not a number. Must be a name then return self.elffile.get_section_by_name(str2bytes(spec)) def _note_relocs_for_section(self, section): """ If there are relocation sections pointing to the givne section, emit a note about it. """ for relsec in self.elffile.iter_sections(): if isinstance(relsec, RelocationSection): info_idx = relsec['sh_info'] if self.elffile.get_section(info_idx) == section: self._emitline(' Note: This section has relocations against it, but these have NOT been applied to this dump.') return def _init_dwarfinfo(self): """ Initialize the DWARF info contained in the file and assign it to self._dwarfinfo. Leave self._dwarfinfo at None if no DWARF info was found in the file """ if self._dwarfinfo is not None: return if self.elffile.has_dwarf_info(): self._dwarfinfo = self.elffile.get_dwarf_info() else: self._dwarfinfo = None def _dump_debug_info(self): """ Dump the debugging info section. """ self._emitline('Contents of the .debug_info section:\n') # Offset of the .debug_info section in the stream section_offset = self._dwarfinfo.debug_info_sec.global_offset for cu in self._dwarfinfo.iter_CUs(): self._emitline(' Compilation Unit @ offset %s:' % self._format_hex(cu.cu_offset)) self._emitline(' Length: %s (%s)' % ( self._format_hex(cu['unit_length']), '%s-bit' % cu.dwarf_format())) self._emitline(' Version: %s' % cu['version']), self._emitline(' Abbrev Offset: %s' % ( self._format_hex(cu['debug_abbrev_offset']))), self._emitline(' Pointer Size: %s' % cu['address_size']) # The nesting depth of each DIE within the tree of DIEs must be # displayed. To implement this, a counter is incremented each time # the current DIE has children, and decremented when a null die is # encountered. Due to the way the DIE tree is serialized, this will # correctly reflect the nesting depth # die_depth = 0 for die in cu.iter_DIEs(): self._emitline(' <%s><%x>: Abbrev Number: %s%s' % ( die_depth, die.offset, die.abbrev_code, (' (%s)' % die.tag) if not die.is_null() else '')) if die.is_null(): die_depth -= 1 continue for attr in itervalues(die.attributes): name = attr.name # Unknown attribute values are passed-through as integers if isinstance(name, int): name = 'Unknown AT value: %x' % name self._emitline(' <%2x> %-18s: %s' % ( attr.offset, name, describe_attr_value( attr, die, section_offset))) if die.has_children: die_depth += 1 self._emitline() def _dump_debug_line_programs(self): """ Dump the (decoded) line programs from .debug_line The programs are dumped in the order of the CUs they belong to. """ self._emitline('Decoded dump of debug contents of section .debug_line:\n') for cu in self._dwarfinfo.iter_CUs(): lineprogram = self._dwarfinfo.line_program_for_CU(cu) cu_filename = bytes2str(lineprogram['file_entry'][0].name) if len(lineprogram['include_directory']) > 0: dir_index = lineprogram['file_entry'][0].dir_index if dir_index > 0: dir = lineprogram['include_directory'][dir_index - 1] else: dir = b'.' cu_filename = '%s/%s' % (bytes2str(dir), cu_filename) self._emitline('CU: %s:' % cu_filename) self._emitline('File name Line number Starting address') # Print each state's file, line and address information. For some # instructions other output is needed to be compatible with # readelf. for entry in lineprogram.get_entries(): state = entry.state if state is None: # Special handling for commands that don't set a new state if entry.command == DW_LNS_set_file: file_entry = lineprogram['file_entry'][entry.args[0] - 1] if file_entry.dir_index == 0: # current directory self._emitline('\n./%s:[++]' % ( bytes2str(file_entry.name))) else: self._emitline('\n%s/%s:' % ( bytes2str(lineprogram['include_directory'][file_entry.dir_index - 1]), bytes2str(file_entry.name))) elif entry.command == DW_LNE_define_file: self._emitline('%s:' % ( bytes2str(lineprogram['include_directory'][entry.args[0].dir_index]))) elif not state.end_sequence: # readelf doesn't print the state after end_sequence # instructions. I think it's a bug but to be compatible # I don't print them too. self._emitline('%-35s %11d %18s' % ( bytes2str(lineprogram['file_entry'][state.file - 1].name), state.line, '0' if state.address == 0 else self._format_hex(state.address))) if entry.command == DW_LNS_copy: # Another readelf oddity... self._emitline() def _dump_debug_frames(self): """ Dump the raw frame information from .debug_frame """ if not self._dwarfinfo.has_CFI(): return self._emitline('Contents of the .debug_frame section:') for entry in self._dwarfinfo.CFI_entries(): if isinstance(entry, CIE): self._emitline('\n%08x %08x %08x CIE' % ( entry.offset, entry['length'], entry['CIE_id'])) self._emitline(' Version: %d' % entry['version']) self._emitline(' Augmentation: "%s"' % bytes2str(entry['augmentation'])) self._emitline(' Code alignment factor: %u' % entry['code_alignment_factor']) self._emitline(' Data alignment factor: %d' % entry['data_alignment_factor']) self._emitline(' Return address column: %d' % entry['return_address_register']) self._emitline() else: # FDE self._emitline('\n%08x %08x %08x FDE cie=%08x pc=%08x..%08x' % ( entry.offset, entry['length'], entry['CIE_pointer'], entry.cie.offset, entry['initial_location'], entry['initial_location'] + entry['address_range'])) self._emit(describe_CFI_instructions(entry)) self._emitline() def _dump_debug_frames_interp(self): """ Dump the interpreted (decoded) frame information from .debug_frame """ if not self._dwarfinfo.has_CFI(): return self._emitline('Contents of the .debug_frame section:') for entry in self._dwarfinfo.CFI_entries(): if isinstance(entry, CIE): self._emitline('\n%08x %08x %08x CIE "%s" cf=%d df=%d ra=%d' % ( entry.offset, entry['length'], entry['CIE_id'], bytes2str(entry['augmentation']), entry['code_alignment_factor'], entry['data_alignment_factor'], entry['return_address_register'])) ra_regnum = entry['return_address_register'] else: # FDE self._emitline('\n%08x %08x %08x FDE cie=%08x pc=%08x..%08x' % ( entry.offset, entry['length'], entry['CIE_pointer'], entry.cie.offset, entry['initial_location'], entry['initial_location'] + entry['address_range'])) ra_regnum = entry.cie['return_address_register'] # Print the heading row for the decoded table self._emit(' LOC') self._emit(' ' if entry.structs.address_size == 4 else ' ') self._emit(' CFA ') # Decode the table nad look at the registers it describes. # We build reg_order here to match readelf's order. In particular, # registers are sorted by their number, and the register matching # ra_regnum is always listed last with a special heading. decoded_table = entry.get_decoded() reg_order = sorted(ifilter( lambda r: r != ra_regnum, decoded_table.reg_order)) # Headings for the registers for regnum in reg_order: self._emit('%-6s' % describe_reg_name(regnum)) self._emitline('ra ') # Now include ra_regnum in reg_order to print its values similarly # to the other registers. reg_order.append(ra_regnum) for line in decoded_table.table: self._emit(self._format_hex( line['pc'], fullhex=True, lead0x=False)) self._emit(' %-9s' % describe_CFI_CFA_rule(line['cfa'])) for regnum in reg_order: if regnum in line: s = describe_CFI_register_rule(line[regnum]) else: s = 'u' self._emit('%-6s' % s) self._emitline() self._emitline() def _emit(self, s=''): """ Emit an object to output """ self.output.write(str(s)) def _emitline(self, s=''): """ Emit an object to output, followed by a newline """ self.output.write(str(s) + '\n')
class Elf(object): def __init__(self, fileobj): self.elffile = ELFFile(fileobj) self.output = sys.stdout # our code starts here :-) def network(self): ret = "None" for section in self.elffile.iter_sections(): if not isinstance(section, SymbolTableSection): continue if section['sh_entsize'] == 0: print("\nSymbol table '%s' has a sh_entsize " \ "of zero!" % (bytes2str(section.name)), file=sys.stderr) continue for _, symbol in enumerate(section.iter_symbols()): # first match IP_PATTERNS for pattern in IP_PATTERNS: if re.match(pattern, bytes2str(symbol.name)): return "network-ip" # then match LOCAL_PATTERNS for pattern in LOCAL_PATTERNS: if re.match(pattern, bytes2str(symbol.name)): ret = "network-local" break return ret def _strings(self): stream = self.elffile.stream epos = stream.tell() stream.seek(0, 0) data = stream.read() stream.seek(epos, 0) ret = [] # XXX avoid calling eu-strings import subprocess p = subprocess.Popen("eu-strings", shell=True, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE) out = p.communicate(input=data)[0] for line in out.splitlines(): if re.match("^/tmp/.+", line) and "XXX" not in line: ret.append(line) return ret def tempstuff(self): tmp_strings = self._strings() # if there are no /tmp references, just return if len(tmp_strings) == 0: return "None" for section in self.elffile.iter_sections(): if not isinstance(section, SymbolTableSection): continue if section['sh_entsize'] == 0: print("\nSymbol table '%s' has a sh_entsize " \ "of zero!" % (bytes2str(section.name)), file=sys.stderr) continue for _, symbol in enumerate(section.iter_symbols()): for pattern in TMP_FUNCTIONS: if re.match(pattern, bytes2str(symbol.name)): return "None" return "$".join(tmp_strings) # XXX implement this def chroot_without_chdir(self): """ This functions looks for apps that use chroot(2) without using chdir(2). Inspired by http://people.redhat.com/sgrubb/security/find-chroot """ pass def fortify(self): """ NA : FORTIFY_SOURCE was not applicable Enabled : unsafe and _chk functions were found Disabled : only unsafe functions were found (_chk functions missing)""" ret = "NA" for section in self.elffile.iter_sections(): if not isinstance(section, SymbolTableSection): continue if section['sh_entsize'] == 0: print("\nSymbol table '%s' has a sh_entsize " \ "of zero!" % (bytes2str(section.name)), file=sys.stderr) continue for _, symbol in enumerate(section.iter_symbols()): for pattern in UNSAFE_FUNCTIONS: if re.match(pattern, bytes2str(symbol.name)): if ret == "NA": ret = "Disabled" break if ret == "Disabled": # afename = "__" + bytes2str(symbol.name) + "_chk" for _, symbol in enumerate(section.iter_symbols()): # first look for corresponding _chk symbol symbolstr = bytes2str(symbol.name) if (symbolstr.startswith("__") and symbolstr.endswith("_chk")) or \ symbolstr.endswith(" __chk_fail"): ret = "Enabled" break return ret def canary(self): for section in self.elffile.iter_sections(): if not isinstance(section, SymbolTableSection): continue if section['sh_entsize'] == 0: print ("\nSymbol table '%s' has a sh_entsize " \ "of zero!" % (bytes2str(section.name)), file=sys.stderr) continue for _, symbol in enumerate(section.iter_symbols()): if re.match(STACK_CHK, bytes2str(symbol.name)): return "Enabled" return "Disabled" def dynamic_tags(self, key="DT_RPATH"): for section in self.elffile.iter_sections(): if not isinstance(section, DynamicSection): continue for tag in section.iter_tags(): if tag.entry.d_tag == key: return "Enabled" return "Disabled" def program_headers(self): pflags = P_FLAGS() if self.elffile.num_segments() == 0: # print('There are no program headers in this file.', \ # file=sys.stderr) return for segment in self.elffile.iter_segments(): if re.search("GNU_STACK", segment['p_type']): if segment['p_flags'] & pflags.PF_X: return "Disabled" return "Enabled" def relro(self): if self.elffile.num_segments() == 0: # print('There are no program headers in this file.', \ # file=sys.stderr) return have_relro = False for segment in self.elffile.iter_segments(): if re.search("GNU_RELRO", segment['p_type']): have_relro = True break if self.dynamic_tags("DT_BIND_NOW") == "Enabled" and have_relro: return "Enabled" if have_relro: return "Partial" return "Disabled" def pie(self): header = self.elffile.header if self.dynamic_tags("EXEC") == "Enabled": return "Disabled" if "ET_DYN" in header['e_type']: if self.dynamic_tags("DT_DEBUG") == "Enabled": return "Enabled" else: return "DSO" return "Disabled" def getdeps(self): deps = [] if self.elffile.num_segments() == 0: return deps for segment in self.elffile.iter_segments(): if re.search("PT_DYNAMIC", segment['p_type']): # this file uses dynamic linking, so read the dynamic section # and find DT_SONAME tag for section in self.elffile.iter_sections(): if not isinstance(section, DynamicSection): continue for tag in section.iter_tags(): if tag.entry.d_tag == 'DT_NEEDED': deps.append(bytes2str(tag.needed)) break return deps
def main(): """Main function""" global args parse_args() if not os.path.exists(args.kernel): error("{0} does not exist.".format(args.kernel)) elf_fd = open(args.kernel, "rb") # Create a modifiable byte stream raw_elf = elf_fd.read() output = create_string_buffer(raw_elf) elf = ELFFile(elf_fd) if not elf.has_dwarf_info(): error("ELF file has no DWARF information") if elf.num_segments() == 0: error("ELF file has no program header table") syms = extract_all_symbols_from_elf(elf) vm_base = syms["CONFIG_KERNEL_VM_BASE"] vm_size = syms["CONFIG_KERNEL_VM_SIZE"] sram_base = syms["CONFIG_SRAM_BASE_ADDRESS"] sram_size = syms["CONFIG_SRAM_SIZE"] * 1024 vm_offset = syms["CONFIG_KERNEL_VM_OFFSET"] sram_offset = syms.get("CONFIG_SRAM_OFFSET", 0) # # Calculate virtual-to-physical address translation # virt_to_phys_offset = (sram_base + sram_offset) - (vm_base + vm_offset) log("Virtual address space: 0x%x - 0x%x size 0x%x (offset 0x%x)" % (vm_base, vm_base + vm_size, vm_size, vm_offset)) log("Physical address space: 0x%x - 0x%x size 0x%x (offset 0x%x)" % (sram_base, sram_base + sram_size, sram_size, sram_offset)) # # Update the entry address in header # if elf.elfclass == 32: load_entry_type = "I" else: load_entry_type = "Q" entry_virt = struct.unpack_from(load_entry_type, output, ELF_HDR_ENTRY_OFFSET)[0] entry_phys = entry_virt + virt_to_phys_offset struct.pack_into(load_entry_type, output, ELF_HDR_ENTRY_OFFSET, entry_phys) log("Entry Address: 0x%x -> 0x%x" % (entry_virt, entry_phys)) # # Update load address in program header segments # # Program header segment offset from beginning of file ph_off = elf.header['e_phoff'] # ph_seg_type: segment type and other fields before virtual address # ph_seg_addr: virtual and phyiscal addresses # ph_seg_whole: whole segment if elf.elfclass == 32: ph_seg_type = "II" ph_seg_addr = "II" ph_seg_whole = "IIIIIIII" else: ph_seg_type = "IIQ" ph_seg_addr = "QQ" ph_seg_whole = "IIQQQQQQ" # Go through all segments for ph_idx in range(elf.num_segments()): seg_off = ph_off + struct.calcsize(ph_seg_whole) * ph_idx seg_type, _ = struct.unpack_from(ph_seg_type, output, seg_off) # Only process LOAD segments if seg_type != 0x01: continue # Add offset to get to the addresses addr_off = seg_off + struct.calcsize(ph_seg_type) # Grab virtual and physical addresses seg_vaddr, seg_paddr = struct.unpack_from(ph_seg_addr, output, addr_off) # Apply virt-to-phys offset so it will load into # physical address seg_paddr_new = seg_vaddr + virt_to_phys_offset log("Segment %d: physical address 0x%x -> 0x%x" % (ph_idx, seg_paddr, seg_paddr_new)) # Put the addresses back struct.pack_into(ph_seg_addr, output, addr_off, seg_vaddr, seg_paddr_new) out_fd = open(args.output, "wb") out_fd.write(output) out_fd.close() elf_fd.close()