Introduction

eleVADR is a network security analysis engine designed to assess Operational Technology (OT) systems by transforming raw PCAP traffic into actionable security intelligence.

The overall goal is to support the automated analysis of data to allow anyone operating OT to make informed decisions about their network security posture.

It is currently designed with ease of use in mind by leveraging containerization.

Core Architecture

EleVADR is split into a frontend and a backend component.

Backend

The backend is a Python application that is used to run Zeek to collect network data and analyze it with numpy, pandas, and generate a JSON report.

The backend collects a PCAP file from the frontend and spawns a Zeek process to collect the network logs. Those logs are then converted to pandas using zat and then custom analytics are written to create report modules.

Zeek Script Integration

eleVADR relies on Zeek to parse raw packet captures (PCAP) and generate structured log files. To support advanced physical asset discovery and network logical boundary mapping, the backend executes custom Zeek scripts located in the backend/src/app/data/zeek_scripts/ directory during the analysis pass.

1. MAC Logging Script (mac_logging.zeek)

By default, Zeek tracks connections at Layer 3 (IP addresses) and Layer 4 (Ports). However, mapping physical hardware manufacturers and establishing true hardware identities requires Layer 2 MAC addresses.

The mac_logging.zeek script hooks into Zeek’s connection state removal pipeline and appends Layer 2 source and destination MAC addresses to the connection metadata.

  • Log Hook: connection_state_remove(c: connection)

  • Extended Fields:

    • orig_l2_addr (String): The hardware MAC address of the initiator (Originator).

    • resp_l2_addr (String): The hardware MAC address of the responder.

These fields are mapped to src_endpoint.mac and dst_endpoint.mac in the backend Pandas DataFrame, allowing the engine to aggregate manufacturer profiles via OUI lookup.


2. Known Services Script (known_services.zeek) DEPRECATED

The known_services.zeek script tracks active, validated services communicating on the network. For the purposes of eleVADR, an active service is defined under the following lifecycle rules:

  • TCP Connections: Requires a completed TCP handshake (SYN+ACK) or an established state where server payload transfer is recognized. Passive, unacknowledged connection attempts (e.g., ports scanned but not open) are ignored.

  • UDP Connections: Requires at least one packet from the server side confirming that the target port is actively responding, unless the configuration option Known::service_udp_requires_response is explicitly disabled.

  • ICMP Traffic: Excluded from service classification.

Configurable Options
  • service_tracking: Defaults to LOCAL_HOSTS. Limits active service profiling to local subnets to protect memory overhead.

  • service_store_expiry: Defaults to 1day. Forces periodic state evaluation to prevent stale connection maps on continuous captures.

PcapParser

The parsing is done through PcapParser.

class PcapParser(file_path_info)[source]

Process PCAP files using Zeek and create traffic dataframe.

parse()[source]

Execute Zeek processing and load results into dataframes.

Return type:

None

zeekify()[source]

Execute PCAP analysis using Zeek in a single high-performance pass.

Return type:

None

The data is then passed to Analyzer.

Analyzer

The Analyzer is responsible for processing the data and running analytics on the data.

class Analyzer(traffic_df, endpoints_df, services_df, file_path_info)[source]

Enrich traffic data with analysis and generate endpoint/service dataframes.

traffic_df_processing()[source]

Add IP, conn type, direction, subnet, service info to traffic concurrently.

Return type:

None

endpoints_df_processing()[source]

Create endpoint DataFrame with device info, IPs, services, OT label.

Return type:

None

services_df_processing()[source]

Prepare self.services_df from self.traffic_df.

  • copy only the service‑related columns,

  • convert category fields to comma‑separated strings,

  • deduplicate by service.name keeping the row with the most populated service.risk_categories.

Return type:

None

ot_cross_segment_communication_count()[source]

Count OT devices communicating across network segments.

Return type:

int

service_counts_in_traffic()[source]

Count occurrences of known and unknown services.

Return type:

dict[str, object]

It is also responsible for responding to the frontend to filter the data for unique views.

class Analyzer(traffic_df, endpoints_df, services_df, file_path_info)[source]

Enrich traffic data with analysis and generate endpoint/service dataframes.

connection_lines_filtered(*, ip=None, src_ip=None, dst_ip=None, subnet=None, src_subnet=None, dst_subnet=None, manufacturer=None, service_name=None, connection_state=None, direction=None, success=None, is_ot=None, limit=500)[source]

Return connection rows matching the provided filter set.

Return type:

list[dict[str, object]]

devices_filtered(*, manufacturer=None, subnet=None, service_name=None, is_ot=None, is_edge=None)[source]

Return device rows matching the provided filters.

Return type:

list[dict[str, object]]

services_filtered(*, subnet=None, manufacturer=None, device_ip=None, risk_category=None, service_name=None)[source]

Return service rows matching the provided filters.

Return type:

list[dict[str, object]]

Structure

All of the findings are summarized into a JSON report.

class Report(analyzer, report_id=None)[source]

Main report orchestrator class.

Instantiates module classes and aggregates their data into a unified report structure.

The report is split into different Modules. See Report Modules for the list of modules that are available.

Frontend

The frontend is a React application that is used to interact with the Python backend component. The frontend takes the JSON report that the backend generates and displays it in an interactive format, allowing the user to filter and drill-down into the data.

The frontend is organized into sections that mirror what the backend generates.

Upload Form

The upload form is where a user can upload a PCAP to run the analysis and download the associated JSON report. There is also an option to upload a JSON report to view it.

Report

The frontend is a React application that is used to interact with the Python backend component. The frontend takes the JSON report that the backend generates and displays it in an interactive format, allowing the user to filter and drill-down into the data.

The frontend is organized into the following sections.

Executive Summary

The executive summary is a high-level set of key takeaways from the report. It can include alerts defined by any class that inherits from DetectionModule and implements executive_summary

class DetectionModule(report_modules)[source]

Abstract base class for detections based on report modules.

abstract property executive_summary: str

Return the human-readable finding summary.

That list is currently:

class SuspiciousOutboundConnectionsDetection(report_modules)[source]

Detection: OT devices making suspicious outbound connections.

class OTcrossSegmentDetection(report_modules)[source]

Detection: OT devices communicating across network segments.

Report Modules

The report modules are panels that aim to answer specific analytic questions. Each module inherits from ReportModule and implements generate_data

That list is currently:

class DevicePanelModule(analyzer)[source]

Device statistics module.

class ServicePanelModule(analyzer)[source]

Service overview statistics module.

class ServiceRiskBreakdownModule(analyzer)[source]

Service risk categorization module.

class ServiceCountModule(analyzer)[source]

Service connection counts module.

class RiskBasisBreakdownModule(analyzer)[source]

Breaks down observed services by risk basis (Observed vs Credible).

Observed - directly used in compromises with evidence. Credible - known threat actor TTPs, not necessarily active now.

class ExposureBreakdownModule(analyzer)[source]

Breaks down observed services by environment/exposure classification.

External - internet-facing or reachable from outside the org. Cross-Zone - traverses trust boundaries without broker/inspection. Internal - confined within a protected cell/zone.

class ConnectionSuccessModule(analyzer)[source]

Connection success vs failure breakdown based on Zeek conn.log.

class ProtocolPostureModule(analyzer)[source]

Breaks down observed services by protocol posture.

Inherently Risky - clear-text or long-standing exploitation history. Conditionally Risky - secure if hardened, risky when exposed or tunnelled.

class SuspiciousOutboundConnectionsModule(analyzer)[source]

Suspicious outbound connections from OT devices module.

class DevicesModule(analyzer, name, device_filter)[source]

Generic devices module to list devices based on a filter.

class OTServicesModule(analyzer)[source]

OT services/protocols module.