이 글은 주로 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)
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 'sa' 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
는 전역 변수인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 중국어 웹사이트의 기타 관련 기사를 참조하세요!