playing with modbus day #1
1 parent a138081 commit 8fd876a265cd662bfd21efdc23ab085934920647
root authored on 8 May 2022
Showing 72 changed files
View
1
■■■■
README.md
SCADA
===============
 
SCADA/ICS/PLC scripts
View
modbus/GRFICS_0-99_Modbus.xls 0 → 100644
Not supported
View
107
modbus/README.md 0 → 100644
MODBUS
===============
 
Modbus scripts
 
## in this folder
- `enum_all.py`, script take IP as input and brute force against all the registers. Identified valid registered will be exported to excel file. Modified registers written back to origional values!
- `monitor_multiple.py`, monitor several modbus addresses with a configurable interval and pretty colours
- `set_coil.py`, set a specific coil value, loop option available
 
## recommendations:
- `https://github.com/sourceperl/mbtget`, Simple perl script for make some modbus transaction from the command line.
 
## Enumerate SCADA Modbus slave ids (sids) and collects their device information.
when talking to a RTU you supply a SID to tell it which PLC you want to interact with. When talking directly to a PLC generally 0x01 will be the default.
```
nmap --script modbus-discover.nse --script-args='modbus-discover.aggressive=true' -p 502 <host>
 
```
 
## mbtget
```
read coils:
mbtget -u 1 -p 502 -r1 -a 0 -n 125 [ip]
 
read registers
mbtget -u 1 -p 502 -r3 -a 0 -n 125 [ip]
 
enumerate coils:
for i in {0..1000}; do mbtget -r1 -a $i -n 1 | grep -v -e exception -e values | tee -a /tmp/coils.txt; done
 
enumerate registers:
for i in {0..1000}; do mbtget -r3 -a $i -n 1 | grep -v -e exception -e values | tee -a /tmp/holding-regs.txt; done
 
polling values over time (assume 5 is changing):
for i in {0..1000}; do echo -n `date +"%Y-%m-%d %T"`; mbtget -r3 -a 4 -n 1 | grep -v values | tee -a reg-4.txt; sleep 1; done
```
 
## enum_all.py
```
$> python enum_all.py -r 0 3 -i 127.0.0.1
******************************
Reading Coils Status - 0x01
100%|████████████████████████████████████| 3/3 [00:00<00:00, 677.19it/s]
Writing Coil Status - 0x05
******************************
Reading Holding Registers - 0x03
100%|████████████████████████████████████| 3/3 [00:00<00:00, 25.89it/s]
Writing Holding Status - 0x06
100%|████████████████████████████████████| 2/2 [00:00<00:00, 246.03it/s]
******************************
Reading Discrete Input - 0x02
100%|████████████████████████████████████| 3/3 [00:00<00:00, 795.93it/s]
******************************
Reading Input Registers - 0x04
100%|████████████████████████████████████| 3/3 [00:00<00:00, 766.69it/s]
******************************
Check the output Excel File - Modbus_Output.xls
 
```
 
## enum_all.py
```
$> python monitor_muiltiple.py -h
usage: monitor_muiltiple.py [-h] [-co [COIL [COIL ...]]]
[-ho [HOLD [HOLD ...]]]
[-di [DISCRETE [DISCRETE ...]]]
[-in [INPUT [INPUT ...]]] -i IPADDRESS [-p PORT]
[-t TIMEOUT]
optional arguments:
-h, --help show this help message and exit
-co [COIL [COIL ...]] list of addresses
-ho [HOLD [HOLD ...]] list of addresses
-di [DISCRETE [DISCRETE ...]] list of addresses
-in [INPUT [INPUT ...]] list of addresses
-i IPADDRESS Input IP Address
-p PORT Port Number
-t TIMEOUT request every X seconds
 
$> python monitor_muiltiple.py -co 0 1 3-6 8 -ho 0-6 -i 127.0.0.1
### watch list ###
coils: (7)[0, 1, 3, 4, 5, 6, 8]
hold reg: (7)[0, 1, 2, 3, 4, 5, 6]
Total = 14
------------------
[coils:[0][1][0][0][0][0][0]][hold regs:[ 13][ 666][ ][ ][ ][ ][ ]]
 
```
## set_coil.py
```
$> python set_coil.py -h
usage: set_coil.py [-h] -i IPADDRESS [-p PORT] -c COIL [-tr] [-l] [-t TIMEOUT]
 
optional arguments:
-h, --help show this help message and exit
-i IPADDRESS, --ipaddress IPADDRESS
Input IP Address
-p PORT, --port PORT Port Number
-c COIL, --coil COIL Coil Number
-tr, --true True if set
-l, --loop loop on
-t TIMEOUT, --timeout TIMEOUT
request every X seconds
 
$> python set_coil.py -i 192.168.95.2 -c 5 -tr
 
```
View
183
modbus/enum_all.py 0 → 100644
#!/usr/bin/env python
 
"""
File: enum_all.py
Desc: brute-force all the registers of a TCP MODBUS Slave
 
based off of: https://github.com/nallamuthu/ModBus
fixed so works, made so sets values back to what were to limit interference/overwriting values
"""
__author__ = '0xRoM'
 
from pyModbusTCP.client import ModbusClient
from xlwt import Workbook
import sys
import argparse
from tqdm import tqdm
 
#Function to write single Holding Register
def write_single_hold_regiser(ip_addr,port,valid_list,permission_list):
write_confirm_list=[]
write_confirm_data=[]
data_orig={}
client=ModbusClient(host=ip_addr,port=port,auto_open=True,auto_close=True,timeout=10)
for i in tqdm(valid_list):
data_orig[i]=client.read_holding_registers(i,1)
orig_values = data_orig.values()
orig_values_list = list(orig_values)
 
data=client.write_single_register(i,43981) # HEX(ABCD) == int(43981)
data=client.read_holding_registers(i,1)
#permission_list.append("Read")
#print("Regiser: "+str(i)+" => Value: "+str(data))
if data:
if 43981==data[0]:
client.write_single_register(i,orig_values_list[i][0])
write_confirm_list.append(i)
write_confirm_data.append(data[0])
permission_list[i] = "Read/Write"
client.close()
return write_confirm_list,write_confirm_data,permission_list
 
#Function to write single coil
def write_single_coil_regiser(ip_addr,port,coil_valid_list,coil_permission_list):
write_coil_confirm_list=[]
write_coil_confirm_data=[]
data_orig={}
client=ModbusClient(host=ip_addr,port=port,auto_open=True,auto_close=True,timeout=20)
for i in coil_valid_list:
data_orig[i] = client.read_coils(i,1)
orig_values = data_orig.values()
orig_values_list = list(orig_values)
#print("Regiser: "+str(i)+" => Value: "+str(orig_values_list[i]))
if orig_values_list[i][0] == True:
client.write_single_coil(i,False)
else:
client.write_single_coil(i,True)
data=client.read_coils(i,1)
#coil_permission_list.append("Read")
#print("Regiser: "+str(i)+" => Value: "+str(data))
if data:
if data_orig[i]!=data[0]:
client.write_single_coil(i,orig_values_list[i][0])
write_coil_confirm_list.append(i)
write_coil_confirm_data.append(data[0])
coil_permission_list[i] = "Read/Write"
client.close()
return write_coil_confirm_list,write_coil_confirm_data,coil_permission_list
 
#Function to read all the registers based on the parameter received
def read_valid_registers(ip_addr,port,reg, lower, upper, timeout):
valid_list=[]
data_list=[]
permission_list=[]
client=ModbusClient(host=ip_addr,port=port,auto_open=True,auto_close=True,timeout=timeout)
for i in tqdm(range(lower,upper)):
if reg == "hold":
data=client.read_holding_registers(i,1)
if reg == "input":
data=client.read_input_registers(i,1)
if reg == "discrete":
data=client.read_discrete_inputs(i,1)
if reg == "coil":
data=client.read_coils(i,1)
if data:
valid_list.append(i)
data_list.append(data[0])
permission_list.append("Read")
#Write the result to Excel File
client.close()
return valid_list,data_list,permission_list
 
 
def write_to_excel(wb,register,valid_list,data_list,permission_list,reg_value):
sheet=wb.add_sheet(register)
sheet.write(0,0,"Register")
sheet.write(0,1,"Permission")
sheet.write(0,2,"Data")
for i in range(len(valid_list)):
sheet.write(i+1,0,valid_list[i])
sheet.write(i+1,1,permission_list[i])
sheet.write(i+1,2,data_list[i])
wb.save("Modbus_Output.xls")
return wb
 
