Simple Analysis of system functions in linux, linuxsystem Functions
1 int 2 __libc_system (const char *line) 3 { 4 if (line == NULL) 5 /* Check that we have a command processor available. It might 6 not be available after a chroot(), for example. */ 7 return do_system ("exit 0") == 0; 8 9 return do_system (line);10 }11 weak_alias (__libc_system, system)
The code is located in glibc/sysdeps/posix/system. c. Here, system is the weak alias of _ libc_system, and _ libc_system is the front-end function of do_system. Check the parameters. Next, let's look at the do_system function.
1 static int 2 do_system (const char *line) 3 { 4 int status, save; 5 pid_t pid; 6 struct sigaction sa; 7 #ifndef _LIBC_REENTRANT 8 struct sigaction intr, quit; 9 #endif 10 sigset_t omask; 11 12 sa.sa_handler = SIG_IGN; 13 sa.sa_flags = 0; 14 __sigemptyset (&sa.sa_mask); 15 16 DO_LOCK (); 17 if (ADD_REF () == 0) 18 { 19 if (__sigaction (SIGINT, &sa, &intr) < 0) 20 { 21 (void) SUB_REF (); 22 goto out; 23 } 24 if (__sigaction (SIGQUIT, &sa, &quit) < 0) 25 { 26 save = errno; 27 (void) SUB_REF (); 28 goto out_restore_sigint; 29 } 30 } 31 DO_UNLOCK (); 32 33 /* We reuse the bitmap in the 'sa' structure. */ 34 __sigaddset (&sa.sa_mask, SIGCHLD); 35 save = errno; 36 if (__sigprocmask (SIG_BLOCK, &sa.sa_mask, &omask) < 0) 37 { 38 #ifndef _LIBC 39 if (errno == ENOSYS) 40 __set_errno (save); 41 else 42 #endif 43 { 44 DO_LOCK (); 45 if (SUB_REF () == 0) 46 { 47 save = errno; 48 (void) __sigaction (SIGQUIT, &quit, (struct sigaction *) NULL); 49 out_restore_sigint: 50 (void) __sigaction (SIGINT, &intr, (struct sigaction *) NULL); 51 __set_errno (save); 52 } 53 out: 54 DO_UNLOCK (); 55 return -1; 56 } 57 } 58 59 #ifdef CLEANUP_HANDLER 60 CLEANUP_HANDLER; 61 #endif 62 63 #ifdef FORK 64 pid = FORK (); 65 #else 66 pid = __fork (); 67 #endif 68 if (pid == (pid_t) 0) 69 { 70 /* Child side. */ 71 const char *new_argv[4]; 72 new_argv[0] = SHELL_NAME; 73 new_argv[1] = "-c"; 74 new_argv[2] = line; 75 new_argv[3] = NULL; 76 77 /* Restore the signals. */ 78 (void) __sigaction (SIGINT, &intr, (struct sigaction *) NULL); 79 (void) __sigaction (SIGQUIT, &quit, (struct sigaction *) NULL); 80 (void) __sigprocmask (SIG_SETMASK, &omask, (sigset_t *) NULL); 81 INIT_LOCK (); 82 83 /* Exec the shell. */ 84 (void) __execve (SHELL_PATH, (char *const *) new_argv, __environ); 85 _exit (127); 86 } 87 else if (pid < (pid_t) 0) 88 /* The fork failed. */ 89 status = -1; 90 else 91 /* Parent side. */ 92 { 93 /* Note the system() is a cancellation point. But since we call 94 waitpid() which itself is a cancellation point we do not 95 have to do anything here. */ 96 if (TEMP_FAILURE_RETRY (__waitpid (pid, &status, 0)) != pid) 97 status = -1; 98 } 99 100 #ifdef CLEANUP_HANDLER101 CLEANUP_RESET;102 #endif103 104 save = errno;105 DO_LOCK ();106 if ((SUB_REF () == 0107 && (__sigaction (SIGINT, &intr, (struct sigaction *) NULL)108 | __sigaction (SIGQUIT, &quit, (struct sigaction *) NULL)) != 0)109 || __sigprocmask (SIG_SETMASK, &omask, (sigset_t *) NULL) != 0)110 {111 #ifndef _LIBC112 /* glibc cannot be used on systems without waitpid. */113 if (errno == ENOSYS)114 __set_errno (save);115 else116 #endif117 status = -1;118 }119 DO_UNLOCK ();120 121 return status;122 }
Do_system
First, the function sets up some signal processing programs to process SIGINT and SIGQUIT signals. Here we are not very concerned about it. The key code segment is here.
1 #ifdef FORK 2 pid = FORK (); 3 #else 4 pid = __fork (); 5 #endif 6 if (pid == (pid_t) 0) 7 { 8 /* Child side. */ 9 const char *new_argv[4];10 new_argv[0] = SHELL_NAME;11 new_argv[1] = "-c";12 new_argv[2] = line;13 new_argv[3] = NULL;14 15 /* Restore the signals. */16 (void) __sigaction (SIGINT, &intr, (struct sigaction *) NULL);17 (void) __sigaction (SIGQUIT, &quit, (struct sigaction *) NULL);18 (void) __sigprocmask (SIG_SETMASK, &omask, (sigset_t *) NULL);19 INIT_LOCK ();20 21 /* Exec the shell. */22 (void) __execve (SHELL_PATH, (char *const *) new_argv, __environ);23 _exit (127);24 }25 else if (pid < (pid_t) 0)26 /* The fork failed. */27 status = -1;28 else29 /* Parent side. */30 {31 /* Note the system() is a cancellation point. But since we call32 waitpid() which itself is a cancellation point we do not33 have to do anything here. */34 if (TEMP_FAILURE_RETRY (__waitpid (pid, &status, 0)) != pid)35 status = -1;36 }
First, the system calls fork through the frontend function to generate a sub-process. fork has two return values: the pid of the sub-process returned for the parent process and the pid of the sub-process returned by 0. Therefore, the child process executes 6-24 lines of code, and the parent process executes 30-35 lines of code.
The logic of the sub-process is very clear. execve is called to execute the program specified by SHELL_PATH. The parameter is passed through new_argv, and the environment variable is global variable _ environ.
The SHELL_PATH and SHELL_NAME are defined as follows:
1 #define SHELL_PATH "/bin/sh" /* Path of the shell. */2 #define SHELL_NAME "sh" /* Name to give it. */
In fact, it is to generate a sub-process to call the/bin/sh-c "command" to execute the command passed into the system.
The following is the reason and focus of my research on system functions:
In the pwn question of CTF, calling the system function through Stack Overflow sometimes fails. Let's say that the environment variables are overwritten, but they have always been ignorant. I have learned more about it today, I finally figured it out.
Here, the environment variables required by the system function are stored in the global variable _ environ. What is the content of this variable.
_ Environ is defined in glibc/csu/libc-start.c, let's look at several key statements.
# define LIBC_START_MAIN __libc_start_main
_ Libc_start_main is the function called by _ start, which involves some initialization work at the beginning of the program. If you do not know these terms, you can read this article. Next, let's look at the LIBC_START_MAIN function.
1 STATIC int 2 LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL), 3 int argc, char **argv, 4 #ifdef LIBC_START_MAIN_AUXVEC_ARG 5 ElfW(auxv_t) *auxvec, 6 #endif 7 __typeof (main) init, 8 void (*fini) (void), 9 void (*rtld_fini) (void), void *stack_end) 10 { 11 /* Result of the 'main' function. */ 12 int result; 13 14 __libc_multiple_libcs = &_dl_starting_up && !_dl_starting_up; 15 16 #ifndef SHARED 17 char **ev = &argv[argc + 1]; 18 19 __environ = ev; 20 21 /* Store the lowest stack address. This is done in ld.so if this is 22 the code for the DSO. */ 23 __libc_stack_end = stack_end;
......
202 /* Nothing fancy, just call the function. */203 result = main (argc, argv, __environ MAIN_AUXVEC_PARAM);204 #endif205 206 exit (result);207 }
We can see that the value of _ environ is defined in row 19th without define SHARED. Before the program calls LIBC_START_MAIN, it will first save the environment variable and the string in argv (actually saved to the stack), and then the address of each string in the environment variable in turn, the address of each string in argv and the address of argc into the stack, so the environment variable array must be located at the front of the argv array, separated by an empty address. Therefore, the & argv [argc + 1] statement in line 1 is to take the first address of the environment variable array on the stack, save it to ev, and finally save it to _ environ. When the main function is called in Row 3, the value of _ environ is added to the stack. It is no problem to overwrite the value of _ environ by Stack Overflow. You only need to ensure that the address in _ environ is not overwritten.
Therefore, when the stack overflow length is too large and the overflow content overwrites important content in the _ environ address, calling the system function will fail. How far is the specific environment variable from the overflow address? You can view it by breaking it down in _ start.