>  기사  >  운영 및 유지보수  >  Linux에서의 시스템 기능에 대한 간단한 분석

Linux에서의 시스템 기능에 대한 간단한 분석

黄舟
黄舟원래의
2017-06-01 11:06:161911검색

이 글은 주로 linux에서의 시스템기능을 간략하게 분석한 것인데, 관심 있는 친구들은 참고하면 됩니다.

리눅스에서의 시스템 기능에 대한 관련 내용을 간략하게 분석하면 다음과 같습니다

rree.

코드는 glibc/sysdeps/posix/system.c에 있습니다. 여기서 system은 libc_system의 약한 별칭이고 libc_system은 do_system의 프런트 엔드 함수입니다. 다음으로 do_system 함수를 살펴보세요.

int
libc_system (const char *line)
{
 if (line == NULL)
  /* Check that we have a command processor available. It might
    not be available after a chroot(), for example. */
  return do_system ("exit 0") == 0;

 return do_system (line);
}
weak_alias (libc_system, system)

먼저, 함수는 SIGINT 및 SIGQUIT 신호를 처리하기 위해 일부 신호 처리기를 설정합니다. 여기서는 크게 신경 쓰지 않습니다. 여기에는 키 코드 세그먼트가 있습니다.

static int
do_system (const char *line)
{
 int status, save;
 pid_t pid;
 struct sigaction sa;
#ifndef _LIBC_REENTRANT
 struct sigaction intr, quit;
#endif
 sigset_t omask;

 sa.sa_handler = SIG_IGN;
 sa.sa_flags = 0;
 sigemptyset (&sa.sa_mask);

 DO_LOCK ();
 if (ADD_REF () == 0)
  {
   if (sigaction (SIGINT, &sa, &intr) < 0)
  {
   (void) SUB_REF ();
   goto out;
  }
   if (sigaction (SIGQUIT, &sa, &quit) < 0)
  {
   save = errno;
   (void) SUB_REF ();
   goto out_restore_sigint;
  }
  }
 DO_UNLOCK ();

 /* We reuse the bitmap in the &#39;sa&#39; structure. */
 sigaddset (&sa.sa_mask, SIGCHLD);
 save = errno;
 if (sigprocmask (SIG_BLOCK, &sa.sa_mask, &omask) < 0)
  {
#ifndef _LIBC
   if (errno == ENOSYS)
  set_errno (save);
   else
#endif
  {
   DO_LOCK ();
   if (SUB_REF () == 0)
    {
     save = errno;
     (void) sigaction (SIGQUIT, &quit, (struct sigaction *) NULL);
    out_restore_sigint:
     (void) sigaction (SIGINT, &intr, (struct sigaction *) NULL);
     set_errno (save);
    }
  out:
   DO_UNLOCK ();
   return -1;
  }
  }

#ifdef CLEANUP_HANDLER
 CLEANUP_HANDLER;
#endif

#ifdef FORK
 pid = FORK ();
#else
 pid = fork ();
#endif
 if (pid == (pid_t) 0)
  {
   /* Child side. */
   const char *new_argv[4];
   new_argv[0] = SHELL_NAME;
   new_argv[1] = "-c";
   new_argv[2] = line;
   new_argv[3] = NULL;

   /* Restore the signals. */
   (void) sigaction (SIGINT, &intr, (struct sigaction *) NULL);
   (void) sigaction (SIGQUIT, &quit, (struct sigaction *) NULL);
   (void) sigprocmask (SIG_SETMASK, &omask, (sigset_t *) NULL);
   INIT_LOCK ();

   /* Exec the shell. */
   (void) execve (SHELL_PATH, (char *const *) new_argv, environ);
   _exit (127);
  }
 else if (pid < (pid_t) 0)
  /* The fork failed. */
  status = -1;
 else
  /* Parent side. */
  {
   /* Note the system() is a cancellation point. But since we call
   waitpid() which itself is a cancellation point we do not
   have to do anything here. */
   if (TEMP_FAILURE_RETRY (waitpid (pid, &status, 0)) != pid)
  status = -1;
  }

#ifdef CLEANUP_HANDLER
 CLEANUP_RESET;
#endif

 save = errno;
 DO_LOCK ();
 if ((SUB_REF () == 0
    && (sigaction (SIGINT, &intr, (struct sigaction *) NULL)
    | sigaction (SIGQUIT, &quit, (struct sigaction *) NULL)) != 0)
   || sigprocmask (SIG_SETMASK, &omask, (sigset_t *) NULL) != 0)
  {
#ifndef _LIBC
   /* glibc cannot be used on systems without waitpid. */
   if (errno == ENOSYS)
  set_errno (save);
   else
#endif
  status = -1;
  }
 DO_UNLOCK ();

 return status;
}

