diff --git a/lib/core/common.py b/lib/core/common.py index 62292b370..7607b645b 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -3309,3 +3309,21 @@ def isNumber(value): return False else: return True + +def pollProcess(process, suppress_errors=False): + while True: + dataToStdout(".") + time.sleep(1) + + returncode = process.poll() + + if returncode is not None: + if not suppress_errors: + if returncode == 0: + dataToStdout(" done\n") + elif returncode < 0: + dataToStdout(" process terminated by signal %d\n" % returncode) + elif returncode > 0: + dataToStdout(" quit unexpectedly with return code %d\n" % returncode) + + break diff --git a/lib/core/subprocessng.py b/lib/core/subprocessng.py index efd3cf34b..9db2af0bd 100644 --- a/lib/core/subprocessng.py +++ b/lib/core/subprocessng.py @@ -7,13 +7,19 @@ See the file 'doc/COPYING' for copying permission import errno import os +import subprocess import sys import time from lib.core.common import dataToStdout from lib.core.settings import IS_WIN -if not IS_WIN: +if IS_WIN: + from win32file import ReadFile, WriteFile + from win32pipe import PeekNamedPipe + import msvcrt +else: + import select import fcntl if (sys.hexversion >> 16) >= 0x202: @@ -61,30 +67,131 @@ def blockingWriteToFD(fd, data): break -def setNonBlocking(fd): - """ - Make a file descriptor non-blocking - """ +# the following code is taken from http://code.activestate.com/recipes/440554-module-to-allow-asynchronous-subprocess-use-on-win/ +class Popen(subprocess.Popen): + def recv(self, maxsize=None): + return self._recv('stdout', maxsize) - if IS_WIN is not True: - flags = fcntl.fcntl(fd, FCNTL.F_GETFL) - flags = flags | os.O_NONBLOCK - fcntl.fcntl(fd, FCNTL.F_SETFL, flags) + def recv_err(self, maxsize=None): + return self._recv('stderr', maxsize) -def pollProcess(process, suppress_errors=False): - while True: - dataToStdout(".") - time.sleep(1) + def send_recv(self, input='', maxsize=None): + return self.send(input), self.recv(maxsize), self.recv_err(maxsize) - returncode = process.poll() + def get_conn_maxsize(self, which, maxsize): + if maxsize is None: + maxsize = 1024 + elif maxsize < 1: + maxsize = 1 + return getattr(self, which), maxsize - if returncode is not None: - if not suppress_errors: - if returncode == 0: - dataToStdout(" done\n") - elif returncode < 0: - dataToStdout(" process terminated by signal %d\n" % returncode) - elif returncode > 0: - dataToStdout(" quit unexpectedly with return code %d\n" % returncode) + def _close(self, which): + getattr(self, which).close() + setattr(self, which, None) + if subprocess.mswindows: + def send(self, input): + if not self.stdin: + return None + + try: + x = msvcrt.get_osfhandle(self.stdin.fileno()) + (errCode, written) = WriteFile(x, input) + except ValueError: + return self._close('stdin') + except (subprocess.pywintypes.error, Exception), why: + if why[0] in (109, errno.ESHUTDOWN): + return self._close('stdin') + raise + + return written + + def _recv(self, which, maxsize): + conn, maxsize = self.get_conn_maxsize(which, maxsize) + if conn is None: + return None + + try: + x = msvcrt.get_osfhandle(conn.fileno()) + (read, nAvail, nMessage) = PeekNamedPipe(x, 0) + if maxsize < nAvail: + nAvail = maxsize + if nAvail > 0: + (errCode, read) = ReadFile(x, nAvail, None) + except ValueError: + return self._close(which) + except (subprocess.pywintypes.error, Exception), why: + if why[0] in (109, errno.ESHUTDOWN): + return self._close(which) + raise + + if self.universal_newlines: + read = self._translate_newlines(read) + return read + else: + def send(self, input): + if not self.stdin: + return None + + if not select.select([], [self.stdin], [], 0)[1]: + return 0 + + try: + written = os.write(self.stdin.fileno(), input) + except OSError, why: + if why[0] == errno.EPIPE: #broken pipe + return self._close('stdin') + raise + + return written + + def _recv(self, which, maxsize): + conn, maxsize = self.get_conn_maxsize(which, maxsize) + if conn is None: + return None + + flags = fcntl.fcntl(conn, fcntl.F_GETFL) + if not conn.closed: + fcntl.fcntl(conn, fcntl.F_SETFL, flags| os.O_NONBLOCK) + + try: + if not select.select([conn], [], [], 0)[0]: + return '' + + r = conn.read(maxsize) + if not r: + return self._close(which) + + if self.universal_newlines: + r = self._translate_newlines(r) + return r + finally: + if not conn.closed: + fcntl.fcntl(conn, fcntl.F_SETFL, flags) + +def recv_some(p, t=.1, e=1, tr=5, stderr=0): + if tr < 1: + tr = 1 + x = time.time()+t + y = [] + r = '' + pr = p.recv + if stderr: + pr = p.recv_err + while time.time() < x or r: + r = pr() + if r is None: break + elif r: + y.append(r) + else: + time.sleep(max((x-time.time())/tr, 0)) + return ''.join(y) + +def send_all(p, data): + if not data: + return + + while len(data): + sent = p.send(data) + data = buffer(data, sent) diff --git a/lib/core/update.py b/lib/core/update.py index f00eba8ef..b04dd7975 100644 --- a/lib/core/update.py +++ b/lib/core/update.py @@ -13,13 +13,13 @@ from subprocess import PIPE from subprocess import Popen as execute from lib.core.common import dataToStdout +from lib.core.common import pollProcess from lib.core.data import conf from lib.core.data import logger from lib.core.data import paths from lib.core.revision import getRevisionNumber from lib.core.settings import GIT_REPOSITORY from lib.core.settings import IS_WIN -from lib.core.subprocessng import pollProcess def update(): if not conf.updateAll: diff --git a/lib/takeover/metasploit.py b/lib/takeover/metasploit.py index acfbea8ac..bb1ba64d2 100644 --- a/lib/takeover/metasploit.py +++ b/lib/takeover/metasploit.py @@ -10,9 +10,7 @@ import re import sys import time -from select import select from subprocess import PIPE -from subprocess import Popen as execute from lib.core.common import dataToStdout from lib.core.common import Backend @@ -21,6 +19,7 @@ from lib.core.common import getRemoteIP from lib.core.common import getUnicode from lib.core.common import normalizePath from lib.core.common import ntToPosixSlashes +from lib.core.common import pollProcess from lib.core.common import randomRange from lib.core.common import randomStr from lib.core.common import readInput @@ -35,9 +34,14 @@ from lib.core.settings import IS_WIN from lib.core.settings import UNICODE_ENCODING from lib.core.subprocessng import blockingReadFromFD from lib.core.subprocessng import blockingWriteToFD -from lib.core.subprocessng import pollProcess -from lib.core.subprocessng import setNonBlocking +from lib.core.subprocessng import Popen as execute +from lib.core.subprocessng import send_all +from lib.core.subprocessng import recv_some +if IS_WIN: + import msvcrt +else: + from select import select class Metasploit: """ @@ -410,14 +414,14 @@ class Metasploit: if not Backend.isOs(OS.WINDOWS): return - proc.stdin.write("use espia\n") - proc.stdin.write("use incognito\n") + send_all(proc, "use espia\n") + send_all(proc, "use incognito\n") # This extension is loaded by default since Metasploit > 3.7 - #proc.stdin.write("use priv\n") + #send_all(proc, "use priv\n") # This extension freezes the connection on 64-bit systems - #proc.stdin.write("use sniffer\n") - proc.stdin.write("sysinfo\n") - proc.stdin.write("getuid\n") + #send_all(proc, "use sniffer\n") + send_all(proc, "sysinfo\n") + send_all(proc, "getuid\n") if conf.privEsc: print @@ -427,7 +431,7 @@ class Metasploit: infoMsg += "techniques, including kitrap0d" logger.info(infoMsg) - proc.stdin.write("getsystem\n") + send_all(proc, "getsystem\n") infoMsg = "displaying the list of Access Tokens availables. " infoMsg += "Choose which user you want to impersonate by " @@ -435,15 +439,11 @@ class Metasploit: infoMsg += "'getsystem' does not success to elevate privileges" logger.info(infoMsg) - proc.stdin.write("list_tokens -u\n") - proc.stdin.write("getuid\n") + send_all(proc, "list_tokens -u\n") + send_all(proc, "getuid\n") def _controlMsfCmd(self, proc, func): stdin_fd = sys.stdin.fileno() - setNonBlocking(stdin_fd) - - proc_out_fd = proc.stdout.fileno() - setNonBlocking(proc_out_fd) while True: returncode = proc.poll() @@ -456,39 +456,63 @@ class Metasploit: return returncode try: - ready_fds = select([stdin_fd, proc_out_fd], [], [], 1) + if IS_WIN: + timeout = 3 - if stdin_fd in ready_fds[0]: - try: - proc.stdin.write(blockingReadFromFD(stdin_fd)) - except IOError: - # Probably the child has exited - pass + inp = "" + start_time = time.time() - if proc_out_fd in ready_fds[0]: - out = blockingReadFromFD(proc_out_fd) - blockingWriteToFD(sys.stdout.fileno(), out) + while True: + if msvcrt.kbhit(): + char = msvcrt.getche() - # For --os-pwn and --os-bof - pwnBofCond = self.connectionStr.startswith("reverse") - pwnBofCond &= "Starting the payload handler" in out + if ord(char) == 13: # enter_key + break + elif ord(char) >= 32: # space_char + inp += char - # For --os-smbrelay - smbRelayCond = "Server started" in out + if len(inp) == 0 and (time.time() - start_time) > timeout: + break - if pwnBofCond or smbRelayCond: - func() + if len(inp) > 0: + try: + send_all(proc, inp) + except IOError: + # Probably the child has exited + pass + else: + ready_fds = select([stdin_fd], [], [], 1) - if "Starting the payload handler" in out and "shell" in self.payloadStr: - if Backend.isOs(OS.WINDOWS): - proc.stdin.write("whoami\n") - else: - proc.stdin.write("uname -a ; id\n") + if stdin_fd in ready_fds[0]: + try: + send_all(proc, blockingReadFromFD(stdin_fd)) + except IOError: + # Probably the child has exited + pass - metSess = re.search("Meterpreter session ([\d]+) opened", out) + out = recv_some(proc, t=.1, e=0) + blockingWriteToFD(sys.stdout.fileno(), out) - if metSess: - self._loadMetExtensions(proc, metSess.group(1)) + # For --os-pwn and --os-bof + pwnBofCond = self.connectionStr.startswith("reverse") + pwnBofCond &= "Starting the payload handler" in out + + # For --os-smbrelay + smbRelayCond = "Server started" in out + + if pwnBofCond or smbRelayCond: + func() + + if "Starting the payload handler" in out and "shell" in self.payloadStr: + if Backend.isOs(OS.WINDOWS): + send_all(proc, "whoami\n") + else: + send_all(proc, "uname -a ; id\n") + + metSess = re.search("Meterpreter session ([\d]+) opened", out) + + if metSess: + self._loadMetExtensions(proc, metSess.group(1)) except EOFError: returncode = proc.wait()