-
Notifications
You must be signed in to change notification settings - Fork 0
/
dnp3.go
201 lines (163 loc) · 7.7 KB
/
dnp3.go
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
/*
* ZGrab Copyright 2015 Regents of the University of Michigan
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package dnp3
import (
"encoding/binary"
"io"
"net"
"errors"
"github.com/dmin12/zgrab_tls1.3"
)
// DNP3 Flags
const (
LINK_MIN_HEADER_LENGTH = 10 // minimum link header length in bytes
LINK_START_FIELD = 0x0564 // Pre-set 2-byte start field
LINK_DIR_BIT = 1 // Direction bit
LINK_PRM_BIT = 1 // Primary message bit
LINK_FCB_BIT = 0 // Frame count bit
LINK_FCV_BIT = 0 // Frame count valid bit
LINK_BROADCAST_ADDRESS = 0x0001 // Broadcast address w/o mandatory application response
LINK_UNCONFIRMED_USER_DATA_FC = 0x4 // don't require link layer response function code
LINK_REQUEST_STATUS_FC = 0x9 // 4-bit function code for requesting link status
LINK_STATUS_FC = 0xB // 4-bit response function code for link status
FUNCTION_CODE_NOT_SUPPORTED = 0xF // Unsupported function code response
TRANSPORT_START_SEQUENCE = 0x00 // starting sequence number for transport packet
APP_START_SEQUENCE = 0x00 // starting sequence number for application packet
APP_CON_BIT = 0 // no app acknowledgement
APP_UNS_BIT = 0 // not an unsolicited response
APP_FUNC_CODE_READ = 0x01 // 1-byte function code for reading
APP_GROUP_0 = 0x00 // group 0 refers to all static data
APP_GROUP_0_QUALIFIER = 0x00 // objects are packed without index prefix
APP_GROUP_0_RANGE = 0x0000 // no range due to no qualifier
APP_GROUP_0_SOFTWARE_VERSION = 0xF2 // group 0 attribute - device manufacturer's software version
APP_GROUP_0_HARDWARE_VERSION = 0xF3 // group 0 attribute - device manufacturer's hardware version
APP_GROUP_0_LOCATION = 0xF5 // group 0 attribute - device location
APP_GROUP_0_DEVICE_ID = 0xF6 // group 0 attribute - device application id
APP_GROUP_0_DEVICE_NAME = 0xF7 // group 0 attribute - device name
APP_GROUP_0_SERIAL_NUMBER = 0xF8 // group 0 attribute - device manufacturer's serial number
APP_GROUP_0_DNP3_SUBSET = 0xF9 // subset of the dnp3 protocol that is implemented
APP_GROUP_0_PRODUCT_NAME = 0xFA // group 0 attribute - device manufacturer's product name and model
APP_GROUP_0_ALL_ATTRIBUTES = 0xFE // get all available group 0 attributes in single response
APP_GROUP_0_LIST_ATTRIBUTES = 0xFF // list available group 0 attributes
)
var linkBatchRequest []byte
func init() {
linkBatchRequest = makeLinkRequestBatch(0x0000, 1, 0x0000, 100)
}
func GetDNP3Banner(logStruct *DNP3Log, connection net.Conn) (err error) {
connection.Write(linkBatchRequest)
data, err := zgrab2.ReadAvailable(connection)
if err != nil && err != io.EOF {
return err
}
if len(data) >= LINK_MIN_HEADER_LENGTH && binary.BigEndian.Uint16(data[0:2]) == LINK_START_FIELD {
logStruct.IsDNP3 = true
logStruct.RawResponse = data
return nil
}
return zgrab2.NewScanError(zgrab2.SCAN_PROTOCOL_ERROR, errors.New("Invalid response for DNP3"))
}
func makeLinkStatusRequest(dstAddress uint16) []byte {
return makeLinkHeader(0x0000, dstAddress, LINK_REQUEST_STATUS_FC, 0) // no transport/app layer
}
func makeBannerRequest(dstAddress uint16) []byte {
var request []byte
transportLayer := makeTransportHeader()
appLayer := makeAppAttrRequest()
linkLayer := makeLinkHeader(0x0000, dstAddress, LINK_UNCONFIRMED_USER_DATA_FC, len(transportLayer)+len(appLayer))
request = append(request, linkLayer...)
request = append(request, transportLayer...)
request = append(request, appLayer...)
return request
}
func setBit(b byte, position uint32, value int) (result byte) {
if value == 1 {
result = b | (1 << position)
} else if value == 0 {
result = b & (^(1 << position))
}
return result
}
/**
* Creates a link-layer header for a given data link function code.
* MsgLength is the length of the transport + application layers
*/
func makeLinkHeader(srcAddress uint16, dstAddress uint16, functionCode int, msgLength int) []byte {
linkHeader := make([]byte, 0, LINK_MIN_HEADER_LENGTH)
// DATA LINK LAYER
// 2-byte start field
startField := make([]byte, 2)
binary.BigEndian.PutUint16(startField, LINK_START_FIELD)
linkHeader = append(linkHeader, startField...)
//length byte
lengthByte := byte(0x5 + msgLength)
linkHeader = append(linkHeader, lengthByte)
//link control byte
linkControlByte := byte(functionCode)
linkControlByte = setBit(linkControlByte, 7, LINK_DIR_BIT)
linkControlByte = setBit(linkControlByte, 6, LINK_PRM_BIT)
linkControlByte = setBit(linkControlByte, 5, LINK_FCB_BIT)
linkControlByte = setBit(linkControlByte, 4, LINK_FCV_BIT)
linkHeader = append(linkHeader, linkControlByte)
// 2-byte destination address
destinationAddress := make([]byte, 2)
binary.LittleEndian.PutUint16(destinationAddress, dstAddress)
linkHeader = append(linkHeader, destinationAddress...)
// 2-byte source address
sourceAddress := make([]byte, 2)
binary.LittleEndian.PutUint16(sourceAddress, srcAddress)
linkHeader = append(linkHeader, sourceAddress...)
//CRC
crcCheck := make([]byte, 2)
binary.LittleEndian.PutUint16(crcCheck, Crc16(linkHeader))
linkHeader = append(linkHeader, crcCheck...)
return linkHeader
}
func makeTransportHeader() []byte {
var transportHeader []byte
transportByte := byte(TRANSPORT_START_SEQUENCE)
transportByte = setBit(transportByte, 7, 1) //first transport segment
transportByte = setBit(transportByte, 6, 1) //last transport segment
transportHeader = append(transportHeader, transportByte)
return transportHeader
}
// Make an application layer []byte request for device attributes
func makeAppAttrRequest() []byte {
var attrRequest []byte
// control byte
appControlByte := byte(APP_START_SEQUENCE)
appControlByte = setBit(appControlByte, 7, 1) //first app layer segment
appControlByte = setBit(appControlByte, 6, 1) //last app layer segment
appControlByte = setBit(appControlByte, 5, APP_CON_BIT) //
appControlByte = setBit(appControlByte, 4, APP_UNS_BIT) //last app layer segment
attrRequest = append(attrRequest, appControlByte)
attrRequest = append(attrRequest, byte(APP_FUNC_CODE_READ)) // function code
// Object header
attrRequest = append(attrRequest, byte(APP_GROUP_0)) // group to be reading from
attrRequest = append(attrRequest, byte(APP_GROUP_0_ALL_ATTRIBUTES)) // get all attributes
attrRequest = append(attrRequest, byte(APP_GROUP_0_QUALIFIER)) // no object indexing
appIndexRange := make([]byte, 2)
binary.LittleEndian.PutUint16(appIndexRange, APP_GROUP_0_RANGE) // range is irrelevant due to no indexing
attrRequest = append(attrRequest, appIndexRange...)
return attrRequest
}
func makeLinkRequestBatch(startingSrcAddress uint16, numberSrc int, startingDestAddress uint16, numberDest int) []byte {
var batchRequest []byte
for src, srcCount := startingSrcAddress, 0; srcCount < numberSrc; src, srcCount = src+1, srcCount+1 {
for dest, destCount := startingDestAddress, 0; destCount < numberDest; dest, destCount = dest+1, destCount+1 {
batchRequest = append(batchRequest, makeLinkHeader(src, dest, LINK_REQUEST_STATUS_FC, 0)...)
}
}
return batchRequest
}