Maison > Article > Opération et maintenance > Introduction à l'analyse du fonctionnement du système sous Linux
Cet article analyse principalement brièvement le systèmefonction sous linux, qui a une certaine valeur de référence. Les amis intéressés peuvent se référer à l'
analyse simple du contenu pertinent de. la fonction système sous Linux est la suivante :
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)
Le code se trouve dans glibc/sysdeps/posix/system.c, où system est un alias faible de libc_system, et libc_system est la fonction frontale de do_system. Après avoir vérifié les paramètres, regardons la fonction do_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
Tout d'abord, la fonction configure des gestionnaires de signaux pour gérer les signaux SIGINT et SIGQUIT. Nous ne nous en soucions pas trop ici. Les segments de code clés sont ici
#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; }<.> Passez d'abord la fonction frontale. L'appel de l'appel système fork génère un processus enfant. Fork a deux valeurs de retour. Il renvoie le pid du processus enfant au processus parent et 0 au processus enfant. Ainsi, le processus enfant exécute 6 à 24 lignes de code et le processus parent exécute 30 à 35 lignes de code. La logique du processus enfant est très claire. execve est appelé pour exécuter le programme spécifié par SHELL_PATH Les paramètres sont passés via new_argv, et la variable d'environnement
est la variable globale environ.
SHELL_PATH et SHELL_NAME sont définis comme suit#define SHELL_PATH "/bin/sh" /* Path of the shell. */ #define SHELL_NAME "sh" /* Name to give it. */En fait, il génère un sous-processus et appelle
/bin/sh -c "command" Exécutez la commande transmise au système.
Ce qui suit est en fait la raison et l'objet de mes recherches sur la fonction système : Dans la question pwn de CTF, l'appel de la fonction système via un débordement de pile échoue parfois, j'ai entendu les maîtres dire. que la variable d'environnement est écrasée, mais j'ai toujours été confus. Je l'ai étudié en profondeur aujourd'hui et j'ai finalement compris. Les variables d'environnement requises par la fonction système ici sont stockées dans la variable globale environ, alors quel est le contenu de cette variable. environ est défini dans glibc/csu/libc-start.c. Examinons quelques déclarations clés.# define LIBC_START_MAIN libc_start_mainlibc_start_main est la fonction appelée par _start, qui implique un travail d'initialisation au début du programme. Si vous ne comprenez pas ces termes, vous pouvez lire cet article. Ensuite, regardez la fonction LIBC_START_MAIN.
STATIC int LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL), int argc, char **argv, #ifdef LIBC_START_MAIN_AUXVEC_ARG ElfW(auxv_t) *auxvec, #endif typeof (main) init, void (*fini) (void), void (*rtld_fini) (void), void *stack_end) { /* Result of the 'main' function. */ int result; libc_multiple_libcs = &_dl_starting_up && !_dl_starting_up; #ifndef SHARED char **ev = &argv[argc + 1]; environ = ev; /* Store the lowest stack address. This is done in ld.so if this is the code for the DSO. */ libc_stack_end = stack_end; ...... /* Nothing fancy, just call the function. */ result = main (argc, argv, environ MAIN_AUXVEC_PARAM); #endif exit (result); }On peut voir que la valeur d'environ est définie à la ligne 19 sans définir SHARED. Avant que le programme de démarrage n'appelle LIBC_START_MAIN, il enregistrera d'abord les variables d'environnement et la
chaîne dans argv (en fait, elle est enregistrée sur la pile), puis enregistrera séquentiellement les adresses de chaque chaîne dans la variable d'environnement et chaque chaîne dans argv. L'adresse de la chaîne d'élément et argc sont poussées sur la pile, donc la variable d'environnement tableau doit être située directement derrière le tableau argv, séparée par une adresse vide. Ainsi, l'instruction &argv[argc + 1] à la ligne 17 prend la première adresse du tableau de variables d'environnement sur la pile, l'enregistre dans ev et enfin l'enregistre dans environ. La ligne 203 appelle la fonction principale, qui poussera la valeur environ sur la pile. Il n'y a aucun problème si elle est écrasée par un débordement de pile, tant que l'adresse dans environ n'est pas écrasée.
Ainsi, lorsque la longueur du débordement de pile est trop grande et que le contenu du débordement couvre le contenu important de l'adresse dans environ, l'appel de la fonction système échouera. La distance entre la variable d'environnement spécifique et l'adresse de débordement peut être vérifiée en l'interrompant dans _start.Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!