How witr Detects LD_PRELOAD and DYLD_* Library Injection in Linux and macOS Processes
witr scans process environment variables against hardcoded rules to flag LD_PRELOAD and any DYLD_* variables as potential library injection attacks.
The witr CLI security tool monitors running processes for suspicious environment configurations that attackers commonly exploit to preload malicious shared libraries. According to the pranshuparmar/witr source code, the detection engine inspects every process in the ancestry chain using pattern-matching rules defined in the core detection module.
Detection Rules for Environment Variable Injection
The detection logic centers on a slice of envSuspiciousRule structs declared in internal/source/detect.go. Two specific rules target library injection vectors across Unix-like systems:
LD_PRELOAD Detection on Linux
The first rule matches the exact environment variable name LD_PRELOAD:
// internal/source/detect.go (lines 40-44)
{
name: "LD_PRELOAD",
pattern: regexp.MustCompile(`^LD_PRELOAD$`),
warning: "Process sets LD_PRELOAD (potential library injection)",
// includeKeys: false (default)
}
When this rule fires, witr emits a fixed warning string without listing additional variable names.
DYLD_* Detection on macOS
The second rule catches any environment variable starting with DYLD_ using a prefix pattern:
// internal/source/detect.go (lines 45-50)
{
name: "DYLD_*",
pattern: regexp.MustCompile(`^DYLD_.*`),
warning: "Process sets DYLD_* variables (potential library injection)",
includeKeys: true,
}
The includeKeys: true flag causes the detector to collect and display which specific DYLD_* variables were found—critical for macOS where multiple dynamic linker variables exist (DYLD_INSERT_LIBRARIES, DYLD_LIBRARY_PATH, DYLD_FRAMEWORK_PATH, etc.).
Scanning Implementation: envSuspiciousWarnings
The function envSuspiciousWarnings(env []string) []string executes the actual scanning logic. Located in internal/source/detect.go (lines 93-108), this function iterates over all rules and environment entries:
func envSuspiciousWarnings(env []string) []string {
var warnings []string
for _, rule := range envSuspiciousRules {
var matches []string
for _, e := range env {
// Parse KEY=VALUE format
parts := strings.SplitN(e, "=", 2)
key := parts[0]
if rule.pattern.MatchString(key) {
matches = append(matches, key)
}
}
if len(matches) > 0 {
warning := rule.warning
if rule.includeKeys {
warning += ": " + strings.Join(matches, ", ")
}
warnings = append(warnings, warning)
}
}
return warnings
}
Key behaviors of this implementation:
- Pattern matching uses compiled regular expressions for flexibility
- Key collection only occurs when
includeKeysis enabled (DYLD rule) - Duplicate elimination happens naturally via iteration order
- Empty value handling processes variables regardless of assigned value
Integration with Process Detection
The Detect function orchestrates full process analysis in internal/source/detect.go. It invokes multiple detection subsystems including detectShell, detectContainer, and the environment scanner. When envSuspiciousWarnings returns non-empty results, those warnings populate the model.Source description structure that drives console output.
Typical runtime output appears as:
[WARN] Process sets LD_PRELOAD (potential library injection)
[WARN] Process sets DYLD_* variables (potential library injection): DYLD_INSERT_LIBRARIES, DYLD_LIBRARY_PATH
Practical Verification with Test Cases
The internal/source/detect_test.go file validates both rules against known-good and known-bad inputs. You can verify detection behavior programmatically:
package main
import (
"fmt"
"github.com/pranshuparmar/witr/internal/source"
)
func main() {
// Simulate attacker-controlled environment
maliciousEnv := []string{
"PATH=/usr/bin:/bin",
"LD_PRELOAD=/tmp/libc_hook.so",
"DYLD_INSERT_LIBRARIES=/tmp/dylib_runner.dylib",
"DYLD_LIBRARY_PATH=/tmp/fake_libs",
"HOME=/root",
}
warnings := source.EnvSuspiciousWarnings(maliciousEnv)
for _, w := range warnings {
fmt.Println("[WARN]", w)
}
// Output:
// [WARN] Process sets LD_PRELOAD (potential library injection)
// [WARN] Process sets DYLD_* variables (potential library injection): DYLD_INSERT_LIBRARIES, DYLD_LIBRARY_PATH
}
Security Relevance: Why These Variables Matter
Library injection via environment variables remains a persistent attack vector:
LD_PRELOADforces the Linux dynamic linker to load specified shared libraries before all others, enabling function hooking and privilege escalationDYLD_INSERT_LIBRARIESprovides equivalent functionality on macOS, loading additional libraries into the target processDYLD_LIBRARY_PATHredirects library resolution to attacker-controlled directories
witr flags these conditions regardless of whether the values appear malicious, adopting a detect-then-investigate posture suitable for security monitoring pipelines.
Summary
internal/source/detect.godefines injection detection rules in theenvSuspiciousRulessliceLD_PRELOADtriggers exact-match detection with a fixed warning messageDYLD_*triggers prefix-match detection with dynamic key enumeration viaincludeKeysenvSuspiciousWarnings()implements the core scanning algorithm iterating over rules and environment variables- Detection coverage includes Linux and macOS library injection vectors through unified Go implementation
- Integration point is the
Detectfunction which surfaces warnings throughmodel.Sourcedescriptions
Frequently Asked Questions
How does witr distinguish between legitimate and malicious LD_PRELOAD usage?
witr does not attempt semantic analysis of library paths—it flags all LD_PRELOAD presence as suspicious. This design reflects the tool's purpose as a security auditor rather than a policy enforcement engine. Operators must investigate flagged processes manually to determine if preloaded libraries are authorized system components or attack artifacts.
Does witr detection work inside containerized environments?
Yes. The detectContainer function in internal/source/detect.go runs alongside environment scanning. witr reads procfs for container metadata and applies the same envSuspiciousWarnings logic to processes regardless of namespace isolation. Container detection and library injection detection operate as independent checks that both contribute to process risk scoring.
What macOS versions does the DYLD_* detection cover?
The prefix pattern ^DYLD_.* matches all DYLD-prefixed variables without version-specific logic. This approach remains compatible across macOS releases because the dynamic linker environment variable namespace has remained stable. Apple's System Integrity Protection (SIP) restricts DYLD manipulation for system processes, but witr still detects attempts in user-space processes and post-exploitation scenarios where SIP is disabled.
Can the detection rules be customized or extended?
The current implementation in pranshuparmar/witr uses a hardcoded envSuspiciousRules slice. Adding custom patterns requires modifying internal/source/detect.go and recompiling. The struct-based definition (envSuspiciousRule with name, pattern, warning, and includeKeys fields) provides a clear extension point for contributors seeking to add detection for additional environment-based attack vectors.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →