Windows: Interactive shells Part 2

Introduction

In part 1, we briefly looked at how to create a simple tcp application that enabled interaction with cmd.exe. It was accomplished by assigning a socket returned from WSASocket() API to stdin,stdout and stderr of cmd.exe before execution.

While this is a popular method of creating a shell, there are some minor limitations. First is that if the connection is severed unexpectedly, cmd.exe will continue to run as if nothing is wrong. Second is that we are unable to manipulate the incoming and outgoing stream of bytes between each system.

The solution to these minor disadvantages is to use synchronization. The code itself remains largely unchanged for now although we use 2 new functions to execute cmd.exe and monitor read/close events on TCP socket in addition to processing output and termination of cmd.exe.

Like the shell in part 1, this can also be used with ncat, netcat or something similar.

Waiting for events

In order to handle multiple situations where cmd.exe could be terminated, the socket could be closed, the socket might require being read; we need to use events. When these events are signalled by the operating system, our client and server applications will act accordingly.

The following function will monitor termination of cmd.exe, TCP socket being closed, TCP socket with data waiting to be read and data available to read from stdout of cmd.exe.

DWORD wait_evt (HANDLE *evt, DWORD evt_cnt, 
  DWORD sck_evt, SOCKET s)
{
  WSANETWORKEVENTS ne;
  u_long           off=0;
  DWORD            e;
  
  // wait for read/close and accept events
  WSAEventSelect (s, evt[sck_evt], FD_CLOSE | FD_READ | FD_ACCEPT);
  e=WaitForMultipleObjects (evt_cnt, evt, FALSE, INFINITE);

  // enumerate events on socket
  WSAEnumNetworkEvents (s, evt[sck_evt], &ne);
  // disable events on socket  
  WSAEventSelect (s, evt[sck_evt], 0);
  // set to blocking mode
  ioctlsocket (s, FIONBIO, &off);
  // socket closed?
  if (ne.lNetworkEvents & FD_CLOSE) {
    e = ~0UL;
  }
  return e;
}

Executing cmd.exe

This is actually based on code written by LSD group back in 2002 for their winasm codes. It’s also very similar to what’s used by Ncat. I’ve commented each step rather than explain here.

void cmd (SOCKET s)
{
  SECURITY_ATTRIBUTES sa;
  PROCESS_INFORMATION pi;
  STARTUPINFO         si;
  OVERLAPPED          lap;
  
  HANDLE              lh[4];
  DWORD               p, e, wr;
  BYTE                buf[BUFSIZ];
  int                 len;
  HANDLE              evt[MAXIMUM_WAIT_OBJECTS];
  DWORD               evt_cnt=0, sck_evt=0;
  DWORD               stdout_evt=0, proc_evt=0;

  // create event for socket
  evt[sck_evt = evt_cnt++] = WSACreateEvent();
  
  // initialize security descriptor
  sa.nLength              = sizeof (SECURITY_ATTRIBUTES);
  sa.lpSecurityDescriptor = NULL;
  sa.bInheritHandle       = TRUE;
  
  // create anonymous read/write pipes for stdin of cmd.exe
  if (CreatePipe (&lh[0], &lh[1], &sa, 0)) 
  {  
    // format named pipe using tick count and current process id
    wnsprintf ((char*)buf, BUFSIZ, "\\.\pipe\%08X", 
      GetCurrentProcessId() ^ GetTickCount());
    
    // create named pipe for stdout/stderr of cmd.exe
    lh[2] = CreateNamedPipe ((char*)buf, 
        PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
        PIPE_TYPE_BYTE     | PIPE_READMODE_BYTE | PIPE_WAIT, 
        PIPE_UNLIMITED_INSTANCES, 0, 0, 0, NULL);
        
    if (lh[2] != INVALID_HANDLE_VALUE) 
    { 
      // open the pipe
      lh[3] = CreateFile ((char*)buf, MAXIMUM_ALLOWED, 
          0, &sa, OPEN_EXISTING, 0, NULL);
      
      if (lh[3] != INVALID_HANDLE_VALUE) 
      {
        // create event for stdout handle of cmd.exe
        // initially in a signalled state
        evt[evt_cnt]=CreateEvent (NULL, TRUE, TRUE, NULL);
        stdout_evt = evt_cnt++;
  
        // zero initialize parameters for CreateProcess
        ZeroMemory (&si, sizeof (si));
        ZeroMemory (&pi, sizeof (pi));

        si.cb              = sizeof (si);
        si.hStdInput       = lh[0];  // assign the anon read pipe
        si.hStdError       = lh[3];  // assign the named write pipe
        si.hStdOutput      = lh[3];  // 
        si.dwFlags         = STARTF_USESTDHANDLES;
        
        // execute cmd.exe with no window
        if (CreateProcess (NULL, "cmd", NULL, NULL, TRUE, 
            CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) 
        {
          // monitor state of cmd.exe
          evt[proc_evt = evt_cnt++] = pi.hProcess;
          
          ZeroMemory (&lap, sizeof (lap));
          // assign stdout event
          lap.hEvent = evt[stdout_evt];
          
          // set pending to 0
          p=0;
          
          for (;;)
          {
            // wait for events on cmd.exe and socket
            e=wait_evt(evt, evt_cnt, sck_evt, s);

            // cmd.exe ended?
            if (e==proc_evt) {
              break;
            }
            
            // wait failed?
            if (e == -1) {
              break;
            }
            // is this socket event?
            if (e == sck_evt) 
            {
              // receive data from socket
              len=recv (s, (char*)buf, sizeof(buf), 0);
              if (len<=0) break;
              
              // write to stdin of cmd.exe
              WriteFile (lh[1], buf, len, &wr, 0);             
            } else
           
            // output from cmd.exe?
            if (e == stdout_evt) 
            {
              // if not in pending read state, read stdin
              if (p == 0)
              {
                ReadFile (lh[2], buf, sizeof(buf), 
                  (LPDWORD)&len, &lap);
                p++;
              } else {
                // get overlapped result
                if (!GetOverlappedResult (lh[2], &lap, 
                  (LPDWORD)&len, FALSE)) {
                  break;
                }
              }
              // if we have something
              if (len != 0)
              {
                // send to remote
                len=send (s, (char*)buf, len, 0);
                if (len<=0) break;
                p--;
              }
            }
          }
          // end cmd.exe incase it's still running
          TerminateProcess (pi.hProcess, 0);
          // close handles and decrease events
          CloseHandle (pi.hThread);
          CloseHandle (pi.hProcess);
          evt_cnt--;
        }
        // close handle to named pipe for stdout
        CloseHandle (lh[3]);
      }
      // close named pipe for stdout
      CloseHandle (lh[2]);
    }
    // close anon pipes for read/write to stdin
    CloseHandle (lh[1]);
    CloseHandle (lh[0]);
  }
  // close stdout event handle
  CloseHandle (evt[stdout_evt]);
  evt_cnt--;
  // close socket event handle
  CloseHandle (evt[sck_evt]);
  evt_cnt--;
}

Limitations

If you just wanted to interact with cmd.exe and handle situations where the connection may be severed unexpectedly, this will tidy up and exit gracefully as opposed to the shell created in part 1 which would carry on running cmd.exe.

In part 3, we’ll look at a simple packet protocol that allows transfer of files between 2 instances of the same shell.

Source

For more details, see s2.c

Advertisements
This entry was posted in programming, windows and tagged , , , , , . Bookmark the permalink.

2 Responses to Windows: Interactive shells Part 2

  1. Pingback: Windows: Interactive shells Part 3 |

  2. Pingback: Windows: Interactive shells Part 3 | modexp

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