#!/usr/bin/python3 # -*- coding: utf-8 -*- import argparse import math import sys import pychrysalide from collections import OrderedDict from pychrysalide.analysis.contents import FileContent from pychrysalide.analysis import LoadedBinary from pychrysalide.arch import vmpa from pychrysalide.format.elf import ElfFormat def _get_string(fmt, start, offset): """Extract a given string.""" string = None maxlen = fmt.content.compute_size() end = start + offset while end < maxlen: byte = fmt.content.read_u8(vmpa(end, vmpa.VMPA_NO_VIRTUAL)) if byte == 0x00: break end += 1 string = fmt.content.read_raw(vmpa(start + offset, vmpa.VMPA_NO_VIRTUAL), end - start) return string.decode('utf-8') def display_elf_header(fmt): """Displays the information contained in the file's main header.""" # File class elf_classes = { ElfFormat.ELFCLASS32 : 'ELF32', ElfFormat.ELFCLASS64 : 'ELF64' } # Data encoding elf_encodings = { ElfFormat.ELFDATA2LSB : "2's complement, little endian", ElfFormat.ELFDATA2MSB : "2's complement, big endian" } # Object file type elf_types = { ElfFormat.ET_REL : 'Relocatable file', ElfFormat.ET_EXEC : 'Executable file', ElfFormat.ET_DYN : 'Shared object file', ElfFormat.ET_CORE : 'Core file' } # Architecture elf_machines = { ElfFormat.EM_ARM : 'ARM', ElfFormat.EM_AARCH64 : 'ARM AARCH64' } # ELF Header header = fmt.get_header() fields = OrderedDict() fields['Magic'] = ' '.join([ '%02x' % c for c in header.e_ident ]) fields['Class'] = elf_classes[header.e_ident[ElfFormat.EI_CLASS]] if header.e_ident[ElfFormat.EI_CLASS] in elf_classes.keys() else 'Invalid' fields['Data'] = elf_encodings[header.e_ident[ElfFormat.EI_DATA]] if header.e_ident[ElfFormat.EI_DATA] in elf_encodings.keys() else 'Invalid' fields['Version'] = '%u' % header.e_ident[ElfFormat.EI_VERSION] fields['OS/ABI'] = 'UNIX - System V' if header.e_ident[ElfFormat.EI_OSABI] == ElfFormat.ELFOSABI_SYSV else 'Other than UNIX' fields['ABI Version'] = '%u' % header.e_ident[ElfFormat.EI_ABIVERSION] fields['Type'] = elf_types[header.e_type] if header.e_type in elf_types.keys() else 'Other file type' fields['Machine'] = elf_machines[header.e_machine] if header.e_machine in elf_machines.keys() else 'Other architecture' fields['Version'] = '0x%x' % header.e_version fields['Entry point address'] = '0x%x' % header.e_entry fields['Start of program headers'] = '%u (bytes into file)' % header.e_phoff fields['Start of section headers'] = '%u (bytes into file)' % header.e_shoff fields['Flags'] = '0x%x' % header.e_flags fields['Size of this header'] = '%u (bytes)' % header.e_ehsize fields['Size of program headers'] = '%u (bytes)' % header.e_phentsize fields['Number of program headers'] = '%u' % header.e_phnum fields['Size of section headers'] = '%u (bytes)' % header.e_shentsize fields['Number of section headers'] = '%u' % header.e_shnum fields['Section header string table index'] = '%u' % header.e_shstrndx # Compute the max length and the relative format max_len = 0 for k in fields.keys(): if len(k) > max_len: max_len = len(k) max_len += len(':') line = ' {0:<%d} {1}' % max_len # Final display print('ELF Header:') for k, v in fields.items(): print(line.format(k + ':', v)) print() def display_elf_program_headers(fmt): """Displays the information contained in the file's program headers, if it has any.""" # Segment type segment_types = { ElfFormat.PT_NULL : 'NULL', ElfFormat.PT_LOAD : 'LOAD', ElfFormat.PT_DYNAMIC : 'DYNAMIC', ElfFormat.PT_INTERP : 'INTERP', ElfFormat.PT_NOTE : 'NOTE', ElfFormat.PT_SHLIB : 'SHLIB', ElfFormat.PT_PHDR : 'PHDR', ElfFormat.PT_TLS : 'TLS', ElfFormat.PT_GNU_EH_FRAME : 'GNU_EH_FRAME', ElfFormat.PT_GNU_STACK : 'GNU_STACK', ElfFormat.PT_GNU_RELRO : 'GNU_RELRO' } # Data computing header = fmt.get_header() fields = [] fields.append([ 'Type', 'Offset', 'VirtAddr', 'PhysAddr', 'FileSiz', 'MemSiz', 'Flg', 'Align' ]) for i in range(header.e_phnum): phdr = fmt.find_program_by_index(i) fields.append([ segment_types[phdr.p_type] if phdr.p_type in segment_types.keys() else 'UNKNOWN', '0x%x' % phdr.p_offset, '0x%x' % phdr.p_vaddr, '0x%x' % phdr.p_paddr, '0x%x' % phdr.p_filesz, '0x%x' % phdr.p_memsz, '%c%c%c' % ('R' if phdr.p_flags & ElfFormat.PF_R else ' ', 'W' if phdr.p_flags & ElfFormat.PF_W else ' ', 'X' if phdr.p_flags & ElfFormat.PF_X else ' '), '0x%x' % phdr.p_align]) # Max string length count = len(fields[0]) lengths = [ 0 for i in range(count) ] for f in fields: for c in range(count): if len(f[c]) > lengths[c]: lengths[c] = len(f[c]) # Relative format string line = ' ' for c in range(count): line += '{%u:<%u}' % (c, lengths[c] + 1) # Final display print('Program Headers:') for f in fields: print(line.format(*f)) print() # Second final display line = ' {0:<7} {1}' print('Section to Segment mapping:') print(line.format('Segment', 'Sections...')) for i in range(header.e_phnum): phdr = fmt.find_program_by_index(i) contained = '' for j in range(header.e_shnum): section = fmt.find_section_by_index(j) if not (section.sh_flags & ElfFormat.SHF_ALLOC): continue if section.sh_addr < phdr.p_vaddr: continue if (section.sh_addr + section.sh_size) > (phdr.p_vaddr + phdr.p_memsz): continue if len(contained) > 0: contained += ' ' + section.name else: contained = section.name print(line.format(i, contained)) print() def display_elf_section_headers(fmt): """Displays the information contained in the file's section headers, if it has any.""" # Section type section_types = { ElfFormat.SHT_NULL : 'NULL', ElfFormat.SHT_PROGBITS : 'PROGBITS', ElfFormat.SHT_SYMTAB : 'SYMTAB', ElfFormat.SHT_STRTAB : 'STRTAB', ElfFormat.SHT_RELA : 'RELA', ElfFormat.SHT_HASH : 'HASH', ElfFormat.SHT_DYNAMIC : 'DYNAMIC', ElfFormat.SHT_NOTE : 'NOTE', ElfFormat.SHT_NOBITS : 'NOBITS', ElfFormat.SHT_REL : 'REL', ElfFormat.SHT_SHLIB : 'SHLIB', ElfFormat.SHT_DYNSYM : 'DYNSYM', ElfFormat.SHT_INIT_ARRAY : 'NIT_ARRAY', ElfFormat.SHT_FINI_ARRAY : 'FINI_ARRAY', ElfFormat.SHT_PREINIT_ARRAY : 'PREINIT_ARRAY', ElfFormat.SHT_GROUP : 'GROUP', ElfFormat.SHT_SYMTAB_SHNDX : 'SYMTAB_SHNDX', ElfFormat.SHT_GNU_ATTRIBUTES : 'GNU_ATTRIBUTES', ElfFormat.SHT_GNU_HASH : 'GNU_HASH', ElfFormat.SHT_GNU_LIBLIST : 'GNU_LIBLIST', ElfFormat.SHT_CHECKSUM : 'CHECKSUM', ElfFormat.SHT_SUNW_move : 'SUNW_move', ElfFormat.SHT_SUNW_COMDAT : 'SUNW_COMDAT', ElfFormat.SHT_SUNW_syminfo : 'SUNW_syminfo', ElfFormat.SHT_GNU_verdef : 'GNU_verdef', ElfFormat.SHT_GNU_verneed : 'GNU_verneed', ElfFormat.SHT_GNU_versym : 'GNU_versym' } def build_section_flags(flags): desc = '' desc += 'W' if flags & ElfFormat.SHF_WRITE else '' desc += 'A' if flags & ElfFormat.SHF_ALLOC else '' desc += 'X' if flags & ElfFormat.SHF_EXECINSTR else '' desc += 'M' if flags & ElfFormat.SHF_MERGE else '' desc += 'S' if flags & ElfFormat.SHF_STRINGS else '' desc += 'I' if flags & ElfFormat.SHF_INFO_LINK else '' desc += 'L' if flags & ElfFormat.SHF_LINK_ORDER else '' desc += 'G' if flags & ElfFormat.SHF_GROUP else '' desc += 'T' if flags & ElfFormat.SHF_TLS else '' desc += 'X' if flags & ElfFormat.SHF_EXCLUDE else '' return desc header = fmt.get_header() count = header.e_shnum section = fmt.find_section_by_index(header.e_shstrndx) if section: strtab_pos = section.sh_offset else: strtab_pos = None # Compute max string lengths name_maxlen = 0 type_maxlen = 0 esize_maxlen = 99 # ES (log10) flags_maxlen = 3 # Flg link_maxlen = 99 # Lk (log10) info_maxlen = 999 # Inf (log10) align_maxlen = 99 # Al (log10) for i in range(header.e_shnum): section = fmt.find_section_by_index(i) if len(section.name) > name_maxlen: name_maxlen = len(section.name) if section.sh_type in section_types.keys(): if len(section_types[section.sh_type]) > type_maxlen: type_maxlen = len(section_types[section.sh_type]) if section.sh_entsize > esize_maxlen: esize_maxlen = section.sh_entsize flags_desc = build_section_flags(section.sh_flags) if len(flags_desc) > flags_maxlen: flags_maxlen = len(flags_desc) if section.sh_link > link_maxlen: link_maxlen = section.sh_link if section.sh_info > info_maxlen: info_maxlen = section.sh_info if section.sh_addralign > align_maxlen: align_maxlen = section.sh_addralign # Relative format string esize_maxlen = math.log10(esize_maxlen) link_maxlen = math.log10(link_maxlen) info_maxlen = math.log10(info_maxlen) align_maxlen = math.log10(align_maxlen) if esize_maxlen % 1: esize_maxlen +=1 if link_maxlen % 1: link_maxlen +=1 if info_maxlen % 1: info_maxlen +=1 if align_maxlen % 1: align_maxlen +=1 esize_maxlen = int(esize_maxlen) link_maxlen = int(link_maxlen) info_maxlen = int(info_maxlen) align_maxlen = int(align_maxlen) line = ' [{0:>2}] {1:<%u} {2:<%u} {3:<8} {4:<8} {5:<8} {6:>%u} {7:>%u} {8:>%u} {9:>%u} {10:>%u}' \ % (name_maxlen, type_maxlen, esize_maxlen, flags_maxlen, link_maxlen, info_maxlen, align_maxlen) # Final display headers = [ 'NR', 'Name', 'Type', 'Addr', 'Off', 'Size', 'ES', 'Flg', 'Lk', 'Inf', 'Al' ] print('Section Headers:') print(line.format(*headers)) for i in range(header.e_shnum): section = fmt.find_section_by_index(i) if section.sh_type in section_types.keys(): sec_type = section_types[section.sh_type] else: sec_type = '???' flags_desc = build_section_flags(section.sh_flags) print(line.format(i, section.name, sec_type, '%08x' % section.sh_addr, '%08x' % section.sh_offset, '%08x' % section.sh_size, '%x' % section.sh_entsize, flags_desc, '%u' % section.sh_link, '%u' % section.sh_info, '%u' % section.sh_addralign)) print() print('Key to Flags:') print(' W (write), A (alloc), X (execute), M (merge), S (strings)') print(' I (info), L (link order), G (group), T (TLS), E (exclude)') print() def display_elf_dynamic_items(fmt): """Displays the information contained in the file's dynamic items, if it has any.""" # Dynamic entry types elf_dynamic_types = { ElfFormat.DT_NULL : 'NULL', ElfFormat.DT_NEEDED : 'NEEDED', ElfFormat.DT_PLTRELSZ : 'PLTRELSZ', ElfFormat.DT_PLTGOT : 'PLTGOT', ElfFormat.DT_HASH : 'HASH', ElfFormat.DT_STRTAB : 'STRTAB', ElfFormat.DT_SYMTAB : 'SYMTAB', ElfFormat.DT_RELA : 'RELA', ElfFormat.DT_RELASZ : 'RELASZ', ElfFormat.DT_RELAENT : 'RELAENT', ElfFormat.DT_STRSZ : 'STRSZ', ElfFormat.DT_SYMENT : 'SYMENT', ElfFormat.DT_INIT : 'INIT', ElfFormat.DT_FINI : 'FINI', ElfFormat.DT_SONAME : 'SONAME', ElfFormat.DT_RPATH : 'RPATH', ElfFormat.DT_SYMBOLIC : 'SYMBOLIC', ElfFormat.DT_REL : 'REL', ElfFormat.DT_RELSZ : 'RELSZ', ElfFormat.DT_RELENT : 'RELENT', ElfFormat.DT_PLTREL : 'PLTREL', ElfFormat.DT_DEBUG : 'DEBUG', ElfFormat.DT_TEXTREL : 'TEXTREL', ElfFormat.DT_JMPREL : 'JMPREL', ElfFormat.DT_BIND_NOW : 'BIND_NOW', ElfFormat.DT_INIT_ARRAY : 'INIT_ARRAY', ElfFormat.DT_FINI_ARRAY : 'FINI_ARRAY', ElfFormat.DT_INIT_ARRAYSZ : 'INIT_ARRAYSZ', ElfFormat.DT_FINI_ARRAYSZ : 'FINI_ARRAYSZ', ElfFormat.DT_RUNPATH : 'RUNPATH', ElfFormat.DT_FLAGS : 'FLAGS', ElfFormat.DT_ENCODING : 'ENCODING', ElfFormat.DT_PREINIT_ARRAY : 'PREINIT_ARRAY', ElfFormat.DT_PREINIT_ARRAYSZ : 'PREINIT_ARRAYSZ' } # See http://www.sco.com/developers/gabi/latest/ch5.dynamic.html elf_dynamic_values = { ElfFormat.DT_NULL : '0x%x', ElfFormat.DT_NEEDED : 'Shared library: [%s]', ElfFormat.DT_PLTRELSZ : '%u (bytes)', ElfFormat.DT_PLTGOT : '0x%x', ElfFormat.DT_HASH : '0x%x', ElfFormat.DT_STRTAB : '0x%x', ElfFormat.DT_SYMTAB : '0x%x', ElfFormat.DT_RELA : '0x%x', ElfFormat.DT_RELASZ : '%u (bytes)', ElfFormat.DT_RELAENT : '%u (bytes)', ElfFormat.DT_STRSZ : '%u (bytes)', ElfFormat.DT_SYMENT : '%u (bytes)', ElfFormat.DT_INIT : '0x%x', ElfFormat.DT_FINI : '0x%x', ElfFormat.DT_SONAME : '%u', ElfFormat.DT_RPATH : '%u', ElfFormat.DT_SYMBOLIC : '0x%x', ElfFormat.DT_REL : '0x%x', ElfFormat.DT_RELSZ : '%u (bytes)', ElfFormat.DT_RELENT : '%u (bytes)', ElfFormat.DT_PLTREL : '%u', ElfFormat.DT_DEBUG : '0x%x', ElfFormat.DT_TEXTREL : '0x%x', ElfFormat.DT_JMPREL : '0x%x', ElfFormat.DT_BIND_NOW : '0x%x', ElfFormat.DT_INIT_ARRAY : '0x%x', ElfFormat.DT_FINI_ARRAY : '0x%x', ElfFormat.DT_INIT_ARRAYSZ : '%u (bytes)', ElfFormat.DT_FINI_ARRAYSZ : '%u (bytes)', ElfFormat.DT_RUNPATH : '%u', ElfFormat.DT_FLAGS : '0x%x', ElfFormat.DT_ENCODING : '0x%x', ElfFormat.DT_PREINIT_ARRAY : '%u (bytes)', ElfFormat.DT_PREINIT_ARRAYSZ : '%u (bytes)' } # Find the right segment & data phdr = fmt.find_program_by_type(ElfFormat.PT_DYNAMIC) assert(phdr.p_filesz % fmt.sizeof_dyn == 0) count = int(phdr.p_filesz / fmt.sizeof_dyn) # Fix the item counter !? for i in range(count): item = fmt.find_dynamic_item_by_index(i) if item.d_tag == ElfFormat.DT_NULL: break count = i + 1 # Get the string location strtab_pos = None strtab_addr = None for i in range(count): item = fmt.find_dynamic_item_by_index(i) if item.d_tag == ElfFormat.DT_STRTAB: strtab_addr = item['d_un.d_ptr'] break loc = fmt.translate_address_into_vmpa(strtab_addr) assert(loc) strtab_pos = loc.phys # Final display print('Dynamic section at offset 0x%x contains %u %s:' % (phdr.p_offset, count, 'entries' if count > 1 else 'entry')) line = ' {0:<10} {1:<30} {2}' print(line.format(' Tag', ' Type', ' Name/Value')) line = ' {0} {1:<30} {2}' for i in range(count): item = fmt.find_dynamic_item_by_index(i) item_type = '(%s)' % elf_dynamic_types[item.d_tag] if item.d_tag in elf_dynamic_types.keys() else '(UNKNOWN)' item_format = elf_dynamic_values[item.d_tag] if item.d_tag in elf_dynamic_values.keys() else '0x%x' if '%s' in item_format: if strtab_pos is None: item_value = item_format % item['d_un.d_val'] else: item_value = item_format % _get_string(fmt, strtab_pos, item['d_un.d_val']) elif '%u' in item_format: item_value = item_format % item['d_un.d_val'] else: item_value = item_format % item['d_un.d_ptr'] print(line.format('0x%08x' % item.d_tag, item_type, item_value)) if item.d_tag == ElfFormat.DT_NULL: break print() if __name__ == '__main__': parser = argparse.ArgumentParser(description='readelf - Displays information about ELF files.', add_help=False) parser.add_argument('-H', '--help', action='store_true', help='Display the command line options understood by readelf.') parser.add_argument('-a', '--all', action='store_true', help='Equivalent to specifying --file-header, --segments, --sections, --dynamic.') parser.add_argument('-h', '--file-header', action='store_true', help='Displays the information contained in the ELF header at the start of the file.') parser.add_argument('-l', '--program-headers', '--segments', action='store_true', help='Displays the information contained in the file\'s segment headers, if it has any.') parser.add_argument('-S', '--section-headers', '--sections', action='store_true', help='Displays the information contained in the file\'s section headers, if it has any.') parser.add_argument('-d', '--dynamic', action='store_true', help='Displays the contents of the file\'s dynamic section, if it has one.') parser.add_argument('elffile', type=str, help='The object file to be examined') args = parser.parse_args() if args.help: parser.print_help() sys.exit(1) cnt = FileContent(args.elffile) fmt = ElfFormat(cnt) binary = LoadedBinary(fmt) binary.analyze_and_wait() print() if args.all or args.file_header: display_elf_header(fmt) if args.all or args.program_headers: display_elf_program_headers(fmt) if args.all or args.section_headers: display_elf_section_headers(fmt) if args.all or args.dynamic: display_elf_dynamic_items(fmt)