# Conflicts:
#	chaos_monkey/config.py
#	chaos_monkey/network/info.py
This commit is contained in:
acepace 2016-07-18 23:45:02 +03:00
commit 5eb2379fa2
16 changed files with 313 additions and 100 deletions

View File

@ -33,7 +33,8 @@ The Monkey itself has been tested on Windows XP, 7, 8.1 and 10. The Linux build
### Installation
For off the shelf use, download our pre-compiled binaries from our website, to setup the C&C server follow the instructions in [Monkey Island readme](monkey_island/readme.txt). If you with to compile the binaries yourself, follow the build instructions in the appropiate [readme](build_env/readme.txt).
For off the shelf use, download our pre-compiled binaries from our website, to setup the C&C server follow the instructions in [Monkey Island readme](monkey_island/readme.txt). If you with to compile the binaries yourself, follow the build instructions later on in this readme.
Usage
-----
@ -148,7 +149,7 @@ Dependency | License |
----------------------------|----------------------------
libffi-dev | https://github.com/atgreen/libffi/blob/master/LICENSE
PyCrypto | Public domain
upx | Custom license, http://upx.sourceforge.net/upx-license.html, according to it (IANL) we're fine as long as we're not modifying UPX
upx | Custom license, http://upx.sourceforge.net/upx-license.html
bson | BSD
enum34 | BSD
pyasn1 | BSD

View File

@ -1,2 +1,2 @@
REM c:\Python27\python -m PyInstaller.main --name monkey -F -y --clean -i monkey.ico main.py
c:\python27\Scripts\pyinstaller --upx-dir=.\bin monkey.spec
c:\python27\Scripts\pyinstaller --log-level=DEBUG --clean --upx-dir=.\bin monkey.spec

View File

@ -112,13 +112,13 @@ class Configuration(object):
###########################
# monkey config
###########################
#sets whether or not the monkey is alive. if false will stop scanning and exploiting
# sets whether or not the monkey is alive. if false will stop scanning and exploiting
alive = True
#sets whether or not to self delete the monkey executable when stopped
# sets whether or not to self delete the monkey executable when stopped
self_delete_in_cleanup = False
#string of the mutex name for single instance
# string of the mutex name for single instance
singleton_mutex_name = "{2384ec59-0df8-4ab9-918c-843740924a28}"
# how long to wait between scan iterations
@ -146,7 +146,7 @@ class Configuration(object):
"127.0.0.1:5000"
]
#sets whether or not to retry failed hosts on next scan
# sets whether or not to retry failed hosts on next scan
retry_failed_explotation = True
#addresses of internet servers to ping and check if the monkey has internet acccess.
@ -156,6 +156,9 @@ class Configuration(object):
# scanners config
###########################
# Auto detect and scan local subnets
local_network_scan = True
range_class = FixedRange
range_size = 1
range_fixed = ["", ]

View File

@ -53,7 +53,8 @@ class ControlClient(object):
data=json.dumps(monkey),
headers={'content-type': 'application/json'},
verify=False,
proxies=ControlClient.proxies)
proxies=ControlClient.proxies,
timeout=20)
break
except Exception, exc:

View File

@ -68,6 +68,7 @@
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}",
"skip_exploit_if_file_exist": true,
"ssh_user": "root",
"local_network_scan": true,
"tcp_scan_get_banner": true,
"tcp_scan_interval": 200,
"tcp_scan_timeout": 10000,

View File

