In diesem Artikel wird hauptsächlich die Systemfunktionunter Linux kurz analysiert. Interessierte Freunde können sich auf die
einfache Analyse beziehen Der relevante Inhalt von Die Systemfunktion unter Linux lautet wie folgt: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
#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; }
ist die globale Variablenumgebung. SHELL_PATH und SHELL_NAME sind wie folgt definiert
#define SHELL_PATH "/bin/sh" /* Path of the shell. */ #define SHELL_NAME "sh" /* Name to give it. */
erzeugt tatsächlich einen Unterprozessaufruf
/bin/sh -c "command" um den an das System übergebenen Befehl auszuführen. Das Folgende ist eigentlich der Grund und der Schwerpunkt meiner Forschung zur Systemfunktion:
In der PWN-Frage von CTF schlägt der Aufruf der Systemfunktion durch Stapelüberlauf manchmal fehl dass die Umgebungsvariable überschrieben wird, aber ich war immer verwirrt. Ich habe es heute eingehend studiert und es endlich herausgefunden.
Die hier für die Systemfunktion erforderlichen Umgebungsvariablen werden in der globalen Variablenumgebung gespeichert. Was ist also der Inhalt dieser Variablen?
environ ist in glibc/csu/libc-start.c definiert. Schauen wir uns ein paar Schlüsselaussagen an.
# define LIBC_START_MAIN libc_start_main
libc_start_main ist die von _start aufgerufene Funktion, die einige Initialisierungsarbeiten zu Beginn des Programms erfordert Wenn Sie mehr darüber wissen, können Sie diesen Artikel lesen. Schauen Sie sich als Nächstes die Funktion LIBC_START_MAIN an.
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); }
Wir können sehen, dass der Wert von environ in Zeile 19 ohne define SHARED definiert ist. Bevor das Startprogramm LIBC_START_MAIN aufruft, speichert es zunächst die Umgebungsvariablen und die
in argv (eigentlich wird sie auf dem Stapel gespeichert) und speichert dann nacheinander die Adressen jeder Zeichenfolge in der Umgebungsvariablen und Jede Zeichenfolge in argv. Die Adresse der Elementzeichenfolge und argc werden auf den Stapel verschoben, daher muss sich das Umgebungsvariable Array direkt hinter dem argv-Array befinden, getrennt durch eine leere Adresse. Die &argv[argc + 1]-Anweisung in Zeile 17 nimmt also die erste Adresse des Umgebungsvariablen-Arrays auf dem Stapel, speichert sie in ev und schließlich in environ. Zeile 203 ruft die Hauptfunktion auf, die den Umgebungswert auf den Stapel legt. Es gibt kein Problem, wenn dieser durch einen Stapelüberlauf überschrieben wird, solange die Adresse in der Umgebung nicht überschrieben wird. Wenn also die Länge des Stapelüberlaufs zu groß ist und der Überlaufinhalt den wichtigen Inhalt in der Adresse in der Umgebung abdeckt, schlägt der Aufruf der Systemfunktion fehl. Wie weit die spezifische Umgebungsvariable von der Überlaufadresse entfernt ist, kann durch eine Unterbrechung in _start überprüft werden.
Das obige ist der detaillierte Inhalt vonEine einfache Analyse der Systemfunktion unter Linux. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!