Source code for tools.imports

"""Tool: get_binary_imports — imported functions grouped by library."""

from __future__ import annotations

from typing import Any

import lief

from app import mcp
from helpers import safe_str, hex_addr, safe_enum, format_name, parse_binary, _error


[docs] @mcp.tool() def get_binary_imports(file_path: str, limit: int = 0) -> dict: """Imported functions, grouped by library. For PE binaries imports are grouped by DLL. For ELF binaries imports are grouped by shared library (via symbol version requirements) and include binding/type info. For Mach-O binaries imports are grouped by dylib (via binding info) and include addresses. Set *limit* > 0 to cap the total number of entries returned. """ try: binary = parse_binary(file_path) except ValueError as exc: return _error(str(exc)) total = 0 if isinstance(binary, lief.PE.Binary): libraries: list[dict] = [] for imp in binary.imports: entries = [] for entry in imp.entries: if 0 < limit <= total: break entries.append({ "name": safe_str(entry.name) or f"ordinal#{entry.ordinal}", "hint": entry.hint, "iat_address": hex_addr(entry.iat_address), }) total += 1 libraries.append({ "library": safe_str(imp.name), "functions": entries, }) if 0 < limit <= total: break return { "format": "PE", "total_returned": total, "limited": limit > 0 and total >= limit, "imports": libraries, } if isinstance(binary, lief.ELF.Binary): # Build version-index -> library name mapping from .gnu.version_r ver_to_lib: dict[int, str] = {} for req in binary.symbols_version_requirement: lib_name = safe_str(req.name) for aux in req.get_auxiliary_symbols(): ver_to_lib[aux.other] = lib_name lib_imports: dict[str, list[dict]] = {} for sym in binary.imported_symbols: if 0 < limit <= total: break name = safe_str(sym.name) if not name: continue # Determine source library via symbol version lib_name = "" sv = sym.symbol_version if sv is not None and sv.value != 0: lib_name = ver_to_lib.get(sv.value, "") lib_imports.setdefault(lib_name or "unknown", []).append({ "name": name, "binding": safe_enum(sym.binding), "type": safe_enum(sym.type), "value": hex_addr(sym.value), }) total += 1 libraries: list[dict] = [ {"library": lib, "functions": funcs} for lib, funcs in lib_imports.items() ] return { "format": "ELF", "total_returned": total, "limited": limit > 0 and total >= limit, "imports": libraries, } if isinstance(binary, lief.MachO.Binary): lib_imports_macho: dict[str, list[dict]] = {} seen: set[str] = set() # Use binding info to associate imports with their source dylib for info in binary.dyld_info.bindings: if 0 < limit <= total: break sym = info.symbol if sym is None: continue name = safe_str(sym.name) if not name or name in seen: continue seen.add(name) lib_name = safe_str(info.library.name) if info.has_library else "unknown" lib_imports_macho.setdefault(lib_name, []).append({ "name": name, "address": hex_addr(info.address), }) total += 1 # Fall back to imported_symbols for anything not covered by bindings for sym in binary.imported_symbols: if 0 < limit <= total: break name = safe_str(sym.name) if not name or name in seen: continue seen.add(name) lib_imports_macho.setdefault("unknown", []).append({ "name": name, "address": hex_addr(sym.value), }) total += 1 macho_libraries: list[dict] = [ {"library": lib, "functions": funcs} for lib, funcs in lib_imports_macho.items() ] return { "format": "Mach-O", "total_returned": total, "limited": limit > 0 and total >= limit, "imports": macho_libraries, } # Fallback for unknown formats funcs = [] for fn in binary.imported_functions: if 0 < limit <= total: break funcs.append(safe_str(fn.name) if hasattr(fn, "name") else safe_str(fn)) total += 1 return { "format": format_name().get(binary.format, "Unknown"), "total_returned": total, "limited": limit > 0 and total >= limit, "imports": funcs, }