私はそのプロセスがSIGKILLをブロックできないことを知っています。
しかし、SIGKILLが(特定の)プロセスに到達するのを一時的に防ぐ外部方法はありますか? (例:ファイアウォールを介したパケットの削除)
答え1
kill()
システムコールを呼び出す(または)プロセスを終了することができますtkill()
(カーネルは独自にプロセス/タスクを終了することもできます(たとえば、Ctrl-Cを押して送信されたSIGINTまたはOut of Memory Killerから送信されたSIGKILL)。シグナルは次のようになります。他のシステムコール(たとえばptrace
)によって送信されました。
呼び出されると、kill()
すべてがカーネルで発生します。
カーネルコードのみが、シグナルを送信したプロセスとシグナルを受け取ったプロセスの間にあります(結果的に終了する可能性があります)。
現在ブロックされているいくつかのカーネル機能はまだ残っており、ここで利用可能です。
単純 Unix 権限。
kill(2)
Linuxのマニュアルページを引用すると、次のようになります。プロセスがシグナルを送信する権限を持つには、許可が必要です(Linuxでは、ターゲットプロセスのユーザーネームスペースにCAP_KILL機能が必要です)。または、転送プロセスの実ユーザーまたは実効ユーザーIDは、実ユーザーまたは実効ユーザーIDと同じでなければなりません。ターゲットプロセスのユーザーIDユーザーIDを設定します。
Linuxセキュリティモジュール。 LSMは(少なくともSmack、SELinux、および衣服の場合)シグナルを受け取ることができるアイテムをフィルタリングできます。
一部のプロセスは殺人に対する免疫。
init
これは、LinuxでIDが1()のプロセスの場合です。他のサブネームスペースのルートプロセスも、そのネームスペースの他のプロセスから送信された信号の影響を受けません。カーネル操作も信号の影響を受けません。そしてそこにカーネル検出機構SystemTapで使用されているのと同様に、カーネルの動作に影響を与える可能性があり、シグナリングを傍受するために使用できます。
しかし、そこに到達する前に最初に試す必要があるのは、SIGKILL信号を送信するすべての操作をブロックすることです。
シャットダウン時に重要なプロセス(ルートファイルシステムをサポートするために使用されるプロセス)がシャットダウンされるのを防ぐことが目的である場合、ほとんどのinitシステムには、そのプロセスがその時点で発生することkillall5
の影響を受けないようにする方法があります。等しい。/run/sendsigs.omit.d
一部のDebianバージョンを参照してください。killmode
例えば。systemd
キラープロセスは、それが何であれ、どのプロセスを殺すかを決定する方法を持っている必要があります。ファイルに保存されている被害者のpidに基づいている場合(たとえば)、そのファイルを変更することができ/run/victim.pid
、プロセス名()に基づいている場合は引数を変更することもできます/proc/pid/task/tid/comm
(たとえば、デバッガを接続して呼び出すなど)。prctl(PR_SET_NAME)
リスト(/proc/pid/cmdline
表示ps -f
)。
キラープロセスが動的にリンクされている場合は、kill()
システムコールを指定されたpidに対してこの操作を拒否するラッパー関数に置き換えるコードを挿入できます。
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <errno.h>
int kill(pid_t pid, int sig)
{
static pid_t pid_to_safeguard = 0;
static int (*orig_kill)(pid_t, int) = 0;
if (!orig_kill)
orig_kill = (int (*)(pid_t, int)) dlsym (RTLD_NEXT, "kill");
if (!orig_kill) abort();
if (!pid_to_safeguard) {
char *env = getenv("NOTME");
if (env) pid_to_safeguard = atol(env);
}
if (pid_to_safeguard && pid == pid_to_safeguard) {
errno = EPERM;
return -1;
}
return orig_kill(pid, sig);
}
tkill()
(殺人者が実際にシグナルを送信する方法によっては、被害者のpgidに対して同じことを行う必要があるかもしれません。)
次にコンパイル:
gcc -fPIC -shared -o notme.so notme.c -ldl
キラーコマンドを実行します。
LD_PRELOAD=/path/to/notme.so NOTME=12345 killer args...
または、プロセスを完全に非表示にすることもできますシステムの残りの部分とは異なるpid名前空間で実行します(ただしその子ではありません)。。
これらのいずれもオプションでない場合は、上記のリストを介して信号が転送されるのを防ぎます。
- 被害者を殺人者とは別のユーザーとして実行します(殺人者として実行されていないと仮定
root
)。 LSMを使用してください。たとえば、Smack(
security=smack
カーネルパラメータで始まる)を使用しているときにlabel
被害者プロセスに異なる値を設定すると、他のプロセスがそれを見るのはもちろん、終了するのを防ぐことができます。たとえば、sudo zsh -c 'echo unkillable > /proc/self/attr/current && exec sleep 1000'
sleep
このドメインで実行されますunkillable
(名前は何でも可能です。重要なのは、現在このドメインへの干渉を許可するルールが定義されていないことです)。同じuidで実行されているプロセスでもドメインを終了できません。root
それでも。新しいpid名前空間のリーダーで被害者プロセスを開始すると、そのプロセスはその子プロセスから保護されます。
~$ sudo unshare -p --fork --mount-proc zsh ~# kill -s KILL "$$" ~#
(まだそこに)。
ここではSystemTapを使用できます。ただし、
linux-image-<version>-dbgsym
これを使用するにはカーネル記号(Debianの場合)が必要であり、stap
スクリプトが接続するSystemTapまたは内部カーネル機能が変更される可能性があります。したがって、最も安定したオプションではない可能性があります。マスターモードもまれに使用する必要があります(あまりにもカラフルなものを試してはいけません)。を使用すると、
stap
実行中のカーネルの複数のポイントにコードを挿入できます。たとえば、システムコールkill()
を処理するカーネル関数に接続して、tkill()
pidが被害者のpidのときに信号を0(無害)に変更するように指示できます。stap -ge 'probe kernel.function("sys_kill") { if ($pid == 12345) $sig = 0; }'
$sig == 9
(ここのすべてのシグナルに対してSIGKILLをオーバーライドしたいかどうかを確認することもできます。)これで、これはプロセスとtkill()
一緒kill()
に呼び出されても動作しません。グループ被害者のIDなので延長が必要です。カーネル自体からシグナルを送信する場合はここに含まれません。ただし、カーネルコードを見て、カーネルがシグナリング権限を確認する場所に接続できることを確認することもできます。
stap -ge 'probe kernel.function("check_kill_permission").return { if (@entry($t->pid) == 12345) $return = -1; }'
リクエストの pid がターゲットの pid の場合 (ここの例)、
-1
(-EPERM
) を返します。これは、キラーに失敗したことを知らせる追加の利点があります。kill()
12345
~$ sleep 1000 & [1] 8508 ~$ sudo stap -ge 'probe kernel.function("check_kill_permission").return { if (@entry($t->pid) == '"$!"') $return = -1; }' & [2] 8510 ~$ kill -s KILL 8508 kill: kill 8508 failed: operation not permitted
それも動作します一部カーネルが信号自体を送信しますが、すべてではない場合。これを行うには、シグナリングを実行するカーネルコードの最低レベルの機能までドリルダウンする必要があります(
__send_signal()
少なくとも現在のバージョンのLinuxカーネルでは)。prepare_signal()
1つの方法は、最初に呼び出された関数に接続することです(0を返すと終了)。__send_signal()
stap -ge 'probe kernel.function("prepare_signal").return { if (@entry($p->pid) == 12345) $return = 0; }'
その後、プロセスが存在する限り、
stap
pid 12345は終了できません。カーネルは通常SIGKILLが機能すると仮定するため、上記のアプローチがいくつかの特別な場合に予期しない副作用を持つことは不可能ではありません(例えば、oom-killerが殺すことができない犠牲者を選択すると効果がなくなります) 。