Files
lief-project-LIEF/api/python/examples/elf_reader.py
T
2024-06-22 07:09:17 +02:00

646 lines
22 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Description
# -----------
# Print information about an ELF binary
import os
import sys
import textwrap
import traceback
import lief
from lief import ELF
import argparse
terminal_rows, terminal_columns = 100, 110
try:
terminal_rows, terminal_columns = os.popen('stty size', 'r').read().split()
except ValueError:
pass
terminal_columns = int(terminal_columns) - 10
terminal_rows = int(terminal_rows)
class exceptions_handler(object):
func = None
def __init__(self, exceptions, on_except_callback=None):
self.exceptions = exceptions
self.on_except_callback = on_except_callback
def __call__(self, *args, **kwargs):
if self.func is None:
self.func = args[0]
return self
try:
return self.func(*args, **kwargs)
except self.exceptions as e:
if self.on_except_callback is not None:
self.on_except_callback(e)
else:
print("-" * 60)
print("Exception in {}: {}".format(self.func.__name__, e))
exc_type, exc_value, exc_traceback = sys.exc_info()
traceback.print_tb(exc_traceback)
print("-" * 60)
@exceptions_handler(Exception)
def print_header(binary):
header = binary.header
identity = header.identity
print("== Header ==\n")
format_str = "{:<30} {:<30}"
format_hex = "{:<30} 0x{:<13x}"
format_dec = "{:<30} {:<30d}"
format_ide = "{:<30} {:<02x} {:<02x} {:<02x} {:<02x}"
eflags_str = ""
if header.machine_type == lief.ELF.ARCH.ARM:
eflags_str = " - ".join([str(s).split(".")[-1] for s in header.arm_flags_list])
if header.machine_type in [lief.ELF.ARCH.MIPS, lief.ELF.ARCH.MIPS_RS3_LE, lief.ELF.ARCH.MIPS_X]:
eflags_str = " - ".join([str(s).split(".")[-1] for s in header.mips_flags_list])
if header.machine_type == lief.ELF.ARCH.PPC64:
eflags_str = " - ".join([str(s).split(".")[-1] for s in header.ppc64_flags_list])
if header.machine_type == lief.ELF.ARCH.HEXAGON:
eflags_str = " - ".join([str(s).split(".")[-1] for s in header.hexagon_flags_list])
if header.machine_type == lief.ELF.ARCH.LOONGARCH:
eflags_str = " - ".join([str(s).split(".")[-1] for s in header.loongarch_flags_list])
print(identity)
print(format_ide.format("Magic:", identity[0], identity[1], identity[2], identity[3]))
print(format_str.format("Class:", str(header.identity_class).split(".")[-1]))
print(format_str.format("Endianness:", str(header.identity_data).split(".")[-1]))
print(format_str.format("Version:", str(header.identity_version).split(".")[-1]))
print(format_str.format("OS/ABI:", str(header.identity_os_abi).split(".")[-1]))
print(format_dec.format("ABI Version:", header.identity_abi_version))
print(format_str.format("File Type:", str(header.file_type).split(".")[-1]))
print(format_str.format("Machine Type:", str(header.machine_type).split(".")[-1]))
print(format_str.format("Object File Version:", str(header.object_file_version).split(".")[-1]))
print(format_hex.format("Entry Point:", header.entrypoint))
print(format_hex.format("Program Header Offset:", header.program_header_offset))
print(format_hex.format("Section Header Offset:", header.section_header_offset))
print(format_hex.format("Processor flags:", header.processor_flag) + eflags_str)
print(format_dec.format("Header Size:", header.header_size))
print(format_dec.format("Program Header Size:", header.program_header_size))
print(format_dec.format("Section Header Size:", header.section_header_size))
print(format_dec.format("Number of segments:", header.numberof_segments))
print(format_dec.format("Number of sections:", header.numberof_sections))
print("")
@exceptions_handler(Exception)
def print_sections(binary):
sections = binary.sections
if len(sections) > 0:
print("== Sections ==\n")
f_title = "|{:<30} | {:<12}| {:<17}| {:<12}| {:<10}| {:<8}| {:<8}|"
f_value = "|{:<30} | {:<12}| 0x{:<14x} | 0x{:<10x}| 0x{:<8x}| {:<8.2f}| {:<10}"
print(f_title.format("Name", "Type", "Virtual address", "File offset", "Size", "Entropy", "Segment(s)"))
for section in sections:
segments_str = " - ".join([str(s.type).split(".")[-1] for s in section.segments])
print(f_value.format(
section.name,
str(section.type).split(".")[-1],
section.virtual_address,
section.file_offset,
section.size,
abs(section.entropy),
segments_str))
print("")
else:
print("No sections")
@exceptions_handler(Exception)
def print_segments(binary):
segments = binary.segments
# Segments
if len(segments) > 0:
print("== Segments ==\n")
f_title = "|{:<30} | {:<10}| {:<18}| {:<17}| {:<17}| {:<17}| {:<19}|"
f_value = "|{:<30} | {:<10}| 0x{:<16x}| 0x{:<15x}| 0x{:<15x}| 0x{:<15x}| {}"
print(f_title.format("Type",
"Flags", "File offset", "Virtual Address", "Virtual Size", "Size", "Sections"))
for segment in segments:
sections = segment.sections
s = ", ".join([section.name for section in sections])
flags_str = ["-"] * 3
if ELF.Segment.FLAGS.R in segment:
flags_str[0] = "r"
if ELF.Segment.FLAGS.W in segment:
flags_str[1] = "w"
if ELF.Segment.FLAGS.X in segment:
flags_str[2] = "x"
flags_str = "".join(flags_str)
print(f_value.format(
str(segment.type).split(".")[-1],
flags_str,
segment.file_offset,
segment.virtual_address,
segment.virtual_size,
segment.physical_size, s))
print("")
else:
print("No segments")
@exceptions_handler(Exception)
def print_dynamic_entries(binary):
dynamic_entries = binary.dynamic_entries
# Dynamic entries
if len(dynamic_entries) == 0:
return
print("== Dynamic entries ==\n")
f_title = "|{:<16} | {:<10}| {:<20}|"
f_value = "|{:<16} | 0x{:<8x}| {:<20}|"
print(f_title.format("Tag", "Value", "Info"))
for entry in dynamic_entries:
if entry.tag == ELF.DynamicEntry.TAG.NULL:
continue
if entry.tag in [ELF.DynamicEntry.TAG.SONAME, ELF.DynamicEntry.TAG.NEEDED, ELF.DynamicEntry.TAG.RUNPATH, ELF.DynamicEntry.TAG.RPATH]:
print(f_value.format(str(entry.tag).split(".")[-1], entry.value, entry.name))
elif type(entry) is ELF.DynamicEntryArray: # [ELF.DynamicEntry.TAG.INIT_ARRAY,ELF.DynamicEntry.TAG.FINI_ARRAY]:
print(f_value.format(str(entry.tag).split(".")[-1], entry.value, ", ".join(map(hex, entry.array))))
elif entry.tag == ELF.DynamicEntry.TAG.FLAGS:
flags_str = " - ".join([str(ELF.DynamicEntryFlags.FLAG(s)).split(".")[-1] for s in entry.flags])
print(f_value.format(str(entry.tag).split(".")[-1], entry.value, flags_str))
elif entry.tag == ELF.DynamicEntry.TAG.FLAGS_1:
flags_str = " - ".join([str(ELF.DynamicEntryFlags.FLAG(s)).split(".")[-1] for s in entry.flags])
print(f_value.format(str(entry.tag).split(".")[-1], entry.value, flags_str))
else:
print(f_value.format(str(entry.tag).split(".")[-1], entry.value, ""))
print("")
@exceptions_handler(Exception)
def print_symbols(symbols, no_trunc):
can_demangle = len(symbols) > 0 and len(symbols[0].demangled_name) > 0
if can_demangle:
maxsize = max([len(symbol.demangled_name) for symbol in symbols])
else:
maxsize = max([len(symbol.name) for symbol in symbols])
SIZE = 70
maxsize = min(maxsize, terminal_columns - SIZE) if terminal_columns > SIZE else terminal_columns
f_title = "|{:<" + str(maxsize) + "} | {:<7}| {:<8}| {:<10}| {:<8}| {:<4}| {:<14}|"
f_value = "|{:<" + str(maxsize) + "} | {:<7}| {:<8x}| {:<10}| {:<8}| {:<4}| {:<14}|"
print(f_title.format("Name", "Type", "Value", "Visibility", "Binding", "I/E", "Version"))
for symbol in symbols:
symbol_version = symbol.symbol_version if symbol.has_version else ""
import_export = ""
if symbol.imported:
import_export = "I"
if symbol.exported:
import_export = "E"
symbol_name = symbol.demangled_name
if len(symbol_name) == 0:
symbol_name = symbol.name
wrapped = textwrap.wrap(symbol_name, maxsize)
if len(wrapped) <= 1 or no_trunc:
symbol_name = symbol_name
else:
symbol_name = wrapped[0][:-3] + "..."
print(f_value.format(
symbol_name,
str(symbol.type).split(".")[-1],
symbol.value,
str(symbol.visibility).split(".")[-1],
str(symbol.binding).split(".")[-1],
import_export,
str(symbol_version)
))
@exceptions_handler(Exception)
def print_dynamic_symbols(binary, args):
print("== Dynamic symbols ==\n")
print_symbols(binary.dynamic_symbols, args.no_trunc)
@exceptions_handler(Exception)
def print_symtab_symbols(binary, args):
print("== Symtab symbols ==\n")
print_symbols(binary.symtab_symbols, args.no_trunc)
@exceptions_handler(Exception)
def print_relocations(binary, relocations):
f_title = "|{:<10} | {:<10}| {:<8}| {:<8}| {:<8}| {:<15}| {:<30} |"
f_value = "|0x{:<8x} | {:<10}| {:<8d}| {:<8d}| {:<8x}| {:<15}| {:<30} |"
print(f_title.format("Address", "Type", "Info", "Size", "Addend", "Purpose", "Symbol"))
for relocation in relocations:
type = str(relocation.type)
if binary.header.machine_type == ELF.ARCH.x86_64:
type = str(relocation.type)
elif binary.header.machine_type == ELF.ARCH.i386:
type = str(relocation.type)
elif binary.header.machine_type == ELF.ARCH.ARM:
type = str(relocation.type)
elif binary.header.machine_type == ELF.ARCH.AARCH64:
type = str(relocation.type)
symbol_name = ""
if relocation.has_symbol:
symbol: lief.ELF.Symbol = relocation.symbol
if len(symbol.name) > 0:
symbol_name = symbol.name
elif symbol.type == lief.ELF.Symbol.TYPE.SECTION:
shndx = symbol.shndx
sections = binary.sections
if 0 < shndx and shndx < len(sections):
symbol_name = sections[shndx].name + " + " + hex(relocation.addend)
else:
symbol_name = "<section #{}>".format(shndx)
print(f_value.format(
relocation.address,
type.split(".")[-1],
relocation.info,
relocation.size,
relocation.addend,
str(relocation.purpose).split(".")[-1],
symbol_name))
@exceptions_handler(Exception)
def print_all_relocations(binary):
dynamicrelocations = binary.dynamic_relocations
pltgot_relocations = binary.pltgot_relocations
object_relocations = binary.object_relocations
if len(dynamicrelocations) > 0:
print("== Dynamic Relocations ==\n")
print_relocations(binary, dynamicrelocations)
if len(pltgot_relocations) > 0:
print("== PLT/GOT Relocations ==\n")
print_relocations(binary, pltgot_relocations)
if len(object_relocations) > 0:
print("== Object Relocations ==\n")
print_relocations(binary, object_relocations)
@exceptions_handler(Exception)
def print_exported_symbols(binary, args):
symbols = binary.exported_symbols
print("== Exported symbols ==\n")
if len(symbols) == 0:
print("No exports!")
return
print_symbols(symbols, args.no_trunc)
@exceptions_handler(Exception)
def print_imported_symbols(binary, args):
symbols = binary.imported_symbols
print("== Imported symbols ==\n")
if len(symbols) == 0:
print("No imports!")
return
print_symbols(symbols, args.no_trunc)
@exceptions_handler(Exception)
def print_information(binary):
print("== Information ==\n")
format_str = "{:<30} {:<30}"
format_hex = "{:<30} 0x{:<28x}"
format_dec = "{:<30} {:<30d}"
print(format_hex.format("Address base:", binary.imagebase))
print(format_hex.format("Virtual size:", binary.virtual_size))
print(format_str.format("PIE:", str(binary.is_pie)))
print(format_str.format("NX:", str(binary.has_nx)))
@exceptions_handler(Exception)
def print_gnu_hash(binary):
print("== GNU Hash ==\n")
if not binary.use_gnu_hash:
return
gnu_hash = binary.gnu_hash
format_str = "{:<30} {}"
format_hex = "{:<30} 0x{:<28x}"
format_dec = "{:<30} {:<30d}"
print(format_dec.format("Number of buckets:", gnu_hash.nb_buckets))
print(format_dec.format("First symbol index:", gnu_hash.symbol_index))
print(format_hex.format("Shift Count:", gnu_hash.shift2))
print(format_str.format("Bloom filters:", gnu_hash.bloom_filters))
print(format_str.format("Buckets:", gnu_hash.buckets))
print(format_str.format("Hash values:", gnu_hash.hash_values))
@exceptions_handler(Exception)
def print_sysv_hash(binary):
print("== SYSV Hash ==\n")
if not binary.use_sysv_hash:
return
sysv_hash = binary.sysv_hash
format_str = "{:<30} {}"
format_hex = "{:<30} 0x{:<28x}"
format_dec = "{:<30} {:<30d}"
print(format_dec.format("Number of buckets:", sysv_hash.nbucket))
print(format_dec.format("Number of chains:", sysv_hash.nchain))
print(format_str.format("Buckets:", sysv_hash.buckets))
print(format_str.format("Chains:", sysv_hash.chains))
@exceptions_handler(Exception)
def print_notes(binary):
print("== Notes ==\n")
format_str = "{:<19} {}"
format_hex = "{:<19} 0x{:<28x}"
format_dec = "{:<19} {:<30d}"
notes = binary.notes
for idx, note in enumerate(notes):
description = note.description
description_str = " ".join(map(lambda e : "{:02x}".format(e), description[:16]))
if len(description) > 16:
description_str += " ..."
print("Note #{:d}".format(idx))
type_str = note.type_core if note.is_core else note.type
type_str = str(type_str).split(".")[-1]
print(format_str.format("Name:", note.name))
print(format_str.format("Type:", type_str))
print(format_str.format("Description:", description_str))
note_details = note.details
if isinstance(note_details, lief.ELF.AndroidIdent):
print(format_dec.format("SDK Version:", note_details.sdk_version))
print(format_str.format("NDK Version:", note_details.ndk_version))
print(format_str.format("NDK build number:", note_details.ndk_build_number))
if isinstance(note_details, lief.ELF.NoteAbi):
version = note_details.version
version_str = "{:d}.{:d}.{:d}".format(version[0], version[1], version[2])
print(format_str.format("ABI:", note_details.abi))
print(format_str.format("Version:", version_str))
if note.type == ELF.Note.TYPE.GNU_GOLD_VERSION:
print(format_str.format("Version:", "".join(map(chr, note.description))))
if note.is_core:
print(note_details)
print("\n")
@exceptions_handler(Exception)
def print_ctor(binary):
print("== Constructors ==\n")
print("Functions: ({:d})".format(len(binary.ctor_functions)))
for idx, f in enumerate(binary.ctor_functions):
print(" [{:d}] {}: 0x{:x}".format(idx, f.name, f.address))
@exceptions_handler(Exception)
def print_strings(binary):
print("== Strings ==\n")
strings = binary.strings
print("Strings: ({:d})".format(len(binary.strings)))
for s in strings:
print(" {}".format(s))
@exceptions_handler(Exception)
def print_functions(binary):
print("== Functions ==\n")
functions = binary.functions
print("Functions: ({:d})".format(len(functions)))
for idx, f in enumerate(functions):
print(" [{:d}] {}: 0x{:x}".format(idx, f.name, f.address))
def main():
parser = argparse.ArgumentParser(add_help=False, prog=sys.argv[0])
parser.add_argument("elf_file")
parser.add_argument('-a', '--all',
action='store_true', dest='show_all',
help='Equivalent to: -h -l -S -s -r -d -V')
parser.add_argument('-d', '--dynamic',
action='store_true', dest='show_dynamic_tags',
help='Display the dynamic section')
parser.add_argument('-H', '--help',
action='help', dest='help',
help='Display this information')
parser.add_argument('-h', '--file-header',
action='store_true', dest='show_file_header',
help='Display the ELF file header')
parser.add_argument('-i', '--imported',
action='store_true', dest='show_imported_symbols',
help='Display imported symbols')
parser.add_argument('-l', '--program-headers', '--segments',
action='store_true', dest='show_program_header',
help='Display the program headers')
parser.add_argument('-S', '--section-headers', '--sections',
action='store_true', dest='show_section_header',
help="Display the sections' headers")
parser.add_argument('-e', '--headers',
action='store_true', dest='show_all_headers',
help='Equivalent to: -h -l -S')
parser.add_argument('-s', '--symbols', '--syms',
action='store_true', dest='show_symbols',
help='Display the symbol table')
parser.add_argument('--dynamic-symbols', '--dsyms',
action='store_true', dest='show_dynamic_symbols',
help='Display the dynamic symbols')
parser.add_argument('--symtab-symbols', '--ssyms',
action='store_true', dest='show_symtab_symbols',
help='Display the symtab symbols')
parser.add_argument('-r', '--relocs',
action='store_true', dest='show_relocs',
help='Display the relocations (if present)')
parser.add_argument('-V', '--version-info',
action='store_true', dest='show_version_info',
help='Display the version sections (if present)')
parser.add_argument('-x', '--exported',
action='store_true', dest='show_exported_symbols',
help='Display exported symbols')
parser.add_argument('--gnu-hash',
action='store_true', dest='show_gnu_hash',
help='Display GNU Hash')
parser.add_argument('--sysv-hash',
action='store_true', dest='show_sysv_hash',
help='Display SYSV Hash')
parser.add_argument('-n', '--notes',
action='store_true', dest='show_notes',
help='Display Notes')
parser.add_argument('--no-trunc',
action='store_true', dest='no_trunc',
default=False,
help='Do not trunc symbol names ...')
parser.add_argument('--ctor',
action='store_true', dest='show_ctor',
help='Constructor functions')
parser.add_argument('--strings',
action='store_true', dest='show_strings',
help='Strings present in the current ELF')
parser.add_argument('--functions',
action='store_true', dest='show_functions',
help='List all function addresses found')
# Logging setup
logger_group = parser.add_argument_group('Logger')
verbosity = logger_group.add_mutually_exclusive_group()
verbosity.add_argument('--debug',
dest='main_verbosity',
action='store_const',
const=lief.logging.LEVEL.DEBUG)
verbosity.add_argument('--trace',
dest='main_verbosity',
action='store_const',
const=lief.logging.LEVEL.TRACE)
verbosity.add_argument('--info',
dest='main_verbosity',
action='store_const',
const=lief.logging.LEVEL.INFO)
verbosity.add_argument('--warn',
dest='main_verbosity',
action='store_const',
const=lief.logging.LEVEL.WARN)
verbosity.add_argument('--err',
dest='main_verbosity',
action='store_const',
const=lief.logging.LEVEL.ERROR)
verbosity.add_argument('--critical',
dest='main_verbosity',
action='store_const',
const=lief.logging.LEVEL.CRITICAL)
parser.set_defaults(main_verbosity=lief.logging.LEVEL.WARN)
args = parser.parse_args()
lief.logging.set_level(args.main_verbosity)
binary = ELF.parse(args.elf_file)
print_information(binary)
if args.show_all:
do_file_header = do_section_header = do_program_header = True
if args.show_all_headers:
do_file_header = do_section_header = do_program_header = True
else:
do_file_header = args.show_file_header
do_section_header = args.show_section_header
do_program_header = args.show_program_header
if do_file_header or args.show_all:
print_header(binary)
if do_section_header or args.show_all:
print_sections(binary)
if do_program_header or args.show_all:
print_segments(binary)
if args.show_dynamic_tags or args.show_all:
print_dynamic_entries(binary)
if (args.show_symbols or args.show_all or args.show_dynamic_symbols) and len(binary.dynamic_symbols) > 0:
print_dynamic_symbols(binary, args)
if (args.show_symbols or args.show_all or args.show_symtab_symbols) and len(binary.symtab_symbols) > 0:
print_symtab_symbols(binary, args)
if args.show_relocs or args.show_all:
print_all_relocations(binary)
if args.show_imported_symbols or args.show_all:
print_imported_symbols(binary, args)
if args.show_exported_symbols or args.show_all:
print_exported_symbols(binary, args)
if (args.show_gnu_hash or args.show_all) and binary.use_gnu_hash:
print_gnu_hash(binary)
if (args.show_sysv_hash or args.show_all) and binary.use_sysv_hash:
print_sysv_hash(binary)
if args.show_notes or args.show_all:
print_notes(binary)
if args.show_ctor or args.show_all:
print_ctor(binary)
if args.show_strings or args.show_all:
print_strings(binary)
if args.show_functions:
print_functions(binary)
if __name__ == "__main__":
main()