#
def print_details(valid_list,data_list,operation):
if valid_list:
print("******************************")
print(operation+" - Valid Registers: \n"+str(valid_list))
print("******************************")
print(operation+" - Valid Registers with Non-Zero Values")
print("******************************")
for i in range(len(valid_list)):
if data_list[i]:
print("Register :"+str(valid_list[i])+" => Value: "+str(data_list[i]))
print("******************************")
else:
print("******************************")
print("No Valid Register Found")
print("******************************")
 
#Parse the input parameters
my_parser = argparse.ArgumentParser(description='Pass the input IP Address')
my_parser.add_argument('-i','--ipaddress', action='store', type=str,required=True, help='Input IP Address')
my_parser.add_argument('-p','--port', action='store', type=int,required=False, help='Port Number', default=502)
my_parser.add_argument('-r','--range', action='store', type=int,required=False, default=[1-500],dest='range',help='Define the range. Default is 1-500',nargs=2)
my_parser.add_argument('-t','--timeout', action='store', type=int,required=False, help='Timeout', default=10)
args = my_parser.parse_args()
ip_addr=args.ipaddress
port=args.port
 
 
#Create workbook to save the result
wb=Workbook()
#Coils - valid and Data [Read and Write]
print("******************************")
print("Reading Coils Status - 0x01")
coil_valid_list,coil_data_list,coil_permission_list=read_valid_registers(ip_addr,port,"coil", args.range[0], args.range[1], args.timeout) #Function Code - 0x01
 
if coil_data_list:
print("Writing Coil Status - 0x05")
write_coil_valid_list,write_coil_data_list,coil_permission_list=write_single_coil_regiser(ip_addr,port,coil_valid_list,coil_permission_list) #Function Code - 0x05
 
else:
print("No valid Register Found to do Write Operation")
#print_details(coil_valid_list,coil_data_list,"Read")
wb=write_to_excel(wb,"Coil",coil_valid_list,coil_data_list,coil_permission_list,0)
 
 
 
#Holding Registers - valid and Data [Read and Write]
print("******************************")
print("Reading Holding Registers - 0x03")
hold_valid_list,hold_data_list,hold_permission_list=read_valid_registers(ip_addr,port,"hold", args.range[0], args.range[1], args.timeout) #Function Code - 0x03
#print_details(hold_valid_list,hold_data_list,"Read")
if hold_data_list:
print("Writing Holding Status - 0x06")
write_hold_valid_list,write_hold_data_list,hold_permission_list=write_single_hold_regiser(ip_addr,port,hold_valid_list,hold_permission_list) #Function Code - 0x06
else:
print("No valid Register Found to do Write Operation")
wb=write_to_excel(wb,"Holding_Register",hold_valid_list,hold_data_list,hold_permission_list,40000)
 
 
 
#Discrete Input - valid and Data [Read Only]
print("******************************")
print("Reading Discrete Input - 0x02")
discrete_valid_list,discrete_data_list,discrete_permission_list=read_valid_registers(ip_addr,port,"discrete", args.range[0], args.range[1], args.timeout) #Function Code - 0x02
#print_details(discrete_valid_list,discrete_data_list,"Read")
wb=write_to_excel(wb,"Discrete",discrete_valid_list,discrete_data_list,discrete_permission_list,10000)
 
 
 
#Read Input Registers - valid and Data [Read Only]
print("******************************")
print("Reading Input Registers - 0x04")
input_valid_list,input_data_list,input_permission_list=read_valid_registers(ip_addr,port,"input", args.range[0], args.range[1], args.timeout)
#print_details(input_valid_list,input_data_list,"Read")
wb=write_to_excel(wb,"Input_Register",input_valid_list,input_data_list,input_permission_list,30000)
print("******************************")
print("Check the output Excel File - Modbus_Output.xls")
View
modbus/misc/ModbusPal.jar 0 → 100755
Not supported
View
107
modbus/misc/README.md 0 → 100644
- `dump_odd.py`, dump all data that doesn't have the default value
- `live_monitor.rb`, Allows you to scan, at a regular time, all Modbus registries of a device.
- `mocli.py`, A Command Line Interface for pymodbus
- `read_all_holding_registers.py`, used to scan and get values from holding registers on a TCP Modbus Slave;
- `write_all_holding_registers.py`, used to write a specific value to one or multiple holding registers of a TCP Modbus Slave;
- `read_register.py`, used to get values from various types of addresses on a TCP Modbus Slave (Holding Register, Discrete Input, Input Register)
- `pymodmon.py`, monitor several modbus addresses with a configurable interval and can also write the received data to a csv file.
- `pymodmon_3.py`, updated version of above script for python 3
- `register_scanner.py`, Script take IP as input and brute force against all the registers. Identified valid registered will be exported to excel file
- `modbuspal.jar`, PC-based Modbus simulator. Its goal is to reproduce a realistic environment, with many slaves and animated register values.
 
