mirror of
https://github.com/lief-project/LIEF
synced 2026-06-08 15:30:44 +00:00
151 lines
4.6 KiB
Python
151 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Analyze ELF shared library dependencies and symbol versioning.
|
|
|
|
A cross-platform alternative to ldd that works without executing the binary.
|
|
Lists shared library dependencies, RPATH/RUNPATH search paths, and optionally
|
|
shows imported symbol versions (useful for checking glibc compatibility).
|
|
|
|
Inspired by conda-build's liefldd.py and Bitcoin's symbol-check.py.
|
|
|
|
Example:
|
|
|
|
$ python elf_deps.py /usr/bin/ls
|
|
Dependencies: /usr/bin/ls
|
|
========================================
|
|
Interpreter: /lib64/ld-linux-x86-64.so.2
|
|
Type: DYN (Position-Independent Executable)
|
|
|
|
Shared Libraries:
|
|
libselinux.so.1
|
|
libc.so.6
|
|
ld-linux-x86-64.so.2
|
|
|
|
$ python elf_deps.py --versions /usr/bin/ls
|
|
...
|
|
Symbol Versions:
|
|
GLIBC_2.2.5 (25 symbols)
|
|
GLIBC_2.3 (1 symbols)
|
|
GLIBC_2.17 (2 symbols)
|
|
"""
|
|
|
|
import argparse
|
|
from collections import defaultdict
|
|
|
|
import lief
|
|
|
|
HEADER_TYPES = {
|
|
lief.ELF.Header.FILE_TYPE.EXEC: "EXEC (Executable)",
|
|
lief.ELF.Header.FILE_TYPE.DYN: "DYN (Shared Object / PIE)",
|
|
lief.ELF.Header.FILE_TYPE.REL: "REL (Relocatable)",
|
|
lief.ELF.Header.FILE_TYPE.CORE: "CORE (Core Dump)",
|
|
}
|
|
|
|
|
|
def analyze_deps(filename, show_versions=False):
|
|
"""Analyze ELF dependencies and symbol version requirements."""
|
|
binary = lief.ELF.parse(filename)
|
|
if binary is None:
|
|
print(f"Error: failed to parse '{filename}' as ELF")
|
|
return False
|
|
|
|
file_type = HEADER_TYPES.get(binary.header.file_type, str(binary.header.file_type))
|
|
interpreter = binary.interpreter or "(none — statically linked)"
|
|
|
|
print(f"Dependencies: {filename}")
|
|
print("=" * 48)
|
|
print(f" Interpreter: {interpreter}")
|
|
print(f" Type: {file_type}")
|
|
print(f" Arch: {binary.header.machine_type.name}")
|
|
print()
|
|
|
|
# Shared libraries (DT_NEEDED entries)
|
|
libraries = list(binary.libraries)
|
|
if libraries:
|
|
print(" Shared Libraries:")
|
|
for lib in libraries:
|
|
print(f" {lib}")
|
|
else:
|
|
print(" Shared Libraries: (none — statically linked)")
|
|
print()
|
|
|
|
# RPATH and RUNPATH
|
|
rpath = None
|
|
runpath = None
|
|
for entry in binary.dynamic_entries:
|
|
if isinstance(entry, lief.ELF.DynamicEntryRpath):
|
|
rpath = entry.rpath
|
|
elif isinstance(entry, lief.ELF.DynamicEntryRunPath):
|
|
runpath = entry.runpath
|
|
|
|
if rpath or runpath:
|
|
print(" Search Paths:")
|
|
if rpath:
|
|
print(f" RPATH: {rpath}")
|
|
if runpath:
|
|
print(f" RUNPATH: {runpath}")
|
|
print()
|
|
|
|
# Symbol versions
|
|
if show_versions:
|
|
version_counts = defaultdict(int)
|
|
version_symbols = defaultdict(list)
|
|
|
|
for sym in binary.imported_symbols:
|
|
if not sym.has_version:
|
|
continue
|
|
version = sym.symbol_version
|
|
if not version.has_auxiliary_version:
|
|
continue
|
|
ver_name = version.symbol_version_auxiliary.name
|
|
version_counts[ver_name] += 1
|
|
version_symbols[ver_name].append(sym.name)
|
|
|
|
if version_counts:
|
|
print(" Symbol Versions:")
|
|
for ver_name in sorted(version_counts):
|
|
count = version_counts[ver_name]
|
|
print(f" {ver_name:<24} ({count} symbol{'s' if count != 1 else ''})")
|
|
print()
|
|
|
|
# Show highest required version per library prefix
|
|
lib_versions = defaultdict(list)
|
|
for ver_name in version_counts:
|
|
parts = ver_name.rsplit("_", 1)
|
|
if len(parts) == 2:
|
|
lib, ver_str = parts
|
|
try:
|
|
ver_tuple = tuple(int(x) for x in ver_str.split("."))
|
|
lib_versions[lib].append((ver_tuple, ver_name))
|
|
except ValueError:
|
|
pass
|
|
|
|
if lib_versions:
|
|
print(" Minimum Required Versions:")
|
|
for lib in sorted(lib_versions):
|
|
max_ver = max(lib_versions[lib], key=lambda x: x[0])
|
|
print(f" {lib}: {max_ver[1]}")
|
|
print()
|
|
|
|
return True
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Analyze ELF shared library dependencies"
|
|
)
|
|
parser.add_argument("binaries", nargs="+", help="ELF binaries to analyze")
|
|
parser.add_argument(
|
|
"-v",
|
|
"--versions",
|
|
action="store_true",
|
|
help="Show imported symbol version requirements",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
for path in args.binaries:
|
|
analyze_deps(path, show_versions=args.versions)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|