Source code for tools.security

"""Tool: get_binary_security — security features and hardening."""

from __future__ import annotations

from typing import Any

import lief

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


def _pe_security(binary: lief.PE.Binary) -> dict:
    """Gather PE security features."""
    opt = binary.optional_header
    dll_chars = opt.dll_characteristics_lists

    def has_char(flag: lief.PE.OptionalHeader.DLL_CHARACTERISTICS) -> bool:
        return flag in dll_chars

    DLL = lief.PE.OptionalHeader.DLL_CHARACTERISTICS

    result: dict[str, Any] = {
        "aslr_dynamic_base": has_char(DLL.DYNAMIC_BASE),
        "aslr_high_entropy_va": has_char(DLL.HIGH_ENTROPY_VA),
        "dep_nx_compat": has_char(DLL.NX_COMPAT),
        "seh": not has_char(DLL.NO_SEH),
        "guard_cf": has_char(DLL.GUARD_CF),
        "force_integrity": has_char(DLL.FORCE_INTEGRITY),
        "appcontainer": has_char(DLL.APPCONTAINER),
        "is_pie": binary.is_pie,
        "has_nx": binary.has_nx,
    }

    # Code signing
    result["signed"] = binary.has_signatures
    if binary.has_signatures:
        sig_status = str(binary.verify_signature())
        result["signature_verification"] = sig_status

    return result


def _elf_security(binary: lief.ELF.Binary) -> dict:
    """Gather ELF security features."""
    result: dict[str, Any] = {
        "has_nx": binary.has_nx,
        "is_pie": binary.is_pie,
    }

    # RELRO detection
    has_gnu_relro = False
    has_bind_now = False

    for seg in binary.segments:
        if seg.type == lief.ELF.Segment.TYPE.GNU_RELRO:
            has_gnu_relro = True
            break

    for entry in binary.dynamic_entries:
        if entry.tag == lief.ELF.DynamicEntry.TAG.BIND_NOW:
            has_bind_now = True
        if entry.tag == lief.ELF.DynamicEntry.TAG.FLAGS:
            # BIND_NOW can also appear as a FLAGS bit
            if hasattr(entry, "value") and (entry.value & 0x8):  # DF_BIND_NOW = 0x8
                has_bind_now = True

    if has_gnu_relro and has_bind_now:
        result["relro"] = "Full"
    elif has_gnu_relro:
        result["relro"] = "Partial"
    else:
        result["relro"] = "None"

    # Stack canary heuristic: presence of __stack_chk_fail in imports
    imported_names = set()
    for fn in binary.imported_functions:
        name = fn.name if hasattr(fn, "name") else str(fn)
        imported_names.add(name)
    result["stack_canary"] = "__stack_chk_fail" in imported_names

    # FORTIFY_SOURCE heuristic: any __*_chk function
    result["fortify_source"] = any(
        "_chk" in name for name in imported_names if name.startswith("__")
    )

    # Interpreter
    if binary.has_interpreter:
        result["interpreter"] = binary.interpreter

    return result


def _macho_security(binary: lief.MachO.Binary) -> dict:
    """Gather Mach-O security features."""
    result: dict[str, Any] = {
        "is_pie": binary.is_pie,
        "has_nx": binary.has_nx,
        "has_nx_stack": binary.has_nx_stack,
        "has_nx_heap": binary.has_nx_heap,
        "has_code_signature": binary.has_code_signature,
    }

    # Flags from the Mach-O header
    flag_names = [safe_enum(f) for f in binary.header.flags_list]
    result["header_flags"] = flag_names

    # Platform info
    if binary.has_build_version:
        bv = binary.build_version
        result["platform"] = safe_enum(bv.platform) if hasattr(bv, "platform") else None

    # ARC / stack canary heuristic
    imported_names = set()
    for fn in binary.imported_functions:
        name = fn.name if hasattr(fn, "name") else str(fn)
        imported_names.add(name)
    result["stack_canary"] = "___stack_chk_fail" in imported_names

    return result


[docs] @mcp.tool() def get_binary_security(file_path: str) -> dict: """Security features and hardening of a binary. PE: ASLR, DEP/NX, SEH, Control Flow Guard, code signing. ELF: NX, PIE, RELRO, stack canaries, FORTIFY_SOURCE. Mach-O: PIE, NX stack/heap, code signature, header flags. """ try: binary = parse_binary(file_path) except ValueError as exc: return _error(str(exc)) fmt = format_name().get(binary.format, "Unknown") if isinstance(binary, lief.PE.Binary): sec = _pe_security(binary) elif isinstance(binary, lief.ELF.Binary): sec = _elf_security(binary) elif isinstance(binary, lief.MachO.Binary): sec = _macho_security(binary) else: sec = { "has_nx": binary.has_nx, "is_pie": binary.is_pie, } sec["format"] = fmt return sec