@ -3,46 +3,74 @@ import sys
import socket
import struct
import array
import ipaddress
from random import randint
__author__ = 'hoffer'
if sys.platform == "win32":
import netifaces
def local_ips():
local_hostname = socket.gethostname()
return socket.gethostbyname_ex(local_hostname)[2]
def get_host_subnets(only_ips=False):
network_adapters = []
valid_ips = local_ips()
if only_ips:
return valid_ips
interfaces = [netifaces.ifaddresses(x) for x in netifaces.interfaces()]
for inte in interfaces:
if netifaces.AF_INET in inte:
for add in inte[netifaces.AF_INET]:
if "netmask" in add and add["addr"] in valid_ips:
network_adapters.append((add["addr"], add["netmask"]))
return network_adapters
else:
import fcntl
def local_ips():
result = []
try:
is_64bits = sys.maxsize > 2**32
struct_size = 40 if is_64bits else 32
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
max_possible = 8 # initial value
while True:
bytes = max_possible * struct_size
names = array.array('B', '\0' * bytes)
outbytes = struct.unpack('iL', fcntl.ioctl(
s.fileno(),
0x8912, # SIOCGIFCONF
struct.pack('iL', bytes, names.buffer_info()[0])
))[0]
if outbytes == bytes:
max_possible *= 2
def get_host_subnets(only_ips=False):
"""Get the list of Linux network adapters."""
import fcntl
max_bytes = 8096
is_64bits = sys.maxsize > 2 ** 32
if is_64bits:
offset1 = 16
offset2 = 40
else:
break
namestr = names.tostring()
offset1 = 32
offset2 = 32
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
names = array.array('B', '\0' * max_bytes)
outbytes = struct.unpack('iL', fcntl.ioctl(
sock.fileno(),
0x8912,
struct.pack('iL', max_bytes, names.buffer_info()[0])))[0]
adapter_names = [names.tostring()[n_cnt:n_cnt + offset1].split('\0', 1)[0]
for n_cnt in xrange(0, outbytes, offset2)]
network_adapters = []
for adapter_name in adapter_names:
ip_address = socket.inet_ntoa(fcntl.ioctl(
sock.fileno(),
0x8915,
struct.pack('256s', adapter_name))[20:24])
if ip_address.startswith('127'):
continue
subnet_mask = socket.inet_ntoa(fcntl.ioctl(
sock.fileno(),
0x891b,
struct.pack('256s', adapter_name))[20:24])
for i in range(0, outbytes, struct_size):
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]
finally:
return result
if only_ips:
network_adapters.append(ip_address)
else:
network_adapters.append((ip_address, subnet_mask))
return network_adapters
def local_ips():
return get_host_subnets(only_ips=True)
def get_free_tcp_port(min_range=1000, max_range=65535):
@ -59,9 +87,25 @@ def get_free_tcp_port(min_range=1000, max_range=65535):
return None
def check_internet_access(services):
ping_str = "-n 1" if sys.platform.startswith("win") else "-c 1"
for host in services:
if os.system("ping " + ping_str + " " + host) == 0:
return True
return False
def get_ips_from_interfaces():
res = []
ifs = get_host_subnets()
for interface in ifs:
ipint = ipaddress.ip_interface(u"%s/%s" % interface)
# limit subnet scans to class C only
if ipint.network.num_addresses > 255:
ipint = ipaddress.ip_interface(u"%s/24" % interface[0])
for addr in ipint.network.hosts():
if str(addr) == interface[0]:
continue
res.append(str(addr))
return res

View File

@ -2,7 +2,7 @@ import time
import logging
from . import HostScanner
from config import WormConfiguration
from info import local_ips
from info import local_ips, get_ips_from_interfaces
from range import *
__author__ = 'itamar'
@ -27,10 +27,12 @@ class NetworkScanner(object):
LOG.info("Found local IP addresses of the machine: %r", self._ip_addresses)
# for fixed range, only scan once.
if WormConfiguration.range_class is FixedRange:
self._ranges = [WormConfiguration.range_class('0.0.0.0')]
self._ranges = [WormConfiguration.range_class(None)]
else:
self._ranges = [WormConfiguration.range_class(ip_address)
for ip_address in self._ip_addresses]
if WormConfiguration.local_network_scan:
self._ranges += [FixedRange([ip_address for ip_address in get_ips_from_interfaces()])]
LOG.info("Base local networks to scan are: %r", self._ranges)
def get_victim_machines(self, scan_type, max_find=5, stop_callback=None):