## live_monitor.rb
```
Usage : ruby live_monitor.rb IP_ADDRESS TIME_IN_SECONDS
```
 
## live_monitor.rb
```
Usage : python register_scanner.py -i ip_addr
```
 
## read_all_holding_registers.py
```
usage: read_all_holding_registers.py [-h] [-p PORT] [-u UID] [-sa START]
[-ea END]
ip
 
Read all holding registries from a TCP MODBUS Slave
 
positional arguments:
ip IP address of the slave
 
optional arguments:
-h, --help show this help message and exit
-p PORT, --port PORT Modbus Port. Defaults to 502
-u UID, --uid UID Modbus Unit ID. Defaults to 1
-sa START, --start-address START
Starting Address for the scanner. Defaults to 1
-ea END, --end-address END
Ending Address for the scanner. Defaults to 65535
```
 
## write_all_holding_registers.py
```
usage: write_all_holding_registers.py [-h] [-p PORT] [-u UID] [-sa START]
[-ea END] [-v VALUE]
ip
 
Write all holding registries on a TCP MODBUS Slave
 
positional arguments:
ip IP address of the slave
 
optional arguments:
-h, --help show this help message and exit
-p PORT, --port PORT Modbus Port. Defaults to 502
-u UID, --uid UID Modbus Unit ID. Defaults to 1
-sa START, --start-address START
Starting Address for the writer. Defaults to 1
-ea END, --end-address END
Ending Address for the writer. Defaults to 65535
-v VALUE, --value VALUE
Value that will be written. Defaults to 7777
```
 
## read_register.py
```
usage: read_register.py [-h] [-p PORT] [-u UID] [-sa START] [-ea END]
[-t TYPE]
ip
 
Read specific addresses on a TCP MODBUS Slave
 
positional arguments:
ip IP address of the slave
 
optional arguments:
-h, --help show this help message and exit
-p PORT, --port PORT Modbus Port. Defaults to 502
-u UID, --uid UID Modbus Unit ID. Defaults to 1
-sa START, --start-address START
Starting Address for the scanner. Defaults to 1
-ea END, --end-address END
Ending Address for the scanner. Defaults to 65535
-t TYPE, --type TYPE Type of Modbus address to read. Values can be 'h' for
Holding, 'd' for Discrete Inputs or 'i' for Input
Registers. Defaults to 'h'
 
```
 
# PyModMon
Python Modbus Monitor
 
This is a Python skript that acts as a Modbus slave.
It can be used e.g. for reading data from newer solar inverters made by SMA.
 
It has the ability to monitor several modbus addresses with a configurable interval and can also write the received data to a csv file.
 
The logged data can then be used with other programs for analysing or plotting.
 
Dependencies:
* Python 2.7
* Python package docopt
* Python package pymodbus (and dependencies)
 
pymodmon_3.py is the updated version for Python 3 (tested with Python 3.7). No additional functionality was added.
View
28
modbus/misc/dump_odd.py 0 → 100644
import sys
from pymodbus.client.sync import ModbusTcpClient
#from https://ctftime.org/writeup/31455
 
client = ModbusTcpClient(sys.argv[1])
 
for unit in range(32):
for address, register in enumerate(client.read_holding_registers(0, 99, unit=unit).registers):
if register != 0:
print(f"hr {unit} {address} {register}")
 
for unit in range(32):
for address, register in enumerate(client.read_input_registers(0, 99, unit=unit).registers):
if register != 1:
print(f"ir {unit} {address} {register}")
 
for unit in range(32):
for address_base in range(0, 2999, 256):
for address_index, coil in enumerate(client.read_coils(address_base, min(256, 2999 - address_base), unit=unit).bits[:min(256, 2999 - address_base)]):
if coil != False:
print(f"c {unit} {address_base + address_index} {coil}")
 
for unit in range(32):
for address_base in range(0, 2999, 256):
for address_index, coil in enumerate(client.read_discrete_inputs(address_base, min(256, 2999 - address_base), unit=unit).bits[:min(256, 2999 - address_base)]):
if coil != True:
print(f"di {unit} {address_base + address_index} {coil}")
View
modbus/misc/live_monitor.rb 0 → 100644
View
modbus/misc/mocli.py 0 → 100644
View
modbus/misc/pymodmon.py 0 → 100644
View
modbus/misc/pymodmon_3.py 0 → 100644
View
modbus/misc/read_all_holding_registers.py 0 → 100644
View
modbus/misc/read_register.py 0 → 100644
View
modbus/misc/register_scanner.py 0 → 100644
View
modbus/misc/register_scanner.py.1 0 → 100644
View
modbus/misc/write_all_holding_registers.py 0 → 100644
View
modbus/monitor_muiltiple.py 0 → 100644
View
modbus/set_coil.py 0 → 100644
View
modbus/test_coil_true.py 0 → 100644
View
modbus/test_hold_int.py 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/attack.st 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/attack.xml 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/attack/beremiz.xml 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/attack/build/Config0.c 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/attack/build/Config0.h 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/attack/build/Config0.o 0 → 100644
Not supported
View
plc/GRFICS_Workstation_Docs/Documents/attack/build/LOCATED_VARIABLES.h 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/attack/build/POUS.c 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/attack/build/POUS.h 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/attack/build/Res0.c 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/attack/build/Res0.o 0 → 100644
Not supported
View
plc/GRFICS_Workstation_Docs/Documents/attack/build/VARIABLES.csv 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/attack/build/attack.so 0 → 100755
Not supported
View
plc/GRFICS_Workstation_Docs/Documents/attack/build/beremiz.h 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/attack/build/generated_plc.st 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/attack/build/lastbuildPLC.md5 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/attack/build/plc.st 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/attack/build/plc_debugger.c 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/attack/build/plc_debugger.o 0 → 100644
Not supported
View
plc/GRFICS_Workstation_Docs/Documents/attack/build/plc_main.c 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/attack/build/plc_main.o 0 → 100644
Not supported
View
plc/GRFICS_Workstation_Docs/Documents/attack/build/py_ext.c 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/attack/build/py_ext.o 0 → 100644
Not supported
View
plc/GRFICS_Workstation_Docs/Documents/attack/plc.xml 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/chemical/beremiz.xml 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/chemical/build/Config0.c 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/chemical/build/Config0.h 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/chemical/build/Config0.o 0 → 100644
Not supported
View
plc/GRFICS_Workstation_Docs/Documents/chemical/build/LOCATED_VARIABLES.h 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/chemical/build/POUS.c 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/chemical/build/POUS.h 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/chemical/build/Res0.c 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/chemical/build/Res0.o 0 → 100644
Not supported
View
plc/GRFICS_Workstation_Docs/Documents/chemical/build/VARIABLES.csv 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/chemical/build/beremiz.h 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/chemical/build/chemical.so 0 → 100755
Not supported
View
plc/GRFICS_Workstation_Docs/Documents/chemical/build/generated_plc.st 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/chemical/build/lastbuildPLC.md5 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/chemical/build/plc.st 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/chemical/build/plc_debugger.c 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/chemical/build/plc_debugger.o 0 → 100644
Not supported
View
plc/GRFICS_Workstation_Docs/Documents/chemical/build/plc_main.c 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/chemical/build/plc_main.o 0 → 100644
Not supported
View
plc/GRFICS_Workstation_Docs/Documents/chemical/build/py_ext.c 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/chemical/build/py_ext.o 0 → 100644
Not supported
View
plc/GRFICS_Workstation_Docs/Documents/chemical/plc.xml 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/mbconfig.cfg 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/simplified_te.st 0 → 100644
View
plc/GRFICS_Workstation_Docs/Documents/simplified_te.xml 0 → 100644
View
plc/GRFICS_Workstation_Docs/attacker_compiled.st 0 → 100644
View
plc/GRFICS_Workstation_Docs/default_compiled.st 0 → 100644
View
plc/OpenPLC_Editor v1.0 - Linux.zip 0 → 100644
Not supported
Buy Me A Coffee