do_system


먼저, 프런트 엔드 함수가 시스템을 호출합니다. 자식 프로세스를 생성하려면 포크를 호출하세요. 그 중 포크에는 두 개의 반환 값이 있습니다. 부모 프로세스에 대해서는 자식 프로세스의 pid를 반환하고 자식 프로세스에 대해서는 0을 반환합니다. 따라서 하위 프로세스는 6~24줄의 코드를 실행하고 상위 프로세스는 30~35줄의 코드를 실행합니다.

자식 프로세스의 논리는 매우 명확합니다. execve는 SHELL_PATH에 지정된 프로그램을 실행하기 위해 호출됩니다. 매개변수는 new_argv를 통해 전달되며 환경 변수

는 전역 변수인eviron입니다. SHELL_PATH 및 SHELL_NAME은 다음과 같이 정의됩니다.

#ifdef FORK
 pid = FORK ();
#else
 pid = fork ();
#endif
 if (pid == (pid_t) 0)
  {
   /* Child side. */
   const char *new_argv[4];
   new_argv[0] = SHELL_NAME;
   new_argv[1] = "-c";
   new_argv[2] = line;
   new_argv[3] = NULL;

   /* Restore the signals. */
   (void) sigaction (SIGINT, &intr, (struct sigaction *) NULL);
   (void) sigaction (SIGQUIT, &quit, (struct sigaction *) NULL);
   (void) sigprocmask (SIG_SETMASK, &omask, (sigset_t *) NULL);
   INIT_LOCK ();

   /* Exec the shell. */
   (void) execve (SHELL_PATH, (char *const *) new_argv, environ);
   _exit (127);
  }
 else if (pid < (pid_t) 0)
  /* The fork failed. */
  status = -1;
 else
  /* Parent side. */
  {
   /* Note the system() is a cancellation point. But since we call
   waitpid() which itself is a cancellation point we do not
   have to do anything here. */
   if (TEMP_FAILURE_RETRY (waitpid (pid, &status, 0)) != pid)
  status = -1;
  }


실제로 시스템에 전달된 명령을 실행하기 위해

/bin/sh -c "command"를 호출하는 하위 프로세스가 생성됩니다. . 실제로 제가 시스템 함수에 대해 연구하는 이유와 초점은 다음과 같습니다.

CTF의 pwn 질문에서 스택 오버플로를 통해 시스템 함수를 호출하는 경우 가끔 실패한다는 고수님들의 말씀을 들었습니다. 나는 항상 무지했습니다. 오늘 심도있는 연구 끝에 마침내 알아 냈습니다.

여기서 시스템 기능에 필요한 환경 변수는 전역 변수인 environ에 저장되는데, 이 변수의 내용은 무엇인가요?

environ은 glibc/csu/libc-start.c에 정의되어 있습니다. 몇 가지 핵심 설명을 살펴보겠습니다.

#define  SHELL_PATH  "/bin/sh"  /* Path of the shell. */
#define  SHELL_NAME  "sh"    /* Name to give it. */


libc_start_main은 _start가 호출하는 함수로, 프로그램 시작 시 일부 초기화 작업이 필요합니다. 이 용어를 이해하지 못한다면 이 글을 읽어보세요. 다음으로 LIBC_START_MAIN 함수를 살펴보세요.

# define LIBC_START_MAIN libc_start_main


Environ 값은 SHARED 정의 없이 19행에서 정의된 것을 볼 수 있습니다. 시작 프로그램이 LIBC_START_MAIN을 호출하기 전에 먼저 환경 변수와 argv에

문자열

을 저장합니다(실제로는 스택에 저장됨). 그런 다음 각 문자열의 주소를 환경 변수에, 각 문자열의 주소를 argv에 순차적으로 저장합니다. 주소와 argc가 스택에 푸시되므로 환경 변수 배열 은 argv 배열 바로 뒤에 빈 주소로 구분되어 위치해야 합니다. 따라서 17행의 &argv[argc + 1] 문은 스택에 있는 환경 변수 배열의 첫 번째 주소를 가져와서 ev에 저장하고 마지막으로 Environ에 저장합니다. 203행에서는 Environ 값을 스택에 푸시하는 기본 함수를 호출합니다. 이 함수는 Environ의 주소를 덮어쓰지 않는 한 스택 오버플로로 덮어쓰여도 문제가 없습니다. 따라서 스택 오버플로의 길이가 너무 크고 오버플로 내용이 Environ 주소의 중요한 내용을 덮으면 시스템 함수 호출이 실패합니다. 특정 환경 변수가 오버플로 주소에서 얼마나 떨어져 있는지는 _start를 인터럽트하여 확인할 수 있습니다.

위 내용은 Linux에서의 시스템 기능에 대한 간단한 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.