analyzing-linux-persistence について
Linux ホストの永続化を調べるインシデント対応担当者やフォレンジック調査者向けの分析スキルです。systemd、スケジュールタスク、カーネル、コンテナなどの起動経路を体系的に確認します。
主な機能
- 01systemd ユニットの体系的な確認
- 02cron と at ジョブの調査
- 03シェル、SSH、自動起動項目の確認
- 04カーネルモジュールとパッケージフックの調査
- 05ライブラリ注入、udev、D-Bus の確認
- 06コンテナの永続化とフォレンジック痕跡の調査
活用シーン
- 01Linux へのアクセス維持方法の調査
- 02稼働中システムのバックドアや未承認サービスの確認
- 03駆除後に永続化機構が残っていないか検証
- 04ログとファイルシステムのタイムラインによる変更範囲の把握
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ trilwu/ secskills --skill analyzing-linux-persistenceインストール後、Claude Code で/analyzing-linux-persistenceを入力して読み込まれたか確認します。
詳細
Linux の永続化メカニズムの分析
Linux の永続化メカニズムは数十か所に分散しており、すべてを網羅的に確認できる単一のツールはありません。攻撃者は、見落としが一つあれば十分です。カーネルとユーザー空間が利用するすべての初期化経路を、一般的なものから珍しいものへ順に体系的に調査します。
使用する場面
- Linux ホストが侵害された際に、攻撃者がアクセスを維持した方法を特定する
- Linux のディスクイメージまたは稼働中システムのフォレンジック分析
- バックドア、不正なサービス、不正なスケジュールタスクを探す
- インシデント対応中に、攻撃者による変更の全容を特定する
- 駆除によってすべての永続化メカニズムが削除されたことを検証する
使用しない場面
- インシデント対応全体を実施する場合 --
responding-to-incidentsを使用してください - 攻撃目的で永続化を確立する場合 --
establishing-persistenceを使用してください - 実際に権限昇格を行う場合 --
escalating-linux-privilegesを使用してください - ホストが Windows の場合 --
investigating-windows-endpointsを使用してください - 稼働中のホストではなく、取得済みのディスクイメージがある場合 --
analyzing-disk-imagesを使用してください - 復元したマルウェアサンプルを分析する場合 --
analyzing-malwareを使用してください
Systemd ユニット
Systemd は、現代の Linux で最も一般的な永続化経路です。/etc/systemd/system だけでなく、すべての検索パスを確認してください。
検索パス(優先順位順に評価):
/etc/systemd/system/ # admin-installed, highest priority
/run/systemd/system/ # runtime units, survive until reboot
/usr/lib/systemd/system/ # package-installed defaults
/usr/local/lib/systemd/system/
~/.config/systemd/user/ # per-user units (no root needed)
列挙:
# All enabled units -- compare against a known-good baseline
systemctl list-unit-files --state=enabled --type=service
systemctl list-unit-files --state=enabled --type=timer
# Recently modified service files
find /etc/systemd /run/systemd /usr/lib/systemd ~/.config/systemd \
-name '*.service' -o -name '*.timer' -o -name '*.socket' \
2>/dev/null | xargs ls -lt | head -30
# Show the full contents of a suspect unit
systemctl cat suspicious.service
# Check for drop-in overrides that modify legitimate units
find /etc/systemd/system/*.d /run/systemd/system/*.d \
-name '*.conf' 2>/dev/null
# Masked units -- attacker may mask a security service
systemctl list-unit-files --state=masked
# Systemd generators -- scripts that dynamically create units at boot.
# Four directories, in ascending precedence. Checking only the first two
# misses generators dropped in the others.
ls -la /etc/systemd/system-generators/
ls -la /run/systemd/system-generators/ # tmpfs: runtime-injected
ls -la /usr/local/lib/systemd/system-generators/
ls -la /usr/lib/systemd/system-generators/
/run 配下のジェネレーターは再起動後に残らないため、厳密な意味では永続化ではありません。ただし、稼働中のシステムでコードを実行する手段であり、再起動後のイメージに存在しないからといって、以前も存在しなかったとは限りません。収集を始める前に、稼働中のホストで見つけた内容を記録してください。
確認するポイント: ExecStart が /tmp、/dev/shm、または書き込み可能なパスを指していないか。Restart=always を設定したユニット。不自然なスケジュールのタイマー。正規のサービスの ExecStart を変更するドロップイン。どのパッケージにも属さないジェネレーター。
Cron と At ジョブ
# System crontab
cat /etc/crontab
# System cron directories
ls -la /etc/cron.d/
ls -la /etc/cron.daily/ /etc/cron.hourly/ /etc/cron.weekly/ /etc/cron.monthly/
# Per-user crontabs -- enumerate every user
for user in $(cut -d: -f1 /etc/passwd); do
echo "=== $user ==="; crontab -l -u "$user" 2>/dev/null
done
# Anacron
cat /etc/anacrontab
# At queue -- one-shot scheduled commands
atq
for job in $(atq | awk '{print $1}'); do at -c "$job"; done
# Systemd timers (modern cron replacement)
systemctl list-timers --all
# Recently modified cron files
find /etc/cron* /var/spool/cron -type f -newer /etc/hostname 2>/dev/null
確認するポイント: 既存ファイルに追記されたエントリー。不自然なパスから root として実行されるジョブ。変更時刻がパッケージのインストール日と一致しない Cron スクリプト。ダウンロードして実行する単発の at ジョブ。
シェル初期化ファイル
# System-wide
cat /etc/profile
ls -la /etc/profile.d/
cat /etc/bash.bashrc # Debian/Ubuntu
cat /etc/bashrc # RHEL/CentOS
cat /etc/environment
ls -la /etc/environment.d/
# Per-user -- check every user with a login shell
for home in $(awk -F: '$7 !~ /(nologin|false)/ {print $6}' /etc/passwd); do
echo "=== $home ==="
for f in .bashrc .bash_profile .bash_login .profile .zshrc .zprofile \
.zlogin .zshenv .config/fish/config.fish; do
[ -f "$home/$f" ] && echo "--- $f ---" && tail -5 "$home/$f"
done
done
# Check for sourced external files
grep -rn 'source \|^\. ' /etc/profile.d/ /root/.bashrc /root/.profile 2>/dev/null
確認するポイント: バックグラウンドプロセスを起動したり、PATH を変更したりするコマンドが rc ファイルに追記されていないか。不自然な場所にある読み込み対象ファイル。LD_PRELOAD、LD_LIBRARY_PATH を設定する環境変数、または攻撃者のディレクトリを PATH の先頭に追加する環境変数。
SSH による永続化
# Authorized keys -- check every user
find / -name authorized_keys -o -name authorized_keys2 2>/dev/null | \
while read f; do echo "=== $f ==="; cat "$f"; done
# Look for command= and from= restrictions (or lack thereof)
grep -n 'command=\|from=\|environment=\|no-pty\|permitopen' \
/root/.ssh/authorized_keys /home/*/.ssh/authorized_keys 2>/dev/null
# SSH daemon configuration
sshd -T 2>/dev/null | grep -iE 'permit|auth|allow|forcecommand|match'
diff /etc/ssh/sshd_config /etc/ssh/sshd_config.dpkg-dist 2>/dev/null
# Config drop-ins
ls -la /etc/ssh/sshd_config.d/ 2>/dev/null
# PAM configuration for SSH
cat /etc/pam.d/sshd
# Look for added auth sufficient lines or custom PAM modules
# PAM module locations are distro-specific. Debian/Ubuntu use a multiarch
# path, so the RHEL-style list alone returns nothing there -- and an empty
# result reads as "no rogue module" when you simply looked in the wrong place.
find /lib/security/ /lib64/security/ /usr/lib/security/ /usr/lib64/security/ \
/lib/*-linux-gnu/security/ /usr/lib/*-linux-gnu/security/ \
-name 'pam_*.so' 2>/dev/null | \
xargs -I{} sh -c 'rpm -qf {} 2>/dev/null || dpkg -S {} 2>/dev/null || echo "UNOWNED: {}"'
# SSH agent hijacking -- check for forwarded agent sockets
find /tmp -name 'agent.*' -type s 2>/dev/null
grep -r SSH_AUTH_SOCK /proc/*/environ 2>/dev/null | tr '\0' '\n'
# SSH certificates
ls -la /etc/ssh/ssh_host_*cert* 2>/dev/null
cat /etc/ssh/sshd_config | grep -i trustedusercakeys
確認するポイント: root またはサービスアカウントに追加された鍵。リバースシェルやトンネルを実行する command= エントリー。ポリシーに反する PermitRootLogin yes または PasswordAuthentication yes。どのパッケージにも属さない独自の PAM モジュール。アクセスをプロキシする ForceCommand または Match ブロック。
カーネルレベルの永続化
# Loaded kernel modules
lsmod
# Details on each module -- look for unsigned or out-of-tree modules
for mod in $(lsmod | awk 'NR>1{print $1}'); do
modinfo "$mod" 2>/dev/null | grep -E 'filename|description|author|sig'
done
# Modules configured to load at boot
cat /etc/modules
ls -la /etc/modules-load.d/
cat /etc/modprobe.d/*
# DKMS-built modules (may rebuild on kernel update)
dkms status 2>/dev/null
# eBPF programs -- increasingly used for rootkits
bpftool prog list 2>/dev/null
bpftool map list 2>/dev/null
# Check for modifications to the kernel command line
cat /proc/cmdline
確認するポイント: インストール済みパッケージに属さないモジュール、または書き込み可能なディレクトリから読み込まれたモジュール。kprobe またはトレースポイントにアタッチされた eBPF プログラム。カーネルをアップグレードしても残る DKMS モジュール。カーネルコマンドラインの init= が想定外のバイナリを指していないか。
パッケージマネージャーのフック
# APT hooks (Debian/Ubuntu)
ls -la /etc/apt/apt.conf.d/
grep -r 'Pre-Invoke\|Post-Invoke\|DPkg::Pre-Install\|DPkg::Post-Install' \
/etc/apt/apt.conf.d/ 2>/dev/null
# dpkg triggers and post-install scripts
ls -la /var/lib/dpkg/info/*.postinst
# Check recently modified postinst scripts
find /var/lib/dpkg/info/ -name '*.postinst' -newer /var/log/dpkg.log \
2>/dev/null
# YUM/DNF plugins (RHEL/CentOS/Fedora)
ls -la /etc/yum/pluginconf.d/ /etc/dnf/plugins/ 2>/dev/null
ls -la /usr/lib/yum-plugins/ /usr/lib/python*/site-packages/dnf-plugins/ \
2>/dev/null
# RPM scriptlets
rpm -qa --scripts 2>/dev/null | grep -B2 -A5 'postinstall\|preinstall'
# Pacman hooks (Arch)
ls -la /etc/pacman.d/hooks/ /usr/share/libalpm/hooks/ 2>/dev/null
確認するポイント: パッケージマネージャーの管理外にあるバイナリを実行するフック。インストール後に変更されたインストール後スクリプト。インストール済みパッケージに対応しないプラグイン。
LD_PRELOAD とライブラリのインジェクション
cat /etc/ld.so.preload
grep -r LD_PRELOAD /etc/environment /etc/profile.d/ /etc/ld.so.conf.d/ \
/etc/systemd/system/*.service 2>/dev/null
# Find libraries not owned by any package
ldconfig -p | awk '{print $NF}' | sort -u | \
xargs -I{} sh -c 'dpkg -S "{}" 2>/dev/null || rpm -qf "{}" 2>/dev/null || echo "UNOWNED: {}"' | \
grep UNOWNED
# Verify shared library integrity against package records
for lib in $(ldd /usr/sbin/sshd | awk '/=>/{print $3}'); do
dpkg -V $(dpkg -S "$lib" 2>/dev/null | cut -d: -f1) 2>/dev/null || \
rpm -V $(rpm -qf "$lib" 2>/dev/null) 2>/dev/null
done
確認するポイント: /etc/ld.so.preload にエントリーがないか(正規の用途で使われることはほとんどありません)。環境ファイルまたは systemd の Environment= ディレクティブに LD_PRELOAD がないか。どのパッケージにも属さないライブラリ、またはパッケージマネージャーの記録とチェックサムが異なるライブラリ。
Udev ルールと D-Bus のアクティベーション
# Udev rules -- trigger on hardware events
ls -la /etc/udev/rules.d/
ls -la /usr/lib/udev/rules.d/
grep -r 'RUN+=' /etc/udev/rules.d/ /run/udev/rules.d/ /usr/lib/udev/rules.d/ 2>/dev/null
# D-Bus system services
ls -la /etc/dbus-1/system.d/ /usr/share/dbus-1/system-services/ 2>/dev/null
# Look for services that auto-activate unexpected binaries
grep -r 'Exec=' /usr/share/dbus-1/system-services/ 2>/dev/null
確認するポイント: デバイスイベント(USB の挿入が一般的なトリガーです)に応じてスクリプトを実行する RUN+= を含む Udev ルール。不自然なパスにあるバイナリをアクティベートする D-Bus サービスファイル。
コンテナ固有の永続化
# Docker -- compare entrypoints against original images
docker inspect --format='{{.Config.Entrypoint}} {{.Config.Cmd}}' \
$(docker ps -aq) 2>/dev/null
docker diff $(docker ps -q) 2>/dev/null
docker images --digests 2>/dev/null
cat /etc/docker/daemon.json 2>/dev/null
# Kubernetes -- mutating webhooks and unexpected sidecars
kubectl get mutatingwebhookconfigurations -o yaml 2>/dev/null
kubectl get pods --all-namespaces -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}: {range .spec.containers[*]}{.name} {end}{"\n"}{end}' 2>/dev/null
確認する内容: エントリーポイントが元のイメージと異なるコンテナ。docker diff に実行ファイルの追加が表示されているもの。サイドカーを注入するミューテーティングWebhook。レジストリのダイジェストがないイメージ(ローカルでビルドされたもの、または改ざんされたもの)。
XDG 自動起動とデスクトップエントリー
# System-wide autostart
ls -la /etc/xdg/autostart/
# Per-user autostart
find /home/*/.config/autostart/ /root/.config/autostart/ \
-name '*.desktop' 2>/dev/null
# Check Exec= lines in desktop files
grep -r 'Exec=' /etc/xdg/autostart/ /home/*/.config/autostart/ 2>/dev/null
確認する内容: 書き込み可能な場所にあるスクリプトを実行するデスクトップエントリー。デスクトップセッションを持つべきではないサービスアカウントに追加された自動起動エントリー。
包括的な調査とフォレンジックアーティファクト
# Timestamp-sorted inventory across all persistence paths
{
find /etc/systemd/system /run/systemd/system -type f 2>/dev/null
find /etc/cron* /var/spool/cron -type f 2>/dev/null
find /etc/profile.d/ -type f 2>/dev/null
find / -name authorized_keys 2>/dev/null
echo /etc/ld.so.preload
find /etc/udev/rules.d/ /etc/xdg/autostart/ /etc/apt/apt.conf.d/ \
-type f 2>/dev/null
} | xargs ls -lt --time-style=full-iso 2>/dev/null | head -60
# Files modified in the last 30 days across persistence paths
find /etc/systemd /etc/cron* /var/spool/cron /etc/profile.d \
/etc/ssh /etc/pam.d /etc/modules-load.d /etc/udev/rules.d \
/etc/xdg/autostart -type f -mtime -30 2>/dev/null | sort
# Files not owned by any package
# Debian/Ubuntu:
find /etc/systemd/system /etc/cron.d /etc/profile.d \
-type f 2>/dev/null | xargs dpkg -S 2>&1 | grep 'not found'
# RHEL/CentOS:
find /etc/systemd/system /etc/cron.d /etc/profile.d \
-type f 2>/dev/null | xargs rpm -qf 2>&1 | grep 'not owned'
# Process tree and network listeners
ps auxwwf
ss -tlnp
タイムラインの照合に備えて、次のログを収集します:/var/log/auth.log(または
/var/log/secure)、/var/log/syslog(または journalctl のエクスポート)、
/var/log/apt/history.log(または /var/log/yum.log)、/var/log/cron、
/etc/passwd、/etc/shadow、/etc/group。fls または MAC タイムを取得できる
find -printf を使ってファイルシステムのタイムラインを作成します。
退けるべき合理化
- 「cron と systemd を確認したので、これで十分です。」 それらは、十数種類以上ある永続化経路のうちの 2 つにすぎません。上記の各セクションを確認するまでは、調査は完了していません。
- 「ファイルのタイムスタンプが古いので、正規のものです。」 タイムスタンプは
touchで簡単に変更できます。mtimeだけでなく、パッケージマネージャーの記録やログエントリーと照合してください。 - 「攻撃者が持っていたのはユーザーレベルのアクセス権だけなので、ユーザーが書き込み可能な場所だけ確認すればよいです。」 ユーザーレベルの永続化には、systemd のユーザーユニット、crontab、rc ファイル、authorized_keys、XDG 自動起動、環境ファイル経由の LD_PRELOAD が含まれます。
- 「AV スキャンを実行しましたが、何も検出されませんでした。」 AV が検出するのは既知のマルウェアであり、正規のシステム機能を利用した永続化の仕組みではありません。
curl | bashを実行する cron ジョブは、シグネチャに検知されません。 - 「システムを再構築したので、永続化は問題ありません。」 どのように永続化されたかが分からなければ、再構築によって経路を塞げたとは確認できません。
- 「EDR が監視している場所だけ確認すればよいです。」 EDR のカバレッジは、製品や構成によって異なります。この調査は特定のツールに依存しません。
参考資料
responding-to-incidents-- この分析の基盤となる、より広範なインシデント対応手法establishing-persistence-- 同じ仕組みを攻撃側の視点から解説escalating-linux-privileges-- 永続化と組み合わせて使われることの多い、権限昇格の手法engineering-detections-- 見つかった仕組みに対する検知ルールの構築hunting-threats-- フリート全体を対象とした、永続化のプロアクティブな探索