View File

@ -58,10 +58,16 @@ class RelativeRange(NetworkRange):
class FixedRange(NetworkRange):
def __init__(self, base_address, shuffle=True):
def __init__(self, fixed_addresses=None, shuffle=True):
base_address = 0
super(FixedRange, self).__init__(base_address, shuffle=shuffle)
if not fixed_addresses:
self._fixed_addresses = self._config.range_fixed
else:
if type(fixed_addresses) is str:
self._fixed_addresses = [fixed_addresses]
else:
self._fixed_addresses = list(fixed_addresses)
def __repr__(self):
return "<FixedRange %s>" % (",".join(self._fixed_addresses))

View File

@ -22,6 +22,7 @@ Windows:
python -m pip install odict
python -m pip install paramiko
python -m pip install psutil
python -m pip install netifaces
python -m pip install PyInstaller
type > C:\Python27\Lib\site-packages\zope\__init__.py
7. Download and extract UPX binary to [source-path]\monkey\chaos_monkey\bin\upx.exe:

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@ -10,6 +10,7 @@
<script type="text/javascript" src="./js/typeahead.bundle.min.js"></script>
<script type="text/javascript" src="./js/bootstrap.min.js"></script>
<script type="text/javascript" src="./js/bootstrap-switch.min.js"></script>
<script type="text/javascript" src="./js/sb-admin-2/sb-admin-2.js"></script>
<script type="text/javascript" src="./js/sb-admin-2/metisMenu.js"></script>
<script type="text/javascript" src="./js/jsoneditor.js"></script>
@ -23,6 +24,7 @@
<link type="text/css" href="./css/typeahead.css" rel="stylesheet"/>
<!-- <link type="text/css" href="./css/font-awesome.min.css" rel="stylesheet"/> -->
<link type="text/css" href="./css/bootstrap.min.css" rel="stylesheet"/>
<link type="text/css" href="./css/bootstrap-switch.min.css" rel="stylesheet"/>
<link type="text/css" href="./css/sb-admin-2/sb-admin-2.css" rel="stylesheet"/>
<link type="text/css" href="./css/sb-admin-2/metisMenu.css" rel="stylesheet"/>
<link type="text/css" href="./css/jquery.dataTables.min.css" rel="stylesheet"/>
@ -71,11 +73,33 @@
</div>
<!-- /.Network section -->
<!-- Info section -->
<div class="col-lg-3 col-md-6 col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<a href="#info" data-toggle="collapse">General Info</a>
</div>
<div id="info" class="panel-body panel-collapse collapse in">
<div>
Num of Monkeys: <label id="infoNumOfMonkeys">0</label> (<label id="infoNumOfParents">0</label> by exploiting)<br/>
Num of Hosts Detected: <label id="infoNumOfHosts">0</label><br/>
Num of Tunnels Used: <label id="infoNumOfTunnels">0</label><br/>
</div>
<div>
Display Scanned Hosts: <input type="checkbox" data-size="mini" name="chboxShowScanned" checked>
</div>
</div>
</div>
</div>
<!-- /.Info section -->
<!-- Details section -->
<div class="col-lg-3 col-md-6 col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<a href="#details" data-toggle="collapse">Details</a>
<a href="#details" data-toggle="collapse">Monkey Details</a>
</div>
<div id="details" class="panel-body panel-collapse collapse in">
<div id="search" class="input-group custom-search-form">
@ -99,6 +123,10 @@
<a href="#mconfig" data-toggle="collapse">Monkey Config</a>
</div>
<div id="mconfig" style="overflow: visible" class="panel-body panel-collapse collapse in" aria-expanded="true">
<div style="display: none;" id="monkey-enabled">
Allow running: <input type="checkbox" data-size="mini" name="chboxMonkeyEnabled" checked>
</div><br/>
<div>
<span class="input-group-btn">
<button id="btnConfigLoad" style="display: none;" class="btn btn-default" type="button"
onclick="loadMonkeyConfig()" style="margin-top:-4px">
@ -108,15 +136,8 @@
onclick="updateMonkeyConfig()" style="margin-top:-4px">
Update
</button>
<button id="btnKillMonkey" style="display: none;" class="btn btn-default" type="button"
onclick="killMonkey()" style="margin-top:-4px">
Mark for Kill
</button>
<button id="btnReviveMonkey" style="display: none;" class="btn btn-default" type="button"
onclick="reviveMonkey()" style="margin-top:-4px">
Revive Monkey
</button>
</span>
</div>
<div style="display: none;" id="monkey-config">
</div>
</div>

File diff suppressed because one or more lines are too long

View File

@ -21,15 +21,27 @@ $.getJSON(jsonFile, function(json) {
var network = null;
var nodes = [];
var edges = [];
var numOfParentLinks = 0;
var numOfTunnelLinks = 0;
var numOfScanLinks = 0;
var showScannedHosts = true;
// Images/icons constants
const ICONS_DIR = "./css/img/objects/";
const ICONS_EXT = ".png";
const HOST_TYPE_MONKEY = "monkey";
const HOST_TYPE_SCAN = "scanned";
const EDGE_TYPE_PARENT = "parent";
const EDGE_TYPE_TUNNEL = "tunnel";
const EDGE_TYPE_SCAN = "scan";
const EDGE_COLOR_PARENT = "red";
const EDGE_COLOR_TUNNEL = "blue";
const EDGE_COLOR_SCAN = "gray";
// General options
// If variable from local storage != null, assign it, otherwise set it's default value.
@ -59,6 +71,8 @@ function initAdmin() {
edges: edges
};
updateCounters();
var options = {
};
@ -67,6 +81,9 @@ function initAdmin() {
network = new vis.Network(container, data, options);
$("[name='chboxShowScanned']").bootstrapSwitch('onSwitchChange', toggleScannedHosts);
$("[name='chboxMonkeyEnabled']").bootstrapSwitch('onSwitchChange', toggleMonkeyEnabled);
prepareSearchEngine();
monkeyCfg = new JSONEditor(document.getElementById('monkey-config'),{
@ -115,6 +132,55 @@ function initAdmin() {
addEventsListeners();
}
function toggleScannedHosts(event, state) {
if (event.type != "switchChange") {
return;
}
if (state) {
showScannedHosts = true;
}
else {
showScannedHosts = false;
}
refreshDrawing();
}
function refreshDrawing() {
// function called before first init
if (network == null) {
return;
}
// keep old selection
var selNode = network.getSelectedNodes();
if (showScannedHosts) {
network.setData({nodes: nodes, edges: edges});
}
else {
var selectiveNodes = [];
var selectiveEdges = [];
for (var i=0; i<nodes.length; i++) {
if (nodes[i].type != HOST_TYPE_SCAN) {
selectiveNodes.push(nodes[i])
}
}
for (var i=0; i<edges.length; i++) {
if (edges[i].type != EDGE_TYPE_SCAN) {
selectiveEdges.push(edges[i])
}
}
network.setData({nodes: selectiveNodes, edges: selectiveEdges});
}
if (selNode.length) {
var monkey = getMonkey(selNode[0]);
if (monkey) { // The selection might be no longer valid if the monkey was deleted
selectNode(monkey.hostname, false);
}
}
}
function updateMonkeys() {
$.getJSON(jsonFile + '?timestamp='+ generationDate, function(json) {
generationDate = json.timestamp;
@ -128,6 +194,7 @@ function updateMonkeys() {
{
monkeys.push(new_monkeys[i]);
nodes.push(createMonkeyNode(new_monkeys[i]));
updateCounters();
}
}
@ -135,16 +202,7 @@ function updateMonkeys() {
{
createEdges();
createTunnels();
// keep old selection
var selNode = network.getSelectedNodes();
network.setData({nodes: nodes, edges: edges});
if (selNode.length) {
var monkey = getMonkey(selNode[0]);
if (monkey) { // The selection might be no longer valid if the monkey was deleted
selectNode(monkey.hostname, false);
}
}
refreshDrawing();
}
createScanned();
window.setTimeout(updateMonkeys, 10000);
@ -163,7 +221,6 @@ function createNodes() {
return nodes;
}
function createMonkeyNode(monkey) {
var title = undefined;
var img = "monkey";
@ -187,12 +244,24 @@ function createMonkeyNode(monkey) {
'image': img,
'title': title,
'value': undefined,
'type' : HOST_TYPE_MONKEY,
'mass': 1,
};
}
function createMachineNode(machine) {
img = ICONS_DIR + "computer" + ICONS_EXT;
img = "computer";
if (undefined != machine.os.type) {
if (machine.os.type == "linux") {
img += "-linux";
}
else if (machine.os.type == "windows") {
img += "-windows";
}
}
img = ICONS_DIR + img + ICONS_EXT;
return {
'id': machine.ip_addr,
@ -202,6 +271,7 @@ function createMachineNode(machine) {
'image': img,
'title': undefined,
'value': undefined,
'type' : HOST_TYPE_SCAN,
'mass': 1,
};
}
@ -213,7 +283,8 @@ function createEdges() {
var parent = getMonkeyByGuid(monkey.parent);
if(parent && !edgeExists([parent.id, monkey.id, EDGE_TYPE_PARENT])) {
edges.push({from: parent.id, to: monkey.id, arrows:'middle', type: EDGE_TYPE_PARENT, color: 'red'});
edges.push({from: parent.id, to: monkey.id, arrows:'middle', type: EDGE_TYPE_PARENT, color: EDGE_COLOR_PARENT});
numOfParentLinks++;
}
}
}
@ -228,7 +299,8 @@ function createTunnels() {
var tunnel = getMonkeyByGuid(monkey.tunnel_guid);
if(tunnel && !edgeExists([monkey.id, tunnel.id, EDGE_TYPE_TUNNEL])) {
edges.push({from: monkey.id, to: tunnel.id, arrows:'middle', type: EDGE_TYPE_TUNNEL, color:'blue'});
edges.push({from: monkey.id, to: tunnel.id, arrows:'middle', type: EDGE_TYPE_TUNNEL, color: EDGE_COLOR_TUNNEL});
numOfTunnelLinks++;
}
}
}
@ -237,13 +309,14 @@ function createTunnels() {
}
function createScanned() {
//For each existing monkey, gets all the scans performed by it
//For each non exploited machine, adds a new node and connects it as a scanned node.
var genTime = temelGenerationDate; // save the initial value as it's going to be changed in each json call
// For each existing monkey, gets all the scans performed by it
// For each non exploited machine, adds a new node and connects it as a scanned node.
for (var i = 0; i < monkeys.length; i++) {
var monkey = monkeys[i];
//Get scans for each monkey
// Get scans for each monkey
// Reading the JSON file containing the monkeys' informations
$.getJSON(jsonFileTelemetry +'?timestamp='+ temelGenerationDate+ "&monkey_guid=" + monkey.guid+"&telem_type=scan", function(json) {
$.getJSON(jsonFileTelemetry +'?timestamp='+ genTime + "&monkey_guid=" + monkey.guid+"&telem_type=scan", function(json) {
temelGenerationDate = json.timestamp;
var scans = json.objects;
for (var i = 0; i < scans.length; i++) {
@ -265,9 +338,14 @@ function createScanned() {
}
if(!edgeExists([monkey.id, machineNode.id, EDGE_TYPE_SCAN])) {
edges.push({from: monkey.id, to: machineNode.id, arrows:'middle', type: EDGE_TYPE_SCAN, color: 'red'});
edges.push({from: monkey.id, to: machineNode.id, arrows:'middle', type: EDGE_TYPE_SCAN, color: EDGE_COLOR_SCAN});
numOfScanLinks++;
}
}
if (scans.length > 0) {
refreshDrawing();
updateCounters();
}
});
}
}
@ -297,6 +375,13 @@ function buildMonkeyDescription(monkey) {
return html;
}
function updateCounters() {
$('#infoNumOfMonkeys').html(monkeys.length);
$('#infoNumOfHosts').html(scannedMachines.length);
$('#infoNumOfParents').html(numOfParentLinks);
$('#infoNumOfTunnels').html(numOfTunnelLinks);
}
/**
* Preparing the autocompletion search engine for the monkeys
@ -370,7 +455,8 @@ function onSelect(properties) {
var content = "<b>No selection</b>"
$("#selectionInfo").html(content);
$('#monkey-config').hide()
$('#btnConfigLoad, #btnConfigUpdate, #btnKillMonkey, #btnReviveMonkey').hide();
$('#btnConfigLoad, #btnConfigUpdate').hide();
$('#monkey-enabled').hide();
telemTable.clear();
telemTable.draw();
}
@ -403,14 +489,12 @@ function onNodeSelect(nodeId) {
loadMonkeyConfig();
if (monkey.config.alive) {
$('#btnKillMonkey').show();
$('#btnReviveMonkey').hide();
$("[name='chboxMonkeyEnabled']").bootstrapSwitch('state', true);
}
else {
$('#btnKillMonkey').hide();
$('#btnReviveMonkey').show();
$("[name='chboxMonkeyEnabled']").bootstrapSwitch('state', false);
}
$('#monkey-enabled').show();
$.getJSON('/api/telemetry/' + monkey.guid, function(json) {
telemTable.clear();
@ -434,6 +518,19 @@ function onEdgeSelect(edge) {
}
function toggleMonkeyEnabled(event, state) {
if (event.type != "switchChange") {
return;
}
if (state) {
reviveMonkey();
}
else {
killMonkey();
}
}
function killMonkey() {
var curr_config = monkeyCfg.getValue();
curr_config.alive = false;

View File

@ -11,27 +11,19 @@ How to set C&C server:
python -m pip install Flask-Pymongo
python -m pip install Flask-Restful
python -m pip install python-dateutil
mkdir C:\MonkeyIsland\bin
mkdir C:\MonkeyIsland\db
mkdir C:\MonkeyIsland\cc\binaries
4. Put monkey binaries in C:\MonkeyIsland\cc\binaries:
mkdir MonkeyIsland\bin
mkdir MonkeyIsland\db
mkdir MonkeyIsland\cc\binaries
4. Put monkey binaries in MonkeyIsland\cc\binaries:
monkey-linux-64 - monkey binary for linux 64bit
monkey-linux-32 - monkey binary for linux 32bit
monkey-windows-32.exe - monkey binary for windows 32bit
monkey-windows-64.exe - monkey binary for windows 64bit
4. Download MongoDB & Extract to C:\MonkeyIsland\bin\mongodb
4. Download MongoDB & Extract to MonkeyIsland\bin\mongodb
http://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-latest.zip
5. Install OpenSSL
https://slproweb.com/download/Win64OpenSSL_Light-1_0_2d.exe
6. Generate SSL Certificate, Run create_certificate.bat
How to Connect to build environment:
1. set hostname to MONKEYCC
2. Put monkey source code at C:\Code\monkey
3. Run:
net share binaries=C:\MonkeyIsland\cc\binaries
net share sources=C:\Code\monkey\chaos_monkey
4. Run batch/sh script according to build environment readme
6. Generate SSL Certificate, run create_certificate.bat when your current working directory is MonkeyIsland
How to run:
1. start run_mongodb.bat