Heim  >  Artikel  >  Betrieb und Instandhaltung  >  Eine einfache Analyse der Systemfunktion unter Linux

Eine einfache Analyse der Systemfunktion unter Linux

黄舟
黄舟Original
2017-06-01 11:06:161917Durchsuche

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 &#39;sa&#39; 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
Zunächst richtet die Funktion einige Signalhandler ein, um die SIGINT- und SIGQUIT-Signale zu verarbeiten. Die wichtigsten Codesegmente sind hier

#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;
  }


Zuerst ruft die Front-End-Funktion den Systemaufruffork auf, um einen untergeordneten Prozess zu generieren. Er gibt die PID des untergeordneten Prozesses an den übergeordneten Prozess zurück der untergeordnete Prozess. 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

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 &#39;main&#39; 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

Zeichenfolge

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!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn