ZeroShadow is a low-level Linux user-space supervisor enforcing coarse-grained Control-Flow Integrity (CFI). It uses ptrace to single-step target threads, validating the instruction pointer (RIP) against known-executable memory boundaries dynamically extracted from /proc/[pid]/maps. Any execution jump to an unauthorized region (stack, heap, unmapped) results in immediate termination.
- Execution Tracking: A unified
waitpidloop single-steps all active threads (PTRACE_SINGLESTEP). Every instruction boundary is verified. - Dynamic Maps: Parses
/proc/[pid]/mapson attach. Interceptsmmap/mprotectsyscalls viaPTRACE_O_TRACESYSGOODto trackPROT_EXECstate changes live. - Thread Lineage: Enforces
PTRACE_O_TRACECLONE | FORK | VFORK. Newly spawned threads are automatically trapped and added to the supervision loop before user-space code executes.
Exploits often attempt to blind debuggers by blocking SIGTRAP or SIGSEGV via rt_sigprocmask. ZeroShadow intercepts this syscall and closes the Time-of-Check to Time-of-Use (TOCTOU) window:
- Traps
rt_sigprocmaskon entry. - Uses atomic
process_vm_readvto pull the tracee's pending signal mask directly from memory. - Strips the
SIGTRAPandSIGSEGVbits locally. - Pushes the sanitized mask back to the tracee via
process_vm_writev. - Resumes the syscall. The kernel consumes the clean mask. No race conditions, no blind spots.
Hot-attach to an already running production process. Extracts current memory boundaries and begins single-stepping instantly.
# Attach to active PID
./ZeroShadow <target_pid> <binary_path>Supervised spawn. ZeroShadow forks, the child calls PTRACE_TRACEME and executes the target binary. Catches the process at the very first instruction before any user-space code runs.
# Launch and trace
./ZeroShadow /path/to/binaryRequires a C++17 compiler and a Linux kernel (4.11+) supporting process_vm_writev.
makeZeroShadow operates as a low-level instrumentation engine. To maintain a verifiable ring of trust, the system boundaries and privileges are strictly mapped out below.
CAP_SYS_PTRACE: Required if tracing external, already-running processes. ZeroShadow uses standard Linux process tracing mechanisms to read memory maps (/proc/[pid]/maps) and hook execution vectors.- Root Privileges:
sudois only required if the target binary being analyzed requires root privileges itself, or when attaching to a process owned by another user.
- No Network I/O: The core engine (
libzeroshadow.so) contains zero networking libraries. It cannot make external calls; phoning home is structurally impossible. - No Arbitrary Persistence: ZeroShadow does not write to system directories, modify initialization scripts, or create background daemons.
The codebase is decoupled into isolated, single-responsibility modules to ensure transparent code audits:
src/elf_parser.cpp: Purely reads and maps ELF structures into memory. It possesses no execution or tracing logic.src/tracer.cpp: Handles the low-level breakpoint placement and registers tracking. It relies entirely on the parsed data from the parser.src/main.cpp: The CLI interface wrapper that drives the underlying modules.
.
├── include
│ ├── elf_parser.hpp # PT_LOAD execution extraction
│ └── tracer.hpp # supervisor architecture and state management
├── Makefile
├── README.md
└── src
├── elf_parser.cpp
├── main.cpp # entry point and argument routing
└── tracer.cpp # ptrace loop, syscall interception, and CFI enforcement