sigkill이 프로세스에 도달하는 것을 방지하는 방법이 있습니까?

sigkill이 프로세스에 도달하는 것을 방지하는 방법이 있습니까?

나는 그 프로세스가 SIGKILL을 차단할 수 없다는 것을 알고 있습니다.

하지만 SIGKILL이 (특정) 프로세스에 도달하는 것을 일시적으로 방지하는 외부 방법이 있습니까? (예: 방화벽을 통해 패킷 삭제)

답변1

kill()시스템 호출을 호출(또는)하여 프로세스를 종료할 수 있습니다 tkill()(커널은 자체적으로 프로세스/작업을 종료할 수도 있습니다(예: Ctrl-C를 눌러 전송된 SIGINT 또는 Out of Memory Killer에서 전송한 SIGKILL). 일부 신호는 다음과 같을 수 있습니다. 다른 시스템 호출(예 ptrace: ) 로 인해 전송되었습니다 .

호출 되면 kill()모든 것이 커널에서 발생합니다.

커널 코드만이 신호를 보낸 프로세스와 신호를 받은 프로세스 사이에 위치합니다(결과적으로 종료될 수 있음).

현재 차단된 일부 커널 기능이 아직 남아 있으며 여기에서 사용할 수 있습니다.

  1. 단순 유닉스 권한. kill(2)Linux의 매뉴얼 페이지를 인용하면 다음과 같습니다 .

    프로세스가 신호를 보낼 수 있는 권한을 가지려면 권한이 부여되어야 합니다(Linux에서는 대상 프로세스의 사용자 네임스페이스에 CAP_KILL 기능이 있어야 함). 또는 전송 프로세스의 실제 또는 유효 사용자 ID가 실제 또는 유효 사용자 ID와 같아야 합니다. 대상 프로세스의 사용자 ID 사용자 ID를 설정합니다.

  2. 리눅스 보안 모듈. LSM은 (적어도 Smack, SELinux 및 의류의 경우) 신호를 받을 수 있는 항목을 필터링할 수 있습니다.

  3. 일부 프로세스는살인에 면역. init이는 Linux에서 ID가 1()인 프로세스의 경우입니다. 다른 하위 네임스페이스의 루트 프로세스도 해당 네임스페이스의 다른 프로세스에서 보낸 신호의 영향을 받지 않습니다. 커널 작업도 신호의 영향을 받지 않습니다.

  4. 그리고 거기에는커널 감지 메커니즘SystemTap에서 사용되는 것과 마찬가지로 커널의 동작에 영향을 줄 수 있으며 신호 전달을 가로채는 데 사용될 수 있습니다.

하지만 거기에 도달하기 전에 가장 먼저 시도해야 할 일은 SIGKILL 신호를 보내는 모든 작업을 차단하는 것입니다.

종료 시 중요한 프로세스(예: 루트 파일 시스템을 지원하는 데 사용되는 프로세스)가 종료되는 것을 방지하는 것이 목적이라면 대부분의 init 시스템에는 해당 프로세스가 killall5당시 발생하는 일의 영향을 받지 않도록 방지하는 방법이 있습니다. 동등한. /run/sendsigs.omit.d일부 데비안 버전을 참조하세요 . 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 네임스페이스에서 실행합니다(그러나 그 자식은 아님)..

이들 중 어느 것도 옵션이 아닌 경우 위 목록을 통해 신호가 전달되는 것을 방지할 수 있습니다.

  1. 피해자를 살인자와 다른 사용자로 실행합니다(살인자가 로 실행되지 않는다고 가정 root).
  2. LSM을 사용하세요. 예를 들어, Smack( security=smack커널 매개변수로 시작) 을 사용할 때 label피해자 프로세스에 대해 다른 값을 설정하면 다른 프로세스가 이를 보는 것은 물론이고 종료하는 것도 방지할 수 있습니다. 예를 들어:

    sudo zsh -c 'echo unkillable > /proc/self/attr/current && exec sleep 1000'
    

    sleep이 도메인에서 실행 됩니다 unkillable(이름은 무엇이든 가능합니다. 중요한 것은 현재 이 도메인에 대한 간섭을 허용하는 규칙이 정의되어 있지 않다는 것입니다). 동일한 uid로 실행 중인 프로세스라도 도메인을 종료할 수 없습니다.root그럴지라도.

  3. 새로운 pid 네임스페이스의 리더로 피해자 프로세스를 시작하면 해당 프로세스는 해당 하위 프로세스로부터 보호됩니다.

    ~$ sudo unshare -p --fork --mount-proc zsh
    ~# kill -s KILL "$$"
    ~#
    

    (아직 거기에).

  4. 여기서 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()프로세스와 함께 호출될 때 작동하지 않습니다.그룹피해자의 아이디이므로 연장이 필요합니다. 커널 자체에서 신호를 보내는 경우는 여기에 포함되지 않습니다.

    그러나 커널 코드를 보고 커널이 신호 전송 권한을 확인하는 위치에 연결할 수 있는지 확인할 수도 있습니다.

    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()한 가지 방법은 처음에 호출된 함수 에 연결하는 것입니다 (0을 반환하면 종료).__send_signal()

    stap -ge 'probe kernel.function("prepare_signal").return {
      if (@entry($p->pid) == 12345) $return = 0; }'
    

    그런 다음 프로세스가 존재하는 한 stappid 12345는 종료될 수 없습니다.

    커널은 일반적으로 SIGKILL이 작동할 것이라고 가정하므로 위의 접근 방식이 일부 특수한 경우에 예상치 못한 부작용을 갖는 것이 불가능하지 않습니다(예: oom-killer가 계속해서 죽일 수 없는 희생자를 선택하면 효과가 없게 됩니다).

관련 정보