zgrab2/modules/dnp3/dnp3.go
Elliot Cubit 457d8a090b
Ecubit/fix false positives (#262)
* telnet module should not return success when it finds something other than telnet

* telnet module should not return success when it finds something other than telnet

* Adds verification step for POP3 banners

* Add validation for IMAP banners & fix some formatting problems

* Verify SMTP banners exist and are successful

* Add check for is_dnp3 flag which seems to be working as expected

* Fix dropping SCAN_APPLICATION_ERROR in IMAP

* Fix dropping SCAN_APPLICATION_ERROR in POP3

* Fix dropping SCAN_APPLICATION_ERROR in SMTP

* Add protocol and blacklist indicators to email protocols

Co-authored-by: Elliot Cubit <elliotcubit@elliots-mbp.lan>
2020-06-01 14:26:24 -04:00

202 lines
7.7 KiB
Go

/*
* 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/zmap/zgrab2"
)
// 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
}