Skip to content

probsJustin/LD_PRELOAD_MCP

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 

Repository files navigation

ld-preload-mcp

An MCP (Model Context Protocol) server that exposes Linux dynamic-linker controls — LD_PRELOAD, LD_LIBRARY_PATH, LD_AUDIT, LD_DEBUG, /etc/ld.so.preload, ldconfig, ldd, readelf, and nm — as callable tools, plus helpers to generate and compile preload shared objects.

Intended for debugging linker issues, library-interposition development, observability work, and authorized security research.

Requirements

  • Linux (uses /proc and glibc-specific LD_* behavior)
  • Python ≥ 3.10
  • binutils (readelf, nm), glibc utilities (ldd, ldconfig), gcc for compilation tools

Install

python3 -m venv .venv
.venv/bin/pip install -e .

This installs a console script ld-preload-mcp that speaks MCP over stdio.

Wire it into an MCP client

Claude Code

claude mcp add ld-preload -- /absolute/path/to/.venv/bin/ld-preload-mcp

Any stdio-capable MCP client

{
  "mcpServers": {
    "ld-preload": {
      "command": "/absolute/path/to/.venv/bin/ld-preload-mcp"
    }
  }
}

Tools

Runtime control

Tool Purpose
run_with_preload Launch a command with any combination of LD_PRELOAD, LD_LIBRARY_PATH, LD_AUDIT, LD_DEBUG, LD_BIND_NOW, plus arbitrary extra env vars. Validates every preload/audit path exists before exec.
trace_library_loads Run a command under LD_DEBUG=<categories> (libs, symbols, bindings, reloc, files, versions, statistics, all, or help) and capture the linker's diagnostic stream.
get_dynamic_linker_env Report which LD_* variables are set in the server's own environment.

Process inspection

Tool Purpose
inspect_process From /proc/<pid>: exe path, cmdline, selected status fields, and the LD_* entries from environ. Deliberately does not return the full environment.
list_loaded_libraries Unique file-backed shared objects mapped into a PID via /proc/<pid>/maps.

ELF / binary inspection

Tool Purpose
ldd_binary Default: readelf -d (safe, does not execute). use_unsafe=true: real ldd, which executes the binary.
readelf_dynamic Dump .dynamic and program headers — DT_NEEDED, RPATH, RUNPATH, SONAME, PT_INTERP, etc.
list_exported_symbols nm -D against a shared object; optional --defined-only and C++ demangling.

Linker cache / search path

Tool Purpose
ldconfig_list Filtered view of ldconfig -p.
resolve_library Resolve a soname against extra_search_paths, LD_LIBRARY_PATH, the standard default directories, and the ldconfig cache. Reports every hit with its source.

System-wide preload

Tool Purpose
get_ld_so_preload Read /etc/ld.so.preload. Absence is reported, not treated as error.
set_ld_so_preload Write /etc/ld.so.preload. Refuses unless confirm=true and requires root.

Authoring

Tool Purpose
generate_preload_template Emit a C skeleton that wraps the named symbols with dlsym(RTLD_NEXT) pass-through stubs. Prototypes are generic void *name(void) — fix them to match the real signatures before compiling.
compile_preload_library gcc -shared -fPIC -o <out.so> <src.c> -ldl plus any extra CFLAGS/LDFLAGS you pass.

Example workflow

Trace every call to a custom symbol in a target binary:

generate_preload_template(functions=["my_unused_hook"], output_path="/tmp/hook.c")
compile_preload_library(source_path="/tmp/hook.c", output_path="/tmp/libhook.so")
run_with_preload(command="/bin/true", preload_libs=["/tmp/libhook.so"])
# → stderr: "[preload] loaded into pid=<n>"

Diagnose why a binary isn't finding a library:

readelf_dynamic(path="/path/to/app")       # inspect RPATH / RUNPATH / NEEDED
resolve_library(libname="libfoo.so.1")     # where would the loader find it?
trace_library_loads(command="/path/to/app", categories="libs")

Audit a running process for unexpected interposition:

inspect_process(pid=1234)                   # any LD_PRELOAD / LD_AUDIT set?
list_loaded_libraries(pid=1234)             # anything outside /usr and /lib?
get_ld_so_preload()                         # system-wide preload present?

Safety

  • run_with_preload validates every preload_libs and ld_audit path exists and is a regular file before calling execve.
  • ldd_binary defaults to readelf, which does not execute the target. Running the binary requires an explicit use_unsafe=true.
  • inspect_process returns only dynamic-linker env vars, not the full environ, to avoid leaking secrets from other processes.
  • set_ld_so_preload refuses unless confirm=true is passed and the server runs as root. A bad entry in /etc/ld.so.preload can make the system unbootable — treat writes as a privileged administrative action.
  • LD_PRELOAD and LD_AUDIT are ignored by the loader for setuid binaries regardless of what this server sets.

Layout

pyproject.toml
src/ld_preload_mcp/
  __init__.py
  server.py          # all 14 tools, FastMCP-based

License

No explicit license yet — add one before external distribution.

About

An MCP (Model Context Protocol) server that exposes Linux dynamic-linker controls — LD_PRELOAD, LD_LIBRARY_PATH, LD_AUDIT, LD_DEBUG, /etc/ld.so.preload, ldconfig, ldd, readelf, and nm — as callable tools, plus helpers to generate and compile preload shared objects. Intended for debugging linker issues, library-interposition development.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Contributors

Languages