Skip to content

Commit 6867435

Browse files
authored
Merge pull request #23 from afmurillo/dev-concealment-mitm
Dev concealment mitm
2 parents 219c203 + e36a1ba commit 6867435

File tree

6 files changed

+97
-30
lines changed

6 files changed

+97
-30
lines changed

dhalsim/network_attacks/concealment_netfilter_queue.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import os
66
import sys
77

8+
import pandas as pd
9+
810
from scapy.layers.inet import IP, TCP
911
from scapy.packet import Raw
1012

@@ -19,6 +21,8 @@ def __init__(self, intermediate_yaml_path: Path, yaml_index: int, queue_number:
1921
self.scada_session_ids = []
2022
self.attack_session_ids = []
2123

24+
self.concealment_data_pd = pd.read_csv(self.intermediate_attack['concealment_data'])
25+
2226
def capture(self, packet):
2327
"""
2428
This function is the function that will run in the thread started in the setup function.
@@ -34,26 +38,17 @@ def capture(self, packet):
3438
if len(p) == 116:
3539
this_session = int.from_bytes(p[Raw].load[4:8], sys.byteorder)
3640
tag_name = p[Raw].load.decode(encoding='latin-1')[54:56]
37-
self.logger.debug('ENIP TCP Session ID: ' + str(this_session))
38-
self.logger.debug('Received tag is: ' + tag_name)
39-
self.logger.debug('Attack tag is: ' + self.attacked_tag)
4041
if self.attacked_tag == tag_name:
4142
# This is a packet being sent to SCADA server, conceal the manipulation
42-
self.logger.debug('Packet source: ' + p[IP].src )
43-
self.logger.debug('SCADA IP: ' + self.intermediate_yaml['scada']['public_ip'])
4443
if p[IP].src == self.intermediate_yaml['scada']['public_ip']:
45-
self.logger.debug('SCADA session: ' + str(this_session))
4644
self.scada_session_ids.append(this_session)
4745
else:
48-
self.logger.debug('PLC session: ' + str(this_session))
4946
self.attack_session_ids.append(this_session)
5047

5148
if len(p) == 102:
5249
this_session = int.from_bytes(p[Raw].load[4:8], sys.byteorder)
5350
if this_session in self.attack_session_ids:
54-
self.logger.debug('Modifying because session is: ' + str(this_session))
5551
value = translate_payload_to_float(p[Raw].load)
56-
self.logger.debug('tag value is:' + str(value))
5752

5853
if 'value' in self.intermediate_attack.keys():
5954
p[Raw].load = translate_float_to_payload(
@@ -72,8 +67,10 @@ def capture(self, packet):
7267

7368
elif this_session in self.scada_session_ids:
7469
self.logger.debug('Concealing to SCADA: ' + str(this_session))
75-
p[Raw].load = translate_float_to_payload(
76-
self.intermediate_attack['concealment_value'], p[Raw].load)
70+
exp = (self.concealment_data_pd['iteration'] == self.get_master_clock())
71+
concealment_value = float(self.concealment_data_pd.loc[exp][self.attacked_tag].values[-1])
72+
self.logger.debug('Concealing with value: ' + str(concealment_value))
73+
p[Raw].load = translate_float_to_payload(concealment_value, p[Raw].load)
7774

7875
del p[IP].chksum
7976
del p[TCP].chksum

dhalsim/network_attacks/mitm_netfilter_queue.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,13 @@ def capture(self, packet):
3333
if len(p) == 116:
3434
this_session = int.from_bytes(p[Raw].load[4:8], sys.byteorder)
3535
tag_name = p[Raw].load.decode(encoding='latin-1')[54:56]
36-
self.logger.debug('ENIP TCP Session ID: ' + str(this_session))
37-
self.logger.debug('Received tag is: ' + tag_name)
38-
self.logger.debug('Attack tag is: ' + self.attacked_tag)
3936
if self.attacked_tag == tag_name:
40-
self.logger.debug('Modifying tag: ' + tag_name)
4137
self.session_ids.append(this_session)
4238

4339
if len(p) == 102:
4440
this_session = int.from_bytes(p[Raw].load[4:8], sys.byteorder)
4541
if this_session in self.session_ids:
46-
self.logger.debug('Modifying because session is: ' + str(this_session))
4742
value = translate_payload_to_float(p[Raw].load)
48-
self.logger.debug('tag value is:' + str(value))
4943

5044
if 'value' in self.intermediate_attack.keys():
5145
p[Raw].load = translate_float_to_payload(

dhalsim/network_attacks/mitm_netfilter_queue_subprocess.py

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,32 @@
1-
from pathlib import Path
1+
import yaml
2+
import sqlite3
23
import sys
34
import signal
5+
import time
6+
import random
47

5-
from netfilterqueue import NetfilterQueue
6-
import yaml
78
from dhalsim.py3_logger import get_logger
8-
9+
from pathlib import Path
10+
from netfilterqueue import NetfilterQueue
911
from abc import ABCMeta, abstractmethod
1012

13+
class Error(Exception):
14+
"""Base class for exceptions in this module."""
15+
16+
17+
class DatabaseError(Error):
18+
"""Raised when not being able to connect to the database"""
19+
1120

1221
class PacketQueue(metaclass=ABCMeta):
1322
"""
1423
Currently, the Netfilterqueue library in Python3 does not support running in threads, using blocking calls.
1524
We will use this class to launch a subprocess that handles the packets in the queue.
1625
"""
1726

27+
DB_TRIES = 10
28+
"""Amount of times a db query will retry on a exception"""
29+
1830
def __init__(self, intermediate_yaml_path: Path, yaml_index: int, queue_number: int):
1931

2032
signal.signal(signal.SIGINT, self.sigint_handler)
@@ -31,6 +43,7 @@ def __init__(self, intermediate_yaml_path: Path, yaml_index: int, queue_number:
3143

3244
# Get the attack that we are from the intermediate YAML
3345
self.intermediate_attack = self.intermediate_yaml["network_attacks"][self.yaml_index]
46+
self.db_sleep_time = random.uniform(0.01, 0.1)
3447

3548
def main_loop(self):
3649
self.logger.debug('Parent NF Class launched')
@@ -44,6 +57,60 @@ def main_loop(self):
4457
self.nfqueue.unbind()
4558
sys.exit(0)
4659

60+
def db_query(self, query, write=False, parameters=None):
61+
"""
62+
Execute a query on the database
63+
On a :code:`sqlite3.OperationalError` it will retry with a max of :code:`DB_TRIES` tries.
64+
Before it reties, it will sleep for :code:`DB_SLEEP_TIME` seconds.
65+
This is necessary because of the limited concurrency in SQLite.
66+
67+
:param query: The SQL query to execute in the db
68+
:type query: str
69+
70+
:param write: Boolean flag to indicate if this query will write into the database
71+
72+
:param parameters: The parameters to put in the query. This must be a tuple.
73+
74+
:raise DatabaseError: When a :code:`sqlite3.OperationalError` is still raised after
75+
:code:`DB_TRIES` tries.
76+
"""
77+
for i in range(self.DB_TRIES):
78+
try:
79+
with sqlite3.connect(self.intermediate_yaml["db_path"]) as conn:
80+
cur = conn.cursor()
81+
if parameters:
82+
cur.execute(query, parameters)
83+
else:
84+
cur.execute(query)
85+
conn.commit()
86+
87+
if not write:
88+
return cur.fetchone()[0]
89+
else:
90+
return
91+
except sqlite3.OperationalError as exc:
92+
self.logger.info(
93+
"Failed to connect to db with exception {exc}. Trying {i} more times.".format(
94+
exc=exc, i=self.DB_TRIES - i - 1))
95+
time.sleep(self.db_sleep_time)
96+
self.logger.error(
97+
"Failed to connect to db. Tried {i} times.".format(i=self.DB_TRIES))
98+
raise DatabaseError("Failed to get master clock from database")
99+
100+
def get_master_clock(self):
101+
"""
102+
Get the value of the master clock of the physical process through the database.
103+
On a :code:`sqlite3.OperationalError` it will retry with a max of :code:`DB_TRIES` tries.
104+
Before it reties, it will sleep for :code:`DB_SLEEP_TIME` seconds.
105+
106+
:return: Iteration in the physical process.
107+
108+
:raise DatabaseError: When a :code:`sqlite3.OperationalError` is still raised after
109+
:code:`DB_TRIES` tries.
110+
"""
111+
master_time = self.db_query("SELECT time FROM master_time WHERE id IS 1", False, None)
112+
return master_time
113+
47114
@abstractmethod
48115
def capture(self, pkt):
49116
"""

dhalsim/parser/config_parser.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -239,9 +239,14 @@ class SchemaParser:
239239
'value': And(
240240
Or(float, And(int, Use(float)))
241241
),
242-
'concealment_value': And(
243-
Or(float, And(int, Use(float)))
244-
)
242+
'concealment_data': And(
243+
str,
244+
Use(Path),
245+
Use(lambda p: p.absolute().parent / p),
246+
Schema(lambda l: Path.is_file, error="'inp_file' could not be found."),
247+
Schema(lambda f: f.suffix == '.csv',
248+
error="Suffix of 'inp_file' should be .inp.")
249+
),
245250
},
246251
{
247252
'type': And(
@@ -630,7 +635,7 @@ def generate_device_attacks(self, yaml_data):
630635

631636
def generate_network_attacks(self):
632637
"""
633-
This function will add device attacks to the appropriate PLCs in the intermediate yaml
638+
This function will add network attacks to the appropriate PLCs in the intermediate yaml
634639
635640
:param network_attacks: The YAML data of the network attacks
636641
"""
@@ -661,6 +666,9 @@ def generate_network_attacks(self):
661666
raise NoSuchTag(
662667
f"PLC {target_plc['name']} does not have all the tags specified.")
663668

669+
if network_attack['type'] == 'concealment_mitm':
670+
network_attack['concealment_data'] = str(network_attack['concealment_data'])
671+
664672
return network_attacks
665673
return []
666674

@@ -766,6 +774,7 @@ def generate_intermediate_yaml(self):
766774
yaml_data = self.generate_device_attacks(yaml_data)
767775
yaml_data["network_attacks"] = self.generate_network_attacks()
768776

777+
769778
# Parse network events from the config file
770779
yaml_data["network_events"] = self.generate_network_events()
771780

examples/ctown_topology/ctown_concealment_mitm.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ network_attacks:
33
type: concealment_mitm
44
tag: T3
55
target: PLC4
6-
value: 42.0
7-
concealment_value: 84
6+
value: 8.0
7+
concealment_data: concealment_test.csv
88
trigger:
9-
start: 5
10-
end: 15
9+
start: 648
10+
end: 936
1111
type: time

examples/ctown_topology/ctown_config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
inp_file: ctown_map.inp
2-
iterations: 20
2+
iterations: 2880
33
network_topology_type: complex
44
plcs: !include ctown_plcs.yaml
55

0 commit comments

Comments
 (0)