lib.core.pipe

Package lib :: Package core :: Module pipe |
[hide private]
[frames] | no frames]

# Source Code for Module lib.core.pipe

      1  # Copyright (C) 2010-2013 Claudio Guarnieri.
      2  # Copyright (C) 2014-2016 Cuckoo Foundation.
      3  # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
      4  # See the file 'docs/LICENSE' for copying permission.
      5
      6  import logging
      7  import socket
      8  import threading
      9
     10  from ctypes import create_string_buffer, c_uint, byref, sizeof
     11
     12  from lib.common.defines import KERNEL32, PIPE_ACCESS_INBOUND, ERROR_MORE_DATA
     13  from lib.common.defines import PIPE_TYPE_BYTE, PIPE_WAIT, ERROR_PIPE_CONNECTED
     14  from lib.common.defines import PIPE_UNLIMITED_INSTANCES, INVALID_HANDLE_VALUE
     15  from lib.common.defines import FILE_FLAG_WRITE_THROUGH, PIPE_READMODE_BYTE
     16  from lib.common.defines import ERROR_BROKEN_PIPE, PIPE_TYPE_MESSAGE
     17  from lib.common.defines import PIPE_ACCESS_DUPLEX, PIPE_READMODE_MESSAGE
     18
     19  log = logging.getLogger(__name__)
     20
     21  BUFSIZE = 0x10000
     22
     23 -class PipeForwarder(threading.Thread):
     24      """The Pipe Forwarder forwards all data received from a local pipe to
     25      the Cuckoo server through a socket."""
     26      sockets = {}
     27      active = {}
     28
     29 -    def __init__(self, pipe_handle, destination):
     30          threading.Thread.__init__(self)
     31          self.pipe_handle = pipe_handle
     32          self.destination = destination
     33
     34 -    def run(self):
     35          buf = create_string_buffer(BUFSIZE)
     36          bytes_read = c_uint()
     37          pid = c_uint()
     38
     39          # The first four bytes indicate the process identifier. In case the
     40          # pipe handle is closed in an unknown way, reopening one and
     41          # specifying the same process identifier will reuse the same socket,
     42          # thus making it look like as if it was never closed in the first
     43          # place.
     44          success = KERNEL32.ReadFile(self.pipe_handle,
     45                                      byref(pid), sizeof(pid),
     46                                      byref(bytes_read), None)
     47
     48          if not success or bytes_read.value != sizeof(pid):
     49              log.warning("Unable to read the process identifier of this "
     50                          "log pipe instance.")
     51              KERNEL32.CloseHandle(self.pipe_handle)
     52              return
     53
     54          if self.active.get(pid.value):
     55              log.warning("A second log pipe handler for an active process is "
     56                          "being requested, denying request.")
     57              KERNEL32.CloseHandle(self.pipe_handle)
     58              return
     59
     60          if pid.value not in self.sockets:
     61              self.sockets[pid.value] = \
     62                  socket.create_connection(self.destination)
     63
     64          sock = self.sockets[pid.value]
     65          self.active[pid.value] = True
     66
     67          while True:
     68              success = KERNEL32.ReadFile(self.pipe_handle,
     69                                          byref(buf), sizeof(buf),
     70                                          byref(bytes_read), None)
     71
     72              if success or KERNEL32.GetLastError() == ERROR_MORE_DATA:
     73                  sock.sendall(buf.raw[:bytes_read.value])
     74              # If we get the broken pipe error then this pipe connection has
     75              # been terminated for one reason or another. So break from the
     76              # loop and make the socket "inactive", that is, another pipe
     77              # connection can in theory pick it up. (This will only happen in
     78              # cases where malware for some reason broke our pipe connection).
     79              elif KERNEL32.GetLastError() == ERROR_BROKEN_PIPE:
     80                  break
     81              else:
     82                  log.warning("The log pipe handler has failed, last error %d.",
     83                              KERNEL32.GetLastError())
     84                  break
     85
     86          self.active[pid.value] = False
     87
     88 -class PipeDispatcher(threading.Thread):
     89      """Receives commands through a local pipe, forwards them to the
     90      dispatcher, and returns the response."""
     91 -    def __init__(self, pipe_handle, dispatcher):
     92          threading.Thread.__init__(self)
     93          self.pipe_handle = pipe_handle
     94          self.dispatcher = dispatcher
     95          self.do_run = True
     96
     97 -    def _read_message(self, buf):
     98          """Reads a message."""
     99          bytes_read = c_uint()
    100          ret = ""
    101
    102          while True:
    103              success = KERNEL32.ReadFile(self.pipe_handle,
    104                                          byref(buf), sizeof(buf),
    105                                          byref(bytes_read), None)
    106
    107              if KERNEL32.GetLastError() == ERROR_MORE_DATA:
    108                  ret += buf.raw[:bytes_read.value]
    109              elif success:
    110                  return ret + buf.raw[:bytes_read.value]
    111              else:
    112                  return
    113
    114 -    def run(self):
    115          """Run the pipe dispatcher."""
    116          buf = create_string_buffer(BUFSIZE)
    117          bytes_written = c_uint()
    118
    119          while self.do_run:
    120              message = self._read_message(buf)
    121              if not message:
    122                  break
    123
    124              response = self.dispatcher.dispatch(message) or "OK"
    125
    126              KERNEL32.WriteFile(self.pipe_handle, response, len(response),
    127                                 byref(bytes_written), None)
    128
    129          KERNEL32.CloseHandle(self.pipe_handle)
    130
    131 -class PipeServer(threading.Thread):
    132      """The Pipe Server accepts incoming pipe handlers and initializes
    133      them in a new thread."""
    134 -    def __init__(self, pipe_handler, pipe_name, message=False, **kwargs):
    135          threading.Thread.__init__(self)
    136          self.pipe_handler = pipe_handler
    137          self.pipe_name = pipe_name
    138          self.message = message
    139          self.kwargs = kwargs
    140          self.do_run = True
    141
    142 -    def run(self):
    143          while self.do_run:
    144              flags = FILE_FLAG_WRITE_THROUGH
    145              if self.message:
    146                  pipe_handle = KERNEL32.CreateNamedPipeA(
    147                      self.pipe_name, PIPE_ACCESS_DUPLEX | flags,
    148                      PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
    149                      PIPE_UNLIMITED_INSTANCES, BUFSIZE, BUFSIZE, 0, None)
    150              else:
    151                  pipe_handle = KERNEL32.CreateNamedPipeA(
    152                      self.pipe_name, PIPE_ACCESS_INBOUND | flags,
    153                      PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
    154                      PIPE_UNLIMITED_INSTANCES, 0, BUFSIZE, 0, None)
    155
    156              if pipe_handle == INVALID_HANDLE_VALUE:
    157                  log.warning("Error opening logging pipe server.")
    158                  continue
    159
    160              if KERNEL32.ConnectNamedPipe(pipe_handle, None) or \
    161                      KERNEL32.GetLastError() == ERROR_PIPE_CONNECTED:
    162                  handler = self.pipe_handler(pipe_handle, **self.kwargs)
    163                  handler.daemon = True
    164                  handler.start()
    165              else:
    166                  KERNEL32.CloseHandle(pipe_handle)
    167
    168 -    def stop(self):
    169          self.do_run = False
    170