Layers

Using layers

BOF relies on protocol implementations built using the Scapy syntax, to provide security testing and fuzzing features. In other words, BOF works as follows:

The layers folder contain BOF features for implemented protocols.

Scapy protocol implementations can be imported directly from Scapy or from a KNX implementation not integrated to Scapy that should be located in the layers/raw_scapy folder.

KNX

KNX and KNXnet/IP

KNX is a common field bus protocol in Europe, mostly used in Building Management Systems. KNXnet/IP is the version of the protocol over IP, implementing specific type of frames that either ask information from or send request to a gateway (server) between an IP network and a KNX bus or carry KNX messages that the gateway must relay to KNX devieces on the field bus.

The protocol is a merge a several older ones, the specifications are maintained by the KNX association and can be found on their website (section 3 is the interesting one).

BOF’s knx submodule can be imported with:

from bof.layers import knx
from bof.layers.knx import *

The following files are available in the module:

knx_network:Class for network communication with KNX over UDP. Inherits from BOF’s network UDP class. Implements methods to connect, disconnect and mostly send and receive frames as KNXPacket objects.
knx_packet:Object representation of a KNX packet. KNXPacket inherits BOFPacket and uses Scapy’s implementation of KNX (located in bof/layers/raw_scapy until contribution to Scapy). Contains method to build, read or alter a frame or part of it, even if this does not follow KNX’s specifications.
knx_feature:Higher-level functions to discover and interact with devices via KNXnet/IP.

Network connection

KNXnet/IP connection features, implementing bof.network’s UDP class.

The KnxNet class translates KNXPacket packet objects and raw Scapy packets to bytes to send them, and received bytes to KNXPacket objects.

KNX usually works over UDP, however KNX specification v2.1 state that TCP can also be used. The communication between BOF and a KNX device still acts like a TCP-based protocol, as (almost) every request expects a response.

Usage:

knxnet = KNXnet()
knxnet.connect("192.168.1.242")
data, addr = knxnet.sr(KNXPacket(type=SID.description_request))
data.show2()
knxnet.disconnect()
class bof.layers.knx.knx_network.KNXnet

Bases: bof.network.UDP

KNXnet/IP communication over UDP with protocol KNX. Relies on bof.network.UDP().

Sent and received datagrams are returned as KNXPacket() objects.

..seealso:: Details on data exchange: KNX Standard v2.1 - 03_03_04.

connect(ip: str, port: int = 3671) → object

Connect to a KNX device (opens socket). Default port is 3671.

Parameters:
  • ip – IPv4 address as a string with format A.B.C.D.
  • port – KNX port. Default is 3671.
Returns:

The KNXnet connection object (this instance).

Raises:

BOFNetworkError – if connection fails.

receive(timeout: float = 1.0) → object

Converts received bytes to a parsed KNXPacket object.

Parameters:timeout – Time to wait to receive a frame (default is 1 sec)
Returns:A KNXPacket object.
send(data: object, address: tuple = None) → int

Converts BOF and Scapy frames to bytes to send. Relies on UDP class to send data.

Parameters:
  • data – Data to send as KNXPacket, Scapy Packet, string or bytes. Will be converted to bytes anyway.
  • address – Address to send data to, with format (ip, port). If address is not specified, uses the address given to `` connect``.
Returns:

The number of bytes sent, as an integer.

sequence_counter = None

KNXPacket

This class inheriting from BOFPacket is the interface between BOF’s usage of KNX by the end user and an actual Scapy packet built using KNX’s implementation in Scapy format.

In BOFPacket and KNXPacket, several builtin methods and attributes are just relayed to the Scapy Packet underneath. We also want to let the user interact directly with the Scapy packet if she wants, using scapy_pkt attribute.

Example:

>>> from bof.layers.knx import *
>>> packet = KNXPacket(type=SID.description_request)
>>> packet
<bof.layers.knx.knx_packet.KNXPacket object at 0x7ff74224add8>
>>> packet.scapy_pkt
<KNX  service_identifier=DESCRIPTION_REQUEST |<KNXDescriptionRequest      control_endpoint=<HPAI  |> |>>
class bof.layers.knx.knx_packet.KNXPacket(_pkt: bytes = None, scapy_pkt: scapy.packet.Packet = None, type: object = None, **kwargs)

Bases: bof.packet.BOFPacket

Builds a KNXPacket packet from a byte array or from attributes.

Parameters:
  • _pkt – KNX frame as byte array to build KNXPacket from.
  • scapy_pkt – Instantiated Scapy Packet to use as a KNXPacket.
  • type – Type of frame to build. Ignored if _pkt set. Should be a value from SID dict imported from KNX Scapy implementation as a dict key, a string or as bytes.
  • kwargs – Any field to initialize when instantiating the frame, with format field_name=value.

Example of initialization:

pkt = KNXPacket(b"[...]") # From frame as a byte array
pkt = KNXPacket(type=SID.description_request) # From service id dict
pkt = KNXPacket(type="DESCRIPTION REQUEST") # From service id name
pkt = KNXPacket(type=b"") # From service id value
pkt = KNXPacket(type=SID.connect_request, communication_channel_id=2)
pkt = KNXPacket(scapy_pkt=KNX()/KNXDescriptionRequest()) # With Scapy Packet
pkt = KNXPacket() # Empty packet (just a KNX header)
set_type(ptype: object, cemi: object = None) → None

Format packet according to the specified type (service identifier).

Parameters:
  • ptype – Type of frame to build. Ignored if _pkt set. Should be a value from SID dict imported from KNX Scapy implementation as a dict key, a string or as bytes.
  • cemi – cEMI field type. Raises error if type does not have have a cEMI field, is ignored if there is no type given.
Raises:

BOFProgrammingError – if type is unknown or invalid or if cEMI is set but there is no cEMI field in packet type.

sid
type

Get information about the packet’s type (protocol-dependent).

Should be overriden in subclasses to match a protocol’s different types of packets. For instance, BOF’s packet for the KNX protocol (KNXPacket) returns the type of packet as a name, relying on its identifier fields. If identifier is 0x0203, pkt.type indicates that the packet is a DESCRIPTION REQUEST.

KNX messages

Module containing a set of functions to build predefined types of KNX messages. Functions in this module do not handle the network exchange, they just create ready-to-send packets.

Contents:

KNXnet/IP requests:
 Direct methods to create initialized requests from the standard.
CEMI:Methods to create specific type of cEMI messages (protocol-independent KNX messages).
bof.layers.knx.knx_messages.cemi_ack(knx_indiv_addr: str, seq_num: int = 0, knx_source: str = '0.0.0') → scapy.packet.Packet

Builds a KNX message (cEMI) to disconnect from an individual address.

Parameters:
  • knx_indiv_addr – KNX individual address of device (with format X.Y.Z)
  • seq_num – Sequence number to use, applies to cEMI when sequence_type is set to “numbered”. So far I haven’t seen seq_num > 0.
  • knx_source – KNX individual address to use as a source for the request. You should usually use the KNXnet/IP server’s individual address, but it works fine with 0.0.0.
Returns:

A raw cEMI object from Scapy’s implementation to be inserted in a KNXPacket object.

Raises:

BOFProgrammingError – if KNX addresses are invalid because the Scapy object does not allow that. You should change the field type if you want to set somethig else.

bof.layers.knx.knx_messages.cemi_connect(knx_indiv_addr: str, knx_source: str = '0.0.0') → scapy.packet.Packet

Builds a KNX message (cEMI) to connect to an individual address.

Parameters:
  • knx_indiv_addr – KNX individual address of device (with format X.Y.Z)
  • knx_source – KNX individual address to use as a source for the request. You should usually use the KNXnet/IP server’s individual address, but it works fine with 0.0.0.
Returns:

A raw cEMI object from Scapy’s implementation to be inserted in a KNXPacket object.

Raises:

BOFProgrammingError – if KNX addresses are invalid because the Scapy object does not allow that. You should change the field type if you want to set somethig else.

bof.layers.knx.knx_messages.cemi_dev_descr_read(knx_indiv_addr: str, seq_num: int = 0, knx_source: str = '0.0.0') → scapy.packet.Packet

Builds a KNX message (cEMI) to write a value to a group address.

Parameters:
  • knx_indiv_addr – KNX individual address of device (with format X.Y.Z)
  • seq_num – Sequence number to use, applies to cEMI when sequence_type is set to “numbered”. So far I haven’t seen seq_num > 0.
  • knx_source – KNX individual address to use as a source for the request. You should usually use the KNXnet/IP server’s individual address, but it works fine with 0.0.0.
Returns:

A raw cEMI object from Scapy’s implementation to be inserted in a KNXPacket object.

Raises:

BOFProgrammingError – if KNX addresses are invalid because the Scapy object does not allow that. You should change the field type if you want to set somethig else.

bof.layers.knx.knx_messages.cemi_disconnect(knx_indiv_addr: str, knx_source: str = '0.0.0') → scapy.packet.Packet

Builds a KNX message (cEMI) to disconnect from an individual address.

Parameters:
  • knx_indiv_addr – KNX individual address of device (with format X.Y.Z)
  • knx_source – KNX individual address to use as a source for the request. You should usually use the KNXnet/IP server’s individual address, but it works fine with 0.0.0.
Returns:

A raw cEMI object from Scapy’s implementation to be inserted in a KNXPacket object.

Raises:

BOFProgrammingError – if KNX addresses are invalid because the Scapy object does not allow that. You should change the field type if you want to set somethig else.

bof.layers.knx.knx_messages.cemi_group_write(knx_group_addr: str, value, knx_source: str = '0.0.0') → scapy.packet.Packet

Builds a KNX message (cEMI) to write a value to a group address.

Parameters:
  • knx_group_addr – KNX group address targeted (with format X/Y/Z) Group addresses are defined in KNX project settings.
  • value – Value to set the group address’ content to.
  • knx_source – KNX individual address to use as a source for the request. You should usually use the KNXnet/IP server’s individual address, but it works fine with 0.0.0.
Returns:

A raw cEMI object from Scapy’s implementation to be inserted in a KNXPacket object.

Raises:

BOFProgrammingError – if KNX addresses are invalid because the Scapy object does not allow that. You should change the field type if you want to set somethig else.

bof.layers.knx.knx_messages.cemi_property_read(object_type: int, property_id: int) → scapy.packet.Packet

Builds a KNX message (cEMI) to write a value to a group address.

Parameters:
  • object_type – Type of object to read, as defined in KNX Standard (and reproduce in Scapy’s KNX implementation).
  • property_id – Property to read, as defined in KNX Standard (and reproduce in Scapy’s KNX implementation).
Returns:

A raw cEMI object from Scapy’s implementation to be inserted in a KNXPacket object.

bof.layers.knx.knx_messages.configuration_ack(channel: int) → bof.layers.knx.knx_packet.KNXPacket

Creates a configuration ack to reply to avoid upsetting KNX servers.

bof.layers.knx.knx_messages.configuration_request(channel: int, cemi: scapy.packet.Packet) → bof.layers.knx.knx_packet.KNXPacket

Creates a configuration request with a specified cEMI message.

Parameters:
  • channel – The communication channel ID for the current KNXnet/IP connection. The channel is set by the server and returned in connect responses.
  • cemi – Protocol-independent KNX message inserted in the request. cEMI are created directly from Scapy’s CEMI object.
Returns:

A configuration request embedding a cEMI packet, as a KNXPacket.

bof.layers.knx.knx_messages.connect_request_management(knxnet: bof.layers.knx.knx_network.KNXnet = None) → bof.layers.knx.knx_packet.KNXPacket

Creates a connect request with device management connection type.

Parameters:knxnet – The KNXnet connection object to use. We only need the source parameter, please create an issue if you think that asking directly for the source instead is a better choice.
Returns:A management connect request as a KNXPacket.
bof.layers.knx.knx_messages.connect_request_tunneling(knxnet: bof.layers.knx.knx_network.KNXnet = None) → bof.layers.knx.knx_packet.KNXPacket

Creates a connect request with tunneling connection type.

Parameters:knxnet – The KNXnet connection object to use. We only need the source parameter, please create an issue if you think that asking directly for the source instead is a better choice.
Returns:A tunneling connect request as a KNXPacket.
bof.layers.knx.knx_messages.description_request(knxnet: bof.layers.knx.knx_network.KNXnet = None) → bof.layers.knx.knx_packet.KNXPacket

Creates a basic description request with appropriate source.

Parameters:knxnet – The KNXnet connection object to use. We only need the source parameter, please create an issue if you think that asking directly for the source instead is a better choice.
Returns:A description request as a KNXPacket.
bof.layers.knx.knx_messages.disconnect_request(knxnet: bof.layers.knx.knx_network.KNXnet = None, channel: int = 1) → bof.layers.knx.knx_packet.KNXPacket

Creates a disconnect request to close connection on given channel.

Parameters:
  • knxnet – The KNXnet connection object to use. We only need the source parameter, please create an issue if you think that asking directly for the source instead is a better choice.
  • channel – The communication channel ID for the current KNXnet/IP connection. The channel is set by the server and returned in connect responses.
Returns:

A disconnect request as a KNXPacket.

bof.layers.knx.knx_messages.search_request(knxnet: bof.layers.knx.knx_network.KNXnet = None) → bof.layers.knx.knx_packet.KNXPacket

Creates a basic search request with appropriate source.

Parameters:knxnet – The KNXnet connection object to use. We only need the source parameter, please create an issue if you think that asking directly for the source instead is a better choice.
Returns:A search request as a KNXPacket.
bof.layers.knx.knx_messages.tunneling_ack(channel: int, sequence_counter: int) → bof.layers.knx.knx_packet.KNXPacket

Creates a tunneling ack to reply to avoid upsetting KNX servers.

bof.layers.knx.knx_messages.tunneling_request(channel: int, sequence_counter: int, cemi: scapy.packet.Packet) → bof.layers.knx.knx_packet.KNXPacket

Creates a tunneling request with a specified cEMI message.

Parameters:
  • channel – The communication channel ID for the current KNXnet/IP connection. The channel is set by the server and returned in connect responses.
  • sequence_counter – Sequence number to use for the request, same principle as TCP’s sequence numbers.
  • cemi – Protocol-independent KNX message inserted in the request. cEMI are created directly from Scapy’s CEMI object.
Returns:

A tunneling request embedding a cEMI packet, as a KNXPacket.

KNX features

This module contains a set of higher-level functions to interact with devices using KNXnet/IP without prior knowledge about the protocol.

Contents:

KNXDevice:An object representation of a KNX device with multiple properties. Only supports KNXnet/IP servers so far, but will be extended to KNX devices.
Features:High-level functions to interact with a device: search, discover, read, write, etc.

Relies on KNX Standard v2.1

bof.layers.knx.knx_feature.GROUP_ADDR(x: int) → str

Converts an int to KNX group address.

bof.layers.knx.knx_feature.INDIV_ADDR(x: int) → str

Converts an int to KNX individual address.

bof.layers.knx.knx_feature.IS_IP(ip: str)

Check that ip is a valid IPv4 address.

class bof.layers.knx.knx_feature.KNXDevice(name: str, ip_address: str, port: int, knx_address: str, mac_address: str, multicast_address: str = '224.0.23.12', serial_number: str = '')

Bases: object

Object representing a KNX device.

Data stored to the object is the one returned by SEARCH RESPONSE and DESCRIPTION RESPONSE messages, stored to public attributes:

Device name, IPv4 address, KNXnet/IP port, KNX individual address, MAC
address, KNX multicast address used, device serial number.

This class provides two factory class methods to build a KNXDevice object from search responses and description responses.

The information gathered from devices may be completed, improved later.

classmethod init_from_description_response(response: bof.layers.knx.knx_packet.KNXPacket, source: tuple)

Set appropriate values according to the content of description response.

Parameters:
  • response – Description Response provided by a device as a KNXPacket.
  • source – Source of the response, usually provided in KNXnet’s receive() and sr() return values.
Returns:

A KNXDevice object.

Usage example:

response, source = knxnet.sr(description_request(knxnet))
device = KNXDevice.init_from_description_response(response, source)
classmethod init_from_search_response(response: bof.layers.knx.knx_packet.KNXPacket)

Set appropriate values according to the content of search response.

Parameters:response – Search Response provided by a device as a KNXPacket.
Returns:A KNXDevice object.

Uage example:

responses = KNXnet.multicast(search_request(), (ip, port))
for response, source in responses:
  device = KNXDevice.init_from_search_response(KNXPacket(response))
bof.layers.knx.knx_feature.discover(ip: str, port: int = 3671) → bof.layers.knx.knx_feature.KNXDevice

Returns discovered information about a device. So far, only sends a DESCRIPTION REQUEST and uses the DESCRIPTION RESPONSE. This function may evolve to gather data on underlying devices.

Parameters:
  • ip – IPv4 address of KNX device.
  • port – KNX port, default is 3671.
Returns:

A KNXDevice object.

Raises:
bof.layers.knx.knx_feature.group_write(ip: str, knx_group_addr: str, value, port: int = 3671) → None

Writes value to KNX group address via the server at address ip. We first need to establish a tunneling connection so that we can reach underlying device groups.

Parameters:
  • ip – IPv4 address of KNX device.
  • knx_group_addr – KNX group address targeted (with format X/Y/Z) Group addresses are defined in KNX project settings.
  • value – Value to set the group address’ content to.
  • port – KNX port, default is 3671.
Returns:

Nothing

Raises:
bof.layers.knx.knx_feature.individual_address_scan(ip: str, addresses: object, port: str = 3671) → bool

Scans KNX gateway to find if individual address exists. We first need to establish a tunneling connection and use cemi connect messages on each address to find out which one responds. As the gateway will answer positively for each address (L_data.con), we also wait for L_data.ind which seems to indicate existing addresses.

Parameters:
  • ip – IPv4 address of KNX device.
  • address – KNx individual addresses as a string or a list.
  • port – KNX port, default is 3671.
Returns:

A list of existing individual addresses.

Raises:

BOFProgrammingError – if IP is invalid.

Does not work (yet) for KNX gateways’ individual addresses. Not reliable: Crashes after 60 addresses… Plz send help ;_; Also requires heavy refactoring after fixing issues.

bof.layers.knx.knx_feature.line_scan(ip: str, line: str = '', port: int = 3671) → list

Scans KNX gateway to find existing individual addresses on a line. We first need to establish a tunneling connection and use cemi connect messages on each address to find out which one responds. As the gateway will answer positively for each address (L_data.con), we also wait for L_data.ind which seems to indicate existing addresses.

Parameters:
  • ip – IPv4 address of KNX device.
  • line – KNX backbone to scan (default == empty == scan all lines from 0.0.0 to 15.15.255)
  • port – KNX port, default is 3671.
Returns:

A list of existing individual addresses on the KNX bus.

Methods require smart detection of line, so far only line 1.1.X is supported and it is dirty.

bof.layers.knx.knx_feature.search(ip: object = '224.0.23.12', port: int = 3671) → list

Search for KNX devices on an network using multicast. Sends a SEARCH REQUEST and expects one SEARCH RESPONSE per device.

Parameters:
  • ip – Multicast IPv4 address. Default value is default KNXnet/IP multicast address 224.0.23.12.
  • port – KNX port, default is 3671.
Returns:

The list of responding KNXnet/IP devices in the network as KNXDevice objects.

Raises:

BOFProgrammingError – if IP is invalid.