zgrab2/modules/modbus/scanner.go
Benjamin Wireman cfd48de9d6
Fix panics in Modbus scanner (#265)
* Check message and body length in in GetModbusResponse()

* add timeout to waiting when PSQL container to start

https://github.com/zmap/zgrab2/pull/265
2020-05-29 13:07:47 -04:00

212 lines
6.9 KiB
Go

// Package modbus provides a zgrab2 module that scans for modbus.
// Default Port: 502 (TCP)
//
// The --unit-id flag allows overriding the default value of 0 (the simulator
// for example does not respond at all to UnitID == 0; other servers may
// interpret it as a broadcast).
//
// The --object-id flag allows reading a different object ID's information.
// The default of 0x00 is the VendorName, which is required.
//
// The --request-id flag allows setting a custom request identifier (which
// the server will use in its response).
//
// The --strict flag allows turning on new validity checks beyond those
// done in the original zgrab, to help rule out false matches.
//
// The output is the same as the original ZGrab: a "modbus event" object,
// with either the parsed MEI response or the parsed exception info.
// The only addition is a "raw" field containing the raw response data.
package modbus
import (
"encoding/hex"
"fmt"
"net"
log "github.com/sirupsen/logrus"
"github.com/zmap/zgrab2"
)
// Flags holds the command-line configuration for the modbus scan module.
// Populated by the framework.
type Flags struct {
zgrab2.BaseFlags
// Protocols that support TLS should include zgrab2.TLSFlags
UnitID uint8 `long:"unit-id" description:"The UnitID / Station ID to probe"`
ObjectID uint8 `long:"object-id" description:"The ObjectID of the object to be read." default:"0x00"`
Strict bool `long:"strict" description:"If set, perform stricter checks on the response data to get fewer false positives"`
RequestID uint16 `long:"request-id" description:"Override the default request ID." default:"0x5A47"`
Verbose bool `long:"verbose" description:"More verbose logging, include debug fields in the scan results"`
}
// Module implements the zgrab2.Module interface.
type Module struct {
}
// Scanner implements the zgrab2.Scanner interface.
type Scanner struct {
config *Flags
}
// RegisterModule registers the zgrab2 module.
func RegisterModule() {
var module Module
_, err := zgrab2.AddCommand("modbus", "modbus", module.Description(), 502, &module)
if err != nil {
log.Fatal(err)
}
}
// NewFlags returns a default Flags object.
func (module *Module) NewFlags() interface{} {
return new(Flags)
}
// NewScanner returns a new Scanner instance.
func (module *Module) NewScanner() zgrab2.Scanner {
return new(Scanner)
}
// Description returns an overview of this module.
func (module *Module) Description() string {
return "Probe for Modbus devices, usually PLCs as part of a SCADA system"
}
// Validate checks that the flags are valid.
// On success, returns nil.
// On failure, returns an error instance describing the error.
func (flags *Flags) Validate(args []string) error {
if flags.Verbose {
// If --verbose is set, do some extra checking but don't fail.
if flags.ObjectID >= 0x07 && flags.ObjectID < 0x80 {
log.Warnf("ObjectIDs 0x07...0x7F are reserved (requested 0x%02x)", flags.ObjectID)
}
}
return nil
}
// Help returns the module's help string.
func (flags *Flags) Help() string {
return ""
}
// Init initializes the Scanner.
func (scanner *Scanner) Init(flags zgrab2.ScanFlags) error {
f, _ := flags.(*Flags)
scanner.config = f
return nil
}
// InitPerSender initializes the scanner for a given sender.
func (scanner *Scanner) InitPerSender(senderID int) error {
return nil
}
// GetName returns the Scanner name defined in the Flags.
func (scanner *Scanner) GetName() string {
return scanner.config.Name
}
// GetTrigger returns the Trigger defined in the Flags.
func (scanner *Scanner) GetTrigger() string {
return scanner.config.Trigger
}
// Protocol returns the protocol identifier of the scan.
func (scanner *Scanner) Protocol() string {
return "modbus"
}
// Conn wraps the connection state (more importantly, it provides the interface used by the old zgrab code, so that it
// could be taken over as-is).
type Conn struct {
Conn net.Conn
scanner *Scanner
}
func (c *Conn) getUnderlyingConn() net.Conn {
return c.Conn
}
// Scan probes for a modbus service.
// It connects to the configured TCP port (default 502) and sends a packet with:
// UnitID = <flags.UnitID, default 0>
// FunctionCode = 0x2B: Encapsulated Interface Transport)
// MEI Type = 0x0E: Read Device Info
// Category = 0x01: Basic
// ObjectID = <flags.ObjectID, default 0: VendorName>
// If the response is not a valid modbus response to this packet, then fail with a SCAN_PROTOCOL_ERROR.
// Otherwise, return the parsed response and status (SCAN_SUCCESS or SCAN_APPLICATION_ERROR)
func (scanner *Scanner) Scan(target zgrab2.ScanTarget) (zgrab2.ScanStatus, interface{}, error) {
conn, err := target.Open(&scanner.config.BaseFlags)
if err != nil {
return zgrab2.TryGetScanStatus(err), nil, err
}
defer conn.Close()
c := Conn{Conn: conn, scanner: scanner}
req := ModbusRequest{
UnitID: int(scanner.config.UnitID),
Function: ModbusFunctionEncapsulatedInterface,
Data: []byte{
0x0E, // 0x0E = MEI Read Device Identification
0x01, // 0x01 = "Category" = basic (02 = regular, 03 = extended, 04 = specific)
scanner.config.ObjectID,
},
}
data, err := c.MarshalRequest(&req)
if err != nil {
log.Fatalf("Unexpected error marshaling modbus packet: %v", err)
}
w := 0
for w < len(data) {
written, err := c.getUnderlyingConn().Write(data[w:])
w += written
if err != nil {
return zgrab2.TryGetScanStatus(err), nil, err
}
}
res, err := c.GetModbusResponse()
if res == nil {
return zgrab2.TryGetScanStatus(err), nil, err
}
// if there was an error but we still got a response, try to continue
if scanner.config.Verbose {
log.Debugf("Got non-fatal error while reading modbus response: %v", err)
}
if res.Function&0x7F != ModbusFunctionEncapsulatedInterface {
// The server should always return a response for the same function
return zgrab2.SCAN_PROTOCOL_ERROR, nil, fmt.Errorf("Invalid response function code 0x%02x (raw = %s)", res.Function, hex.Dump(res.Raw))
}
if scanner.config.Strict && (scanner.config.UnitID != 0 && res.UnitID != int(scanner.config.UnitID)) {
// response for different unitID.
// If request unit ID was 0, don't enforce matching since that may be interpreted as a broadcast.
return zgrab2.SCAN_PROTOCOL_ERROR, nil, fmt.Errorf("Invalid response unit ID 0x%02x (raw = %s)", res.UnitID, hex.Dump(res.Raw))
}
if res.Length != len(res.Data)+2 {
// data not expected size
// Let this one slide with a debug-only warning, since some actual servers seem to behave this way
log.Debugf("Server advertised %d bytes of data, received %d", res.Length, len(res.Data)+2)
}
ret, err := res.getEvent(scanner.config.Strict)
if err != nil {
// Unable to parse the response as a valid event
log.Debugf("Unable to process response as modbus: %v. Raw response data=[\n%s\n]", err, hex.Dump(res.Raw))
return zgrab2.SCAN_PROTOCOL_ERROR, nil, err
}
status := zgrab2.SCAN_SUCCESS
if res.IsException() {
// Note the exception, but note that the modbus protocol was detected
status = zgrab2.SCAN_APPLICATION_ERROR
}
return status, ret, nil
}