Skip to content

CyberSnakeH/Specter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Specter

A ptrace-based shared object (.so) injector for Linux x86-64 — the Linux analogue of Windows DLL injection. It forces a running target process to dlopen() an arbitrary library, which is the foundation for things like injecting an ImGui overlay into another process.

specter <PID> <LIBRARY.so> [--global] [--timeout SECONDS] [--retries N] [-v|-vv|-vvv]

How it works

  1. Resolve the loader. dlopen (or __libc_dlopen_mode) is resolved inside the target by reading /proc/<pid>/maps, locating the exact libc image the target uses, and parsing that ELF's dynamic symbol table. We read the target's own libc, so libc version/ABI differences never matter.
  2. Freeze the whole thread group, at a safe point. Every thread of the target is stopped with PTRACE_SEIZE + PTRACE_INTERRUPT (re-scanning /proc/<pid>/task until no new thread appears), so no sibling thread runs while we operate. We only freeze when no thread is executing inside the dynamic loader — otherwise a thread might hold the loader lock our forged dlopen needs, so we release and retry until we land a clean instant. The thread-group leader's registers are snapshotted, and a RAII guard guarantees they are restored and every thread is detached even if a later step fails.
  3. Stage the path. The .so path is written onto the target's stack, safely below the ABI red zone.
  4. Call, with a watchdog. A System V AMD64 call to dlopen(path, flags) is forged by setting up registers and a NULL return address, so the call returns into an unmapped page and traps deterministically; RAX then holds the handle. The call is bounded by --timeout: if it overruns (a blocking payload constructor, or loader-lock contention), the target is interrupted and rewound rather than left frozen.
  5. Restore & detach. The leader's original registers are restored and every thread is detached, resuming the whole process cleanly.

If a dlopen times out before it could map the library, that indicates loader-lock contention with a target thread rather than a payload problem, so the whole injection is retried up to --retries times (releasing the target between tries lets the lock holder finish). A timeout after the library is mapped means the payload's constructor is blocking, which is reported immediately.

The crate is #![forbid(unsafe_code)]: every privileged operation goes through a safe wrapper (nix for ptrace, std file I/O for /proc/<pid>/mem, object for ELF parsing).

Build

cargo build --release
# binary at ./target/release/specter

Usage

ptrace is restricted by the Yama LSM. To attach to a process that is not a child of the injector, either run as root or relax the policy:

echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope

Then:

# Find a target
pidof firefox

# Inject
./target/release/specter 12345 /absolute/path/to/payload.so -v

Options

Flag Default Meaning
--global off dlopen with RTLD_GLOBAL (payload symbols globally visible).
--timeout SECONDS 10 Watchdog per remote call. 0 waits forever. On overrun the target is rewound, never frozen.
--retries N 3 Retries on loader-lock contention (a dlopen that stalls before mapping the library). A blocking constructor is not retried.
-v/-vv/-vvv warn Log level (info / debug / trace); logs go to stderr.

Try it end-to-end

A demo payload and several victim processes are provided under examples/:

# 1. Build the demo payload (writes a marker file from inside the target)
cc -shared -fPIC -o examples/payload.so examples/payload.c

# 2. Build and start a victim, note its PID
cc -O2 -o examples/victim examples/victim.c
./examples/victim &
VICTIM=$!

# 3. Inject
cargo run --release -- "$VICTIM" "$PWD/examples/payload.so" -vv

# 4. Observe the side effect produced from inside the victim
cat /tmp/specter_demo.txt   # -> "injected into pid <VICTIM>"

Other victims demonstrate the hardening: victim_mt.c (multi-threaded freeze), victim_dlsym.c (loader-lock contention → retries), and payload_blocking.c (a misbehaved constructor that trips the --timeout watchdog without freezing the target).

Writing a payload

dlopen runs the library's constructors synchronously, so put your entry logic in a constructor and return quickly — the injector's remote call does not complete until the constructor returns. For anything long-lived (a render hook, an overlay event loop), spawn a thread from the constructor:

#include <pthread.h>

static void *worker(void *arg) { /* hook the render loop, draw ImGui... */ return NULL; }

__attribute__((constructor))
static void on_load(void) {
    pthread_t t;
    pthread_create(&t, NULL, worker, NULL);   // return immediately
}

Scope & limitations

  • x86-64 only. The remote-call ABI is System V AMD64. aarch64 would need its own register/ABI handling.
  • glibc-oriented. Symbol resolution targets glibc/libdl naming. musl works if it exports dlopen in a mapped libc (it does), but is less tested.
  • Loader-lock contention is mitigated, not eliminated. The safe-injection point check avoids freezing while a thread's instruction pointer is inside the dynamic loader, but a thread can hold the loader lock while executing in libc (e.g. the lock's own mutex code). That residual case is handled reactively: the dlopen times out, the target is released and the injection retried. The malloc-arena lock is a similar rarer hazard, also caught by the watchdog.
  • A timed-out call leaves partial side effects. Rewinding restores the hijacked thread's registers, but memory already written by an aborted dlopen (e.g. a partially-mapped library) is not unwound. The target keeps running; the injection simply fails.

License

MIT OR Apache-2.0

About

Inject a shared object into any running Linux process. The Linux equivalent of DLL injection, in 100% safe Rust: ptrace + dlopen, zero unsafe code.

Topics

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages