mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 14:48:21 +00:00

This is based on commit 2731def9159247e6da8a3191783c89683363689c from the zeek-docs repo.
170 lines
7.6 KiB
ReStructuredText
170 lines
7.6 KiB
ReStructuredText
|
|
.. _packet-analysis:
|
|
|
|
===============
|
|
Packet Analysis
|
|
===============
|
|
|
|
.. TODO: integrate BoZ revisions
|
|
|
|
.. rst-class:: opening
|
|
|
|
The Packet Analysis plugin architecture handles parsing of packet headers at
|
|
layers below Zeek's existing Session analysis. In particular, this allows to
|
|
add new link and network layer protocols to Zeek.
|
|
This section provides an overview of the underlying architecture as well as
|
|
an example-based walk-through. For further details, consider to take a look
|
|
at the built-in packet analyzers as well as the packet analyzer tests.
|
|
|
|
The Flow of Packets
|
|
===================
|
|
|
|
The basic packet flow through Zeek is as follows. First, an ``IOSource`` deals with
|
|
getting the packets into Zeek. While an ``IOSource`` can be used to interface all
|
|
sorts of capturing mechanisms, the default source makes use of libpcap to either
|
|
read PCAP files or sniff an interface. Once acquired, a packet is handed into
|
|
the packet analysis and processed layer by layer.
|
|
|
|
.. figure:: packet-analysis-pdu.svg
|
|
:width: 600
|
|
:align: center
|
|
:alt: Nesting of Protocol Data Units (PDUs)
|
|
:target: ../_images/netcontrol-openflow.png
|
|
|
|
Nesting of Protocol Data Units (PDUs).
|
|
|
|
At the lower layers, Protocol Data Units (PDUs) typically consist of a header
|
|
and a payload, where the payload is the next layer's PDU and the header carries
|
|
a numeric identifier that determines the encapsulated protocol (see figure
|
|
above, where "ID" denotes the location of such a numeric protocol identifier
|
|
within the header).
|
|
|
|
Each packet analyzer parses the packet's header according to the implemented
|
|
protocol, determines a suitable analyzer for the encapsulated protocol and hands its
|
|
payload to that next analyzer. Once the IP layer is reached, packet analysis is
|
|
finished and Zeek continues by constructing a session for the observed
|
|
connection. After session analysis, which includes processing of TCP and UDP,
|
|
the packet continues its journey into the land of application layer analyzers.
|
|
There, Dynamic Protocol Detection is used to determine the application layer
|
|
protocol and continue the analysis.
|
|
|
|
Packet Analyzer Configuration
|
|
=============================
|
|
|
|
The following script shows an example configuration of the Ethernet packet
|
|
analyzer:
|
|
|
|
.. literalinclude:: packet-analysis-1-ethernet.zeek
|
|
:caption:
|
|
:language: zeek
|
|
:linenos:
|
|
:tab-width: 4
|
|
|
|
Within :zeek:see:`zeek_init`, various EtherType-to-PacketAnalyzer mappings are
|
|
registered by using :zeek:see:`PacketAnalyzer::register_packet_analyzer`. For
|
|
example, for EtherType ``0x8864``, the packet's payload is passed to the PPPoE
|
|
analyzer.
|
|
|
|
The ``default_analyzer`` analyzer specifies which packet analyzer to use if
|
|
none of the mappings matched. In case of Ethernet, we try to fall back to IP.
|
|
|
|
Furthermore, Ethernet needs to handle different types of frames, with three of
|
|
them identified using the first payload bytes (see `Wikipedia
|
|
<https://en.wikipedia.org/wiki/Ethernet_frame#Types>`_).
|
|
As the EtherType needs to be interpreted with respect to the frame type in these
|
|
cases, the Ethernet analyzer provides three additional configuration parameters,
|
|
``snap_analyzer``, ``novell_raw_analyzer``, and ``llc_analyzer``.
|
|
to configure analyzers that handle the different frame types.
|
|
|
|
.. note::
|
|
|
|
There are a few conventions involved here:
|
|
|
|
* The name of the module is expected to be ``PacketAnalyzer::<analyzer's canonical name>``.
|
|
* The default analyzer is expected to be named ``default_analyzer``.
|
|
|
|
Packet analysis starts at a root analyzer that dispatches based on the link
|
|
types obtained from the ``IOSource``. Accordingly
|
|
:doc:`/scripts/base/packet-protocols/root/main.zeek` contains the following
|
|
call to integrate the Ethernet analyzer:
|
|
|
|
.. code-block:: zeek
|
|
|
|
PacketAnalyzer::register_packet_analyzer(PacketAnalyzer::ANALYZER_ROOT, DLT_EN10MB, PacketAnalyzer::ANALYZER_ETHERNET);
|
|
|
|
Packet Analyzer API
|
|
===================
|
|
|
|
Just like for other parts of Zeek, a plugin may provide a packet analyzer by
|
|
adding a packet analysis component that instantiates an analyzer. The packet
|
|
analyzer itself is implemented by inheriting from
|
|
``zeek::packet_analysis::Analyzer`` and overriding the ``AnalyzePacket()``
|
|
method. The following is an excerpt from a test case that shows the exemplary
|
|
analysis of LLC:
|
|
|
|
.. literalinclude:: packet-analysis-2-llc.cc
|
|
:caption:
|
|
:language: C++
|
|
:linenos:
|
|
:tab-width: 4
|
|
|
|
First, we verify that the size of the packet matches what we expect. If that is
|
|
not the case, we create a weird using the ``Packet`` object that is passed along
|
|
the chain of analyzers. To signal that the analysis failed, the method returns
|
|
``false``. For valid packets, we just read some protocol-specific values. As of
|
|
now, there is no mechanism to pass extracted meta data on to other analyzers.
|
|
While it is possible to trigger events that receive these values as parameters,
|
|
keep in mind that handling events for every packet can be *extremely
|
|
expensive*. However, for our test case we defined an event as follows in a
|
|
separate ``.bif`` file:
|
|
|
|
.. code-block:: zeek
|
|
|
|
event llc_demo_message%(dsap: count, ssap: count, control: count%);
|
|
|
|
Before we can expect the event to be generated, we need to integrate the
|
|
analyzer. The configuration might be included in the scripts that are shipped
|
|
with the packet analyzer. For example, one could add a new EtherType by
|
|
adding a call to :zeek:see:`PacketAnalyzer::register_packet_analyzer` from
|
|
within a :zeek:see:`zeek_init` event handler.
|
|
For the LLC example we redefine one of the additional constants:
|
|
|
|
.. code-block:: zeek
|
|
|
|
redef PacketAnalyzer::ETHERNET::llc_analyzer = PacketAnalyzer::ANALYZER_LLC_DEMO;
|
|
|
|
In this example, packet analysis as well as all further analysis ends with the
|
|
LLC analyzer. The ``ForwardPacket()`` method can be used to pass data to another
|
|
packet analyzer. The method takes a pointer to the beginning of the data to
|
|
process (usually the start of the payload in the current context), the length of
|
|
the data to process, a pointer to the ``Packet`` object and an identifier. The
|
|
identifier would be used to lookup the next analyzer based on which other
|
|
analyzers were previously associated with LLC as a parent analyzer in a call to
|
|
:zeek:see:`PacketAnalyzer::register_packet_analyzer`. If there is no
|
|
previously-registered analyzer that matches the identifier, it will fall back
|
|
to the ``default_analyzer`` if available.
|
|
|
|
In case a packet analyzer requires initialization, e.g., reading additional
|
|
configuration values from script-land, this can be implemented by overriding
|
|
the ``Initialize()`` method. When overriding this method, always make sure to
|
|
call the base-class version to ensure proper initialization.
|
|
|
|
With the addition of the transport-layer analyzer to the packet analysis framework,
|
|
it's now possible to register for ports as the identifier. This is natural, given
|
|
that a port number is just another numeric identifier for moving from one
|
|
protocol to another. Packet analyzers should call
|
|
``PacketAnalyzer::register_for_port`` or ``PacketAnalyzer::register_for_ports``
|
|
to ensure that the ports are also stored in the global ``Analyzer::ports`` table for
|
|
use with BPF filters.
|
|
|
|
The packet analysis framework also provides a ``register_protocol_detection``
|
|
method that is used to register a packet analyzer to use protocol detection
|
|
instead of using a numeric identifier. Analyzers can use this method and then
|
|
override ``Analyzer::DetectProtocol`` to search the packet data for byte strings
|
|
or other markers to detect whether a protocol exists in the data. This is similar
|
|
to how DPD works for non-packet analyzers, but is not limited to pattern matching.
|
|
|
|
.. note::
|
|
|
|
When writing your own packet analyzer, take a look into the existing code to
|
|
identify idiomatic ways to handle tasks like looking up configuration values.
|