#!/usr/bin/env python3 """ elf_static_py.py - the parts of readelf/strings this kit needs, in pure Python. A locked-down RHEL 9.6 host often has no binutils (and installing it pulls elfutils-debuginfod-client, which is not always available offline). pyelftools has no such problem - it is one noarch RPM - so elf-static.sh falls back here. elf_static_py.py header|dynamic|undef-syms|sections|hardening|strings FILE Each subcommand prints the same information readelf would, in the same shape elf-static.sh expects. Requires python3-pyelftools (rpms/). """ import sys try: from elftools.elf.elffile import ELFFile from elftools.elf.dynamic import DynamicSection from elftools.elf.sections import SymbolTableSection except ImportError: sys.stderr.write( "pyelftools is not installed.\n" " dnf install -y --disablerepo='*' --nogpgcheck rpms/python3-pyelftools-*.rpm\n" "or install binutils so that readelf is available.\n") sys.exit(3) def _tag(t): """DT_ tag name, across pyelftools versions.""" return t.entry.d_tag if isinstance(t.entry.d_tag, str) else str(t.entry.d_tag) def cmd_header(e): h = e.header print("ELF Header:") print(" Class: %s" % e.elfclass) print(" Data: %s" % ("2's complement, little endian" if e.little_endian else "big endian")) print(" Type: %s" % h["e_type"]) print(" Machine: %s" % h["e_machine"]) print(" Entry point address: 0x%x" % h["e_entry"]) print(" Number of program headers: %d" % h["e_phnum"]) print(" Number of section headers: %d" % h["e_shnum"]) def cmd_dynamic(e): interp = None for seg in e.iter_segments(): if seg.header.p_type == "PT_INTERP": interp = seg.get_interp_name() if interp: print(" [Requesting program interpreter: %s]" % interp) found = False for sec in e.iter_sections(): if not isinstance(sec, DynamicSection): continue for t in sec.iter_tags(): tag = _tag(t) if tag in ("DT_NEEDED", "DT_SONAME", "DT_RPATH", "DT_RUNPATH"): found = True val = getattr(t, tag[3:].lower(), None) or t.entry.d_val print(" 0x0000000000000001 (%-8s) %s" % (tag[3:], val)) if not found: print("(no dynamic section - static binary?)") def cmd_undef_syms(e): out = set() for sec in e.iter_sections(): if not isinstance(sec, SymbolTableSection) or sec.name != ".dynsym": continue for sym in sec.iter_symbols(): shndx = sym.entry["st_shndx"] undef = (shndx == "SHN_UNDEF") or (isinstance(shndx, int) and shndx == 0) if undef and sym.name: out.add(sym.name.split("@")[0]) for n in sorted(out): print(n) def cmd_sections(e): for i, sec in enumerate(e.iter_sections()): print(" [%2d] %s" % (i, sec.name)) def cmd_hardening(e): h = e.header pie = h["e_type"] == "ET_DYN" nx, relro, bind_now = "yes", "none", False for seg in e.iter_segments(): t = seg.header.p_type if t == "PT_GNU_STACK" and (seg.header.p_flags & 0x1): nx = "NO - executable stack" if t == "PT_GNU_RELRO": relro = "partial" for sec in e.iter_sections(): if isinstance(sec, DynamicSection): for t in sec.iter_tags(): tag = _tag(t) if tag == "DT_BIND_NOW": bind_now = True if tag == "DT_FLAGS" and (t.entry.d_val & 0x8): # DF_BIND_NOW bind_now = True if tag == "DT_FLAGS_1" and (t.entry.d_val & 0x1): # DF_1_NOW bind_now = True if relro == "partial" and bind_now: relro = "full" canary = any( s.name.startswith("__stack_chk") for sec in e.iter_sections() if isinstance(sec, SymbolTableSection) for s in sec.iter_symbols()) print("PIE : %s" % ("yes" if pie else "no (%s)" % h["e_type"])) print("NX (stack) : %s" % nx) print("RELRO : %s" % relro) print("Canary : %s" % ("yes" if canary else "not visible")) print("Stripped : %s" % ("no" if e.get_section_by_name(".symtab") else "yes")) print("Sections : %d (very low count + high entropy = packed)" % h["e_shnum"]) def cmd_strings(path, minlen=6): """A stand-in for `strings -a`, good enough to surface embedded paths.""" with open(path, "rb") as fh: data = fh.read() run = bytearray() for b in data: if 32 <= b < 127: run.append(b) continue if len(run) >= minlen: print(run.decode("ascii", "replace")) run = bytearray() if len(run) >= minlen: print(run.decode("ascii", "replace")) def main(): if len(sys.argv) < 3: sys.stderr.write(__doc__) return 2 what, path = sys.argv[1], sys.argv[2] if what == "strings": cmd_strings(path) return 0 with open(path, "rb") as fh: e = ELFFile(fh) {"header": cmd_header, "dynamic": cmd_dynamic, "undef-syms": cmd_undef_syms, "sections": cmd_sections, "hardening": cmd_hardening}[what](e) return 0 if __name__ == "__main__": sys.exit(main())