Heim > Artikel > Betrieb und Instandhaltung > Einführung in die Analyse der Systemfunktion unter Linux
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)Der Code befindet sich in glibc/sysdeps/posix/system.c, wobei system ein schwacher Alias von libc_system und libc_system die Front-End-Funktion ist von do_system. Nachdem wir die Parameter überprüft haben, schauen wir uns die Funktion do_system an.
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_systemZunächst richtet die Funktion einige Signalhandler ein, um die SIGINT- und SIGQUIT-Signale zu verarbeiten. Die wichtigsten Codesegmente sind hier 🎜>Übergeben Sie zunächst die Front-End-Funktion. Durch Aufrufen des Systemaufrufs Fork wird ein untergeordneter Prozess generiert. Er gibt die PID des untergeordneten Prozesses an den übergeordneten Prozess zurück und gibt 0 an den untergeordneten Prozess zurück. Der untergeordnete Prozess führt also 6–24 Codezeilen aus und der übergeordnete Prozess führt 30–35 Codezeilen aus. Die Logik des untergeordneten Prozesses ist sehr klar. Execve wird aufgerufen, um das durch SHELL_PATH angegebene Programm auszuführen. Die Parameter werden über new_argv übergeben, und die Umgebungsvariable
#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
Tatsächlich generiert es einen Unterprozess und ruft
/bin/sh -c „command“ auf
#define SHELL_PATH "/bin/sh" /* Path of the shell. */ #define SHELL_NAME "sh" /* Name to give it. */Führen Sie den an das System übergebenen Befehl aus.
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.libc_start_main ist die von _start aufgerufene Funktion, die einige Initialisierungsarbeiten zu Beginn des Programms erfordert. Wenn Sie diese Begriffe nicht verstehen, können Sie diesen Artikel lesen. Schauen Sie sich als Nächstes die Funktion LIBC_START_MAIN an.
Wir können sehen, dass der Wert von environ in Zeile 19 definiert ist, ohne SHARED zu definieren. Bevor das Startprogramm LIBC_START_MAIN aufruft, speichert es zunächst die Umgebungsvariablen und die# define LIBC_START_MAIN libc_start_mainZeichenfolge
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
ArraySTATIC 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); }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 vonEinführung in die Analyse der Systemfunktion unter Linux. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!