Skip to content

Commit

Permalink
rebase (#32)
Browse files Browse the repository at this point in the history
* Break out port and ip

* Roughly handle mushorg#399 (not very PEP-8)

* Some PEP-8 adjustments as suggested in mushorg#402

* Adds random generated functions for function pointers in template.xml to support non static register values.

* fix tftp test

* Fixes crypto quality issues and extra space.

* Fix typo.

* Fix s7 parameter length calculation

* Docker fixes (mushorg#416)

* updated Dockerfile

* cleanup

* image size reduced

* multi stage build to reduce image size

* moved to alpine

* Fixed wrong Docker ports

* fix mushorg#418

add missing dependency libffi-dev

* Makefile for docker added

* Fixed exception loop in the FTP handler

* Changing exception (mushorg#425)

* updated dockerfile (mushorg#429) - related (mushorg#428)

With the recent update to pip 19.01 there was a build slight issue with the docker commands, refer here:
pypa/pip#6197

* Revert "docker/dockerfile:Updated dockerfile" (mushorg#432)

* Revert "updated dockerfile (mushorg#429) - related (mushorg#428)"
This reverts commit fe65c16.
* fix docker

* Added support for plc-stop signal. (mushorg#447)

* Added support for plc-stop signal.

* Added support for plc-stop signal and added it's tests.

* Added support for plc-stop signal and added it's tests.
  • Loading branch information
xandfury committed Jul 30, 2019
1 parent d008ece commit 53f75f2
Show file tree
Hide file tree
Showing 13 changed files with 174 additions and 82 deletions.
38 changes: 0 additions & 38 deletions Dockerfile

This file was deleted.

6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.PHONY: docker
build-docker:
docker build -t conpot docker/

run-docker: build-docker
docker run -it -p 80:8800 -p 102:10201 -p 502:5020 -p 161:16100/udp -p 47808:47808/udp -p 623:6230/udp -p 21:2121 -p 69:6969/udp -p 44818:44818 --network=bridge conpot
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ The build of the documentations [source](https://github.com/mushorg/conpot/tree/

## Easy install using Docker

![Docker Build Status](https://img.shields.io/docker/build/honeynet/conpot.svg)
![Docket Image Size](https://img.shields.io/microbadger/image-size/honeynet/conpot.svg?style=flat-square)
![Docker Pulls](https://img.shields.io/docker/pulls/honeynet/conpot.svg?style=flat-square)
[![Docker Build Status](https://img.shields.io/docker/build/honeynet/conpot.svg)](https://hub.docker.com/r/honeynet/conpot)
[![Docket Image Size](https://img.shields.io/microbadger/image-size/honeynet/conpot.svg)](https://hub.docker.com/r/honeynet/conpot)
[![Docker Pulls](https://img.shields.io/docker/pulls/honeynet/conpot.svg)](https://hub.docker.com/r/honeynet/conpot)

#### Via a pre-built image

Expand All @@ -34,16 +34,16 @@ Navigate to ``http://MY_IP_ADDRESS`` to confirm the setup.
#### Build docker image from source

1. Install [Docker](https://docs.docker.com/engine/installation/)
2. Clone this repo with `git clone https://github.com/mushorg/conpot.git` and `cd conpot`
2. Clone this repo with `git clone https://github.com/mushorg/conpot.git` and `cd conpot/docker`
3. Run `docker build -t conpot .`
4. Run `docker run -it -p 80:80 -p 102:102 -p 502:502 -p 161:161/udp --network=bridge conpot`
4. Run `docker run -it -p 80:8800 -p 102:10201 -p 502:5020 -p 161:16100/udp -p 47808:47808/udp -p 623:6230/udp -p 21:2121 -p 69:6969/udp -p 44818:44818 --network=bridge conpot`

Navigate to `http://MY_IP_ADDRESS` to confirm the setup.

#### Build from source and run with docker-compose

1. Install [docker-compose](https://docs.docker.com/compose/install/)
2. Clone this repo with `git clone https://github.com/mushorg/conpot.git` and `cd conpot`
2. Clone this repo with `git clone https://github.com/mushorg/conpot.git` and `cd conpot/docker`
3. Build the image with `docker-compose build`
4. Test if everything is running correctly with `docker-compose up`
5. Permanently run as a daemon with `docker-compose up -d`
Expand Down
40 changes: 20 additions & 20 deletions bin/conpot
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def logo():


def on_unhandled_greenlet_exception(dead_greenlet):
logger.error('Stopping because %s died: %s', dead_greenlet, dead_greenlet.exception)
logger.exception('Stopping because {} died: {}'.format(dead_greenlet, dead_greenlet.exception))
sys.exit(1)


Expand Down Expand Up @@ -103,8 +103,8 @@ def drop_privileges(uid_name=None, gid_name=None):
wanted_user = pwd.getpwnam(uid_name)
except KeyError:
logger.exception(
'Cannot drop privileges: user "%s" does not exist.',
uid_name)
'Cannot drop privileges: user "{}" does not exist.'.
format(uid_name))
sys.exit(1)

if gid_name is None:
Expand All @@ -114,18 +114,18 @@ def drop_privileges(uid_name=None, gid_name=None):
wanted_group = grp.getgrnam(gid_name)
except KeyError:
logger.exception(
'Cannot drop privileges: group "%s" does not exist.',
gid_name)
'Cannot drop privileges: group "{}" does not exist.'.
format(gid_name))
sys.exit(1)

logger.debug('Attempting to drop privileges to "%s:%s"',
wanted_user.pw_name, wanted_group.gr_name)
logger.debug('Attempting to drop privileges to "{}:{}"'.
format(wanted_user.pw_name, wanted_group.gr_name))
os.setgid(wanted_group.gr_gid)
os.setuid(wanted_user.pw_uid)
new_user = pwd.getpwuid(os.getuid())
new_group = grp.getgrgid(os.getgid())
logger.info('Privileges dropped, running as "%s:%s"',
new_user.pw_name, new_group.gr_name)
logger.info('Privileges dropped, running as "{}:{}"'.
format(new_user.pw_name, new_group.gr_name))


def validate_template(xml_file, xsd_file):
Expand All @@ -134,7 +134,7 @@ def validate_template(xml_file, xsd_file):
xml = etree.parse(xml_file)
xsd.validate(xml)
if xsd.error_log:
logger.error('Error parsing XML template: %s', xsd.error_log)
logger.exception('Error parsing XML template: {}'.format(xsd.error_log))
sys.exit(1)


Expand Down Expand Up @@ -280,16 +280,16 @@ def main():
elif os.path.isfile(os.path.join(package_directory, 'templates', args.template, 'template.xml')):
root_template_directory = os.path.join(package_directory, 'templates', args.template)
else:
logger.error('Template not found: %s', args.template)
logger.exception('Template not found: {}'.format(args.template))
sys.exit(1)

# Check if the configuration file exists..
if not os.path.isfile(args.config):
logger.error('Config not found: %s', args.config)
logger.exception('Config not found: {}'.format(args.config))
sys.exit(1)

logger.info('Starting Conpot using template: %s', root_template_directory)
logger.info('Starting Conpot using configuration found in: %s', args.config)
logger.info('Starting Conpot using template: {}'.format(root_template_directory))
logger.info('Starting Conpot using configuration found in: {}'.format(args.config))

servers = list()

Expand All @@ -298,7 +298,7 @@ def main():
validate_template(template_base, os.path.join(package_directory, 'tests/template_schemas/core.xsd'))
dom_base = etree.parse(template_base)
else:
logger.error('Could not access template configuration')
logger.exception('Could not access template configuration')
sys.exit(1)

session_manager = conpot_core.get_sessionManager()
Expand All @@ -316,7 +316,7 @@ def main():
os.mkdir(temp_dir)
if fs_url == 'default' or data_fs_url == 'default':
if not args.force:
logger.error('Can\'t start conpot with default file system')
logger.exception('Can\'t start conpot with default file system')
sys.exit(3)
else:
fs_url, data_fs_url = None, None
Expand Down Expand Up @@ -375,18 +375,18 @@ def main():
if 'testing.cfg' in args.config:
if '127.' not in host:
if not args.force:
logger.error('To run conpot on a non local interface, please specify -f option')
logger.exception('To run conpot on a non local interface, please specify -f option')
sys.exit(1)
port = ast.literal_eval(dom_protocol.xpath('//{0}/@port'.format(protocol_name))[0])
server = server_class(protocol_template, root_template_directory, args)
greenlet = gevent.spawn(server.start, host, port)
greenlet.link_exception(on_unhandled_greenlet_exception)
servers.append(server)
logger.info('Found and enabled %s protocol.', (protocol[0], server))
logger.info('Found and enabled {} protocol.'.format(protocol[0], server))
else:
logger.info('%s available but disabled by configuration.', protocol_name)
logger.info('{} available but disabled by configuration.'.format(protocol_name))
else:
logger.debug('No %s template found. Service will remain unconfigured/stopped.', protocol_name)
logger.debug('No {} template found. Service will remain unconfigured/stopped.', protocol_name)

log_worker = LogWorker(config, dom_base, session_manager, public_ip)
greenlet = gevent.spawn(log_worker.start)
Expand Down
34 changes: 34 additions & 0 deletions conpot/emulators/misc/random.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright (C) 2018 Billy Ferguson <wferguson@blueprintpower.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

import random

class Random8BitRegisters:
def __init__(self):
self.key_num = random.SystemRandom()

def get_value(self):
values = [self.key_num.randint(0,1) for b in range (0, 8)]
return values


class Random16bitRegister:
def __init__(self):
self.key_num = random.SystemRandom()

def get_value(self):
return [self.key_num.randint(0,1)]
7 changes: 6 additions & 1 deletion conpot/protocols/ftp/ftp_base_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def __init__(self):
self.data_channel_bytes_send = 0
self.command_chanel_bytes_send = 0
self.command_chanel_bytes_recv = 0
self.last_active = None
self.last_active = self.start_time
# basically we need a timeout time composite of timeout for
# data_sock and client_sock. Let us say that timeout of 5 sec for data_sock
# and command_sock is 300.
Expand Down Expand Up @@ -245,6 +245,11 @@ def handle_cmd_channel(self):
"""Read data from the socket and add it to the _command_channel_input_q for processing"""
log_data = dict()
try:
if self.client_sock.closed:
logger.info('FTP socket is closed, connection lost. Remote: {} ({}).'.format(self.client_address, self.session.id))
self.session.add_event({'type': 'CONNECTION_LOST'})
self.finish()
return
socket_read, socket_write, _ = gevent.select.select([self.client_sock], [self.client_sock], [], 1)
# make sure the socket is ready to read - we would read from the command channel.
if self.client_sock in socket_read:
Expand Down
14 changes: 12 additions & 2 deletions conpot/protocols/s7comm/s7.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import conpot.core as conpot_core
from conpot.helpers import str_to_bytes
from conpot.protocols.s7comm.exceptions import AssembleException, ParseException
import logging
logger = logging.getLogger(__name__)


# S7 packet
Expand Down Expand Up @@ -39,7 +41,8 @@ def __init__(self, pdu_type=0, reserved=0, request_id=0, result_info=0, paramete
0x1d: ('start_upload', self.request_not_implemented),
0x1e: ('upload', self.request_not_implemented),
0x1f: ('end_upload', self.request_not_implemented),
0x28: ('insert_block', self.request_not_implemented)}
0x28: ('insert_block', self.request_not_implemented),
0x29: ('plc_stop',self.plc_stop_signal)}

# maps valid pdu codes to name
self.pdu_mapping = {0x01: set('request_pdu'),
Expand All @@ -56,8 +59,10 @@ def __len__(self):
else:
return 10 + int(self.param_length) + int(self.data_length)

def handle(self):
def handle(self, current_client=None):
if self.param in self.param_mapping:
if self.param == 0x29:
return self.param_mapping[self.param][1](current_client)
# direct execution to the correct method based on the param
return self.param_mapping[self.param][1]()

Expand Down Expand Up @@ -116,6 +121,11 @@ def parse(self, packet):
return self

# SSL/SZL System Status List/Systemzustandsliste
def plc_stop_signal(self, current_client):
# This function gets executed after plc stop signal is received the function stops the server for a while and then restarts it
logger.info("Stop signal recieved from {}".format(current_client))
return str_to_bytes('0x00'), str_to_bytes('0x29')

def request_diagnostics(self):

# semi-check
Expand Down
2 changes: 1 addition & 1 deletion conpot/protocols/s7comm/s7_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def handle(self, sock, address):
S7_packet.param_length, S7_packet.data_length,
S7_packet.result_info, session.id)

response_param, response_data = S7_packet.handle()
response_param, response_data = S7_packet.handle(address[0])
s7_resp_ssl_packet = S7(7, 0, S7_packet.request_id, 0, response_param,
response_data).pack()
cotp_resp_ssl_packet = COTP_BASE_packet(0xf0, 0x80, s7_resp_ssl_packet).pack()
Expand Down
17 changes: 17 additions & 0 deletions conpot/tests/helpers/s7comm_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,23 @@ def Function(self, _type, group, function, data=''):
raise S7Error(code)
return response.data[4:]

def plc_stop_function(self):
pdu_type = 1
request_id = 256
stop_func_parameter = struct.pack('!B5x10p',
0x29, # function code
str_to_bytes('P_PROGRAM') # Function Name
)
s7packet = S7Packet(pdu_type,request_id,stop_func_parameter).pack()
cotp_packet = COTPDataPacket(s7packet).pack()
tpkt_packet = TPKTPacket(cotp_packet).pack()
self.s.send(tpkt_packet)
reply = self.s.recv(1024)
if reply:
return S7Packet().unpack(COTPDataPacket().unpack(TPKTPacket().unpack(reply).data).data).data
else:
return None

def ReadSZL(self, szl_id):
szl_data = self.Function(
0x04, # request
Expand Down
1 change: 1 addition & 0 deletions conpot/tests/test_s7_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def test_s7(self):
s7_con.s.connect((s7_con.ip, s7_con.port))
s7_con.Connect()
identities = s7comm_client.GetIdentity('127.0.0.1', self.server_port, res[0], res[1])
s7_con.plc_stop_function()

dic = {
17: {1: "v.0.0"},
Expand Down
14 changes: 0 additions & 14 deletions docker-compose.yml

This file was deleted.

Loading

0 comments on commit 53f75f2

Please sign in to comment.