zeek-iec104 is a Zeek plugin written using
Spicy for parsing and
logging fields used by the IEC 104 protocol.
- More ASDU type parsing implemented
- APDU contents logged to log files (only if
log.zeekscript is loaded)- For unknown (unimplemented) ASDU types hex dump of content is logged
- Zeek events generated only for high-level units (i.e., no separate events
for internal units like
CP56Time2aor bit-fields):- Control part of an APCI (S, U and I format headers)
- Identification part of an ASDU
- Object specific information of an ASDU
- Easy to correlate APDU sub-part event generation, with the parts mentioned
above in sequence:
- APCI control field
- ASDU common part (Cause of Transmission, address fields)
- Object-specific ASDU part (object information)
- All events carry communication direction information
print.zeekscript to dump APCIs to console (good for analyzing captured traffic with no need to correlate different log files)- Proper parsing (and logging) of R32 (float) and VTI (7-bit signed integer) values
- Logging in TSV (Zeek default) or JSON formats
To build and install the parser into Zeek the following can be used:
cmake . && make installAfter successful installation the following command:
zeek -NN | grep IEC104should have output similar to this:
[Analyzer] spicy_iec104 (ANALYZER_SPICY_IEC104, enabled)
The plugin is implemented using following files:
analyzer/zeek_iec104.spicy- Spicy protocol analyzer.
analyzer/iec104.evt- Event descriptions for Zeek integration.
scripts/iec104.zeek- Zeek side definitions of structures exported from
Spicy and analyzer registration for IEC-104 default port
2404/tcp. scripts/common.zeek- Shared helper code
scripts/log_telemetry.zeek- Emits telemetry/measured-value style information objects into
iec104_telemetry.log. scripts/log_cmd.zeek- Command transaction logging with correlation of
Act/Actcon/ActTerminto:iec104_write.log(writes/commands)iec104_query.log(interrogation/counter interrogation)iec104_ctrl.log(clock sync/test/reset)iec104_param.log(parameterization)
scripts/log_read.zeek- Correlates
C_RD_NA_1read request/response pairs intoiec104_read.log. scripts/log_file.zeek- Summarizes file transfer segment bursts
(
F_SG_NA_1) and flushes on end markers or timeout intoiec104_file.log. scripts/print.zeek- Support script that prints communication in
sequential manner in a way that can be easily cross-checked with other
tools (e.g., Wireshark). This is how the initial output of analyzing
testing/Traces/first/iec104.pcapusing this script looks like:1372918997.053303 10.20.102.1:46413 -> 10.20.100.108:2404, U TESTFR act 1372918997.053845 10.20.102.1:46413 <- 10.20.100.108:2404, U TESTFR con 1372918997.306461 10.20.102.1:46413 -> 10.20.100.108:2404, U STARTDT act 1372918997.307014 10.20.102.1:46413 <- 10.20.100.108:2404, U STARTDT con 1372918997.321659 10.20.102.1:46413 -> 10.20.100.108:2404, I ssn:0, rsn:0 ASDU Act OA=0 CA=10, C_IC_NA_1 obj_addr=0 QOI=20 1372918997.323589 10.20.102.1:46413 <- 10.20.100.108:2404, I ssn:0, rsn:0 ASDU Init OA=0 CA=10, M_EI_NA_1 obj_addr=0 COI=0 LPC=F 1372918997.331734 10.20.102.1:46413 -> 10.20.100.108:2404, I ssn:1, rsn:1 ASDU Act OA=0 CA=10, C_IC_NA_1 obj_addr=0 QOI=20 1372918997.333710 10.20.102.1:46413 <- 10.20.100.108:2404, I ssn:1, rsn:1 ASDU Actcon OA=0 CA=10, C_IC_NA_1 obj_addr=0 QOI=20 1372918997.333710 10.20.102.1:46413 <- 10.20.100.108:2404, I ssn:2, rsn:1 ASDU Inrogen OA=0 CA=10, M_SP_NA_1 obj_addr=1 SIQ=[spi=F, bl=F, sb=F, nt=F, iv=F] ASDU Inrogen OA=0 CA=10, M_SP_NA_1 obj_addr=2 SIQ=[spi=F, bl=F, sb=F, nt=F, iv=F] ASDU Inrogen OA=0 CA=10, M_SP_NA_1 obj_addr=3 SIQ=[spi=F, bl=F, sb=F, nt=F, iv=F] ASDU Inrogen OA=0 CA=10, M_SP_NA_1 obj_addr=4 SIQ=[spi=F, bl=F, sb=F, nt=F, iv=F] scripts/seq.zeek- APCI Send and Receive sequence number tracking.
The Spicy protocol analyzer and the corresponding Zeek code has support for the following ASDU information object types:
| Reference | TypeID | Implemented |
|---|---|---|
M_SP_NA_1 | 1 | yes |
M_SP_TA_1 | 2 | yes |
M_DP_NA_1 | 3 | yes |
M_DP_TA_1 | 4 | yes |
M_ST_NA_1 | 5 | yes |
M_ST_TA_1 | 6 | yes |
M_BO_NA_1 | 7 | yes |
M_BO_TA_1 | 8 | yes |
M_ME_NA_1 | 9 | yes |
M_ME_TA_1 | 10 | yes |
M_ME_NB_1 | 11 | yes |
M_ME_TB_1 | 12 | yes |
M_ME_NC_1 | 13 | yes |
M_ME_TC_1 | 14 | yes |
M_IT_NA_1 | 15 | yes |
M_IT_TA_1 | 16 | yes |
M_EP_TA_1 | 17 | yes |
M_EP_TB_1 | 18 | yes |
M_EP_TC_1 | 19 | yes |
M_PS_NA_1 | 20 | yes |
M_ME_ND_1 | 21 | yes |
M_SP_TB_1 | 30 | yes |
M_DP_TB_1 | 31 | yes |
M_ST_TB_1 | 32 | yes |
M_BO_TB_1 | 33 | yes |
M_ME_TD_1 | 34 | yes |
M_ME_TE_1 | 35 | yes |
M_ME_TF_1 | 36 | yes |
M_IT_TB_1 | 37 | yes |
M_EP_TD_1 | 38 | yes |
M_EP_TE_1 | 39 | yes |
M_EP_TF_1 | 40 | yes |
C_SC_NA_1 | 45 | yes |
C_DC_NA_1 | 46 | yes |
C_RC_NA_1 | 47 | yes |
C_SE_NA_1 | 48 | yes |
C_SE_NB_1 | 49 | yes |
C_SE_NC_1 | 50 | yes |
C_BO_NA_1 | 51 | yes |
C_SC_TA_1 | 58 | yes |
C_DC_TA_1 | 59 | yes |
C_RC_TA_1 | 60 | yes |
C_SE_TA_1 | 61 | yes |
C_SE_TB_1 | 62 | yes |
C_SE_TC_1 | 63 | yes |
C_BO_TA_1 | 64 | yes |
M_EI_NA_1 | 70 | yes |
C_IC_NA_1 | 100 | yes |
C_CI_NA_1 | 101 | yes |
C_RD_NA_1 | 102 | yes |
C_CS_NA_1 | 103 | yes |
C_TS_NA_1 | 104 | |
C_RP_NA_1 | 105 | yes |
C_CD_NA_1 | 106 | |
C_TS_TA_1 | 107 | yes |
P_ME_NA_1 | 110 | yes |
P_ME_NB_1 | 111 | yes |
P_ME_NC_1 | 112 | yes |
P_AC_NA_1 | 113 | yes |
F_FR_NA_1 | 120 | yes |
F_SR_NA_1 | 121 | yes |
F_SC_NA_1 | 122 | yes |
F_LS_NA_1 | 123 | yes |
F_AF_NA_1 | 124 | yes |
F_SG_NA_1 | 125 | yes |
F_DR_TA_1 | 126 | |
F_SC_NB_1 | 127 |
This repository ships a set of Zeek scripts that turn the Spicy-generated IEC-104 events into a number of dedicated log files.
The log files are written in Zeek’s default TSV format unless you enable JSON
logging (e.g., via Zeek’s LogAscii::use_json).
Many logs share the following concepts:
ts: event/transaction start time as seen by Zeek.uid: Zeek connection UID (useful for joining withconn.log).id: Zeek 4-tuple record (orig_h,orig_p,resp_h,resp_p).is_orig: Zeek’s direction flag for the event (originator -> responder).asdu_type: IEC-104 ASDU Type ID (printed as the enum name, e.g.C_SC_NA_1).cot: Cause of Transmission (enum, e.g.Act,Actcon,Spont,Inrogen, …).ca: “Common Address of ASDU”.ioa: “Information Object Address” (object address).
Unless noted otherwise, optional fields are omitted (empty in TSV, absent in JSON) when the value is not available for the particular ASDU type or phase.
The command-oriented logs (iec104_write, iec104_query, iec104_ctrl,
iec104_param) correlate the IEC-104 activation sequence:
Act(activation)Actcon(activation confirmation)ActTerm(activation termination)
Correlation is performed per connection and per command “shape” (ASDU type,
addressing, value, qualifiers, etc.) using FIFO assumptions (i.e., the first
Actcon is matched to the first pending Act of the same command “shape”).
These logs expose:
actcon_ts,actterm_ts: timestamps of the observed phases (if any).duration: typicallyactterm_ts - ts(only present if both exist).missing: comma-separated list of missing phases (e.g.,Actcon,ActTerm).error/error_reason: derived error signal and reasons; reasons are comma-separated, examples:negative_confirmation@Actcon(PN flag set)test_frame@Act(TEST flag set)cot=UnkType@Actcon(hard error COT values)
status: synthesized outcome string:ok: all expected phases observed, no errorok_broadcast: broadcast CA (65535) where confirmations are not expectedincomplete: missing phases but no timeout triggered yeterror: error conditions observedtimeout(request),timeout(Actcon,ActTerm), …: timed-out with missing phases
The read log (iec104_read.log) correlates request/response pairs for C_RD_NA_1.
The following sections document each log file (path and column meanings).
This log emits one entry per telemetry object (e.g., single-point or measured values). It focuses on “what value was reported” plus quality and optional timestamps.
Columns:
| Field | Type | Optional | Meaning |
|---|---|---|---|
| ts | time | no | Time when Zeek processed the information object. |
| uid | string | no | Zeek connection UID. |
| id | conn_id | no | Zeek connection 4-tuple. |
| asdu_type | IEC104TypeID | no | ASDU type of the current ASDU context. |
| cot | IEC104CoT | no | Cause of transmission of the current ASDU context. |
| ca | count | no | Common address (CA). |
| ioa | count | no | Information object address. |
| value | string | yes | Human-readable value (type-dependent; e.g. "T", "0xdeadbeef", "12.34"). |
| spi | bool | yes | Single-point information state (for M_SP_*). |
| bl | bool | yes | “Blocked” quality flag (when available). |
| sb | bool | yes | “Substituted” quality flag (when available). |
| nt | bool | yes | “Not topical” quality flag (when available). |
| iv | bool | yes | “Invalid” quality flag (when available). |
| ov | bool | yes | “Overflow” quality flag (for QDS-based types). |
| cp24 | string | yes | CP24Time2a formatted string (if present in the IO). |
| cp56 | string | yes | CP56Time2a formatted string (if present in the IO). |
Notes:
- For ASDU types that do not carry quality flags, the respective fields may be unset.
- CP24/CP56 are rendered as strings (see
common.zeek), e.g.- CP24:
min=... sec=... msec=... iv=T/F - CP56:
y=... mon=... day=... h=... min=... sec=... msec=... iv=T/F
- CP24:
Tracks write-like command transactions (e.g., single command, double command,
set-point, bitstring command), correlating Act / Actcon / ActTerm.
Columns:
| Field | Type | Optional | Meaning |
|---|---|---|---|
| ts | time | no | Transaction start (usually time of Act). |
| uid | string | no | Connection UID. |
| id | conn_id | no | Connection 4-tuple. |
| is_orig | bool | no | Direction of the phase that started/anchored the transaction. |
| action | string | no | Normalized action name (e.g. single_write, double_write, setpoint_write, bitstring_write, step_write). |
| asdu_type | IEC104TypeID | no | ASDU type. |
| cot | IEC104CoT | no | COT of the anchored phase (typically Act). |
| ca | count | no | Common address. |
| ioa | count | yes | Information object address (when applicable). |
| value | string | yes | Normalized value representation (type-dependent). |
| se | bool | yes | Select/Execute flag (where available). |
| qualifier | count | yes | Command qualifier (e.g., QU/QL depending on command type). |
| actcon_ts | time | yes | Timestamp when Actcon was seen. |
| actterm_ts | time | yes | Timestamp when ActTerm was seen. |
| duration | interval | yes | actterm_ts - ts when both are present. |
| missing | string | yes | Missing phases (comma-separated). |
| error | bool | yes | Derived error indicator. |
| error_reason | string | yes | Comma-separated error reasons. |
| status | string | yes | Outcome (ok, incomplete, error, timeout(...), ok_broadcast). |
Tracks interrogation-like transactions.
Columns:
| Field | Type | Optional | Meaning |
|---|---|---|---|
| ts | time | no | Transaction start (usually time of Act). |
| uid | string | no | Connection UID. |
| id | conn_id | no | Connection 4-tuple. |
| is_orig | bool | no | Direction of the anchored phase. |
| action | string | no | interrogation or counter_interrogation. |
| asdu_type | IEC104TypeID | no | ASDU type. |
| cot | IEC104CoT | no | COT of anchored phase. |
| ca | count | no | Common address. |
| ioa | count | yes | Information object address (when present). |
| qoi | count | yes | Qualifier of interrogation (for C_IC_NA_1). |
| qcc_rqt | count | yes | Counter interrogation request (RQT) (for C_CI_NA_1). |
| qcc_frz | count | yes | Counter freeze (FRZ) (for C_CI_NA_1). |
| actcon_ts | time | yes | Timestamp when Actcon was seen. |
| actterm_ts | time | yes | Timestamp when ActTerm was seen. |
| duration | interval | yes | actterm_ts - ts when both are present. |
| missing | string | yes | Missing phases. |
| error | bool | yes | Derived error indicator. |
| error_reason | string | yes | Comma-separated error reasons. |
| status | string | yes | Outcome. |
Tracks control-type operations such as clock synchronization, test, and reset process.
Columns:
| Field | Type | Optional | Meaning |
|---|---|---|---|
| ts | time | no | Transaction start (usually time of Act). |
| uid | string | no | Connection UID. |
| id | conn_id | no | Connection 4-tuple. |
| is_orig | bool | no | Direction of anchored phase. |
| action | string | no | clock_sync, test, reset_process. |
| asdu_type | IEC104TypeID | no | ASDU type. |
| cot | IEC104CoT | no | COT of anchored phase. |
| ca | count | no | Common address. |
| ioa | count | yes | Information object address. |
| time_sync | string | yes | Formatted CP56Time2a (for clock sync/test where present). |
| tsc | count | yes | Test sequence counter (for C_TS_TA_1). |
| qrp | count | yes | Qualifier of reset process (for C_RP_NA_1). |
| actcon_ts | time | yes | Timestamp when Actcon was seen. |
| actterm_ts | time | yes | Timestamp when ActTerm was seen. |
| duration | interval | yes | actterm_ts - ts when both are present. |
| missing | string | yes | Missing phases. |
| error | bool | yes | Derived error indicator. |
| error_reason | string | yes | Comma-separated error reasons. |
| status | string | yes | Outcome. |
Tracks parameterization transactions (P_ME_* and P_AC_NA_1) except when they
are part of interrogation results (those are treated as telemetry and appear in
iec104_telemetry.log).
Columns:
| Field | Type | Optional | Meaning |
|---|---|---|---|
| ts | time | no | Transaction start (usually time of Act). |
| uid | string | no | Connection UID. |
| id | conn_id | no | Connection 4-tuple. |
| is_orig | bool | no | Direction of anchored phase. |
| action | string | no | param_measured or param_activation. |
| asdu_type | IEC104TypeID | no | ASDU type. |
| cot | IEC104CoT | no | COT of anchored phase. |
| ca | count | no | Common address. |
| ioa | count | yes | Information object address. |
| value | string | yes | Value / parameter representation. |
| qpm_kpa | count | yes | KPA from QPM (parameter class). |
| qpm_pop | bool | yes | POP from QPM. |
| qpm_lpc | bool | yes | LPC from QPM. |
| qpa | count | yes | Qualifier of parameter activation (for P_AC_NA_1). |
| actcon_ts | time | yes | Timestamp when Actcon was seen. |
| actterm_ts | time | yes | Timestamp when ActTerm was seen. |
| duration | interval | yes | actterm_ts - ts when both are present. |
| missing | string | yes | Missing phases. |
| error | bool | yes | Derived error indicator. |
| error_reason | string | yes | Comma-separated error reasons. |
| status | string | yes | Outcome. |
This log correlates read requests (C_RD_NA_1 from the originator) with the
corresponding response (C_RD_NA_1 from the responder) using a FIFO model.
Columns:
| Field | Type | Optional | Meaning |
|---|---|---|---|
| ts | time | no | Request timestamp (when seen as originator). |
| uid | string | no | Connection UID. |
| id | conn_id | no | Connection 4-tuple. |
| is_orig | bool | no | Direction of the anchored phase; for a complete transaction this is typically T (request). |
| action | string | no | Always value_read. |
| asdu_type | IEC104TypeID | no | ASDU type (C_RD_NA_1). |
| cot | IEC104CoT | no | COT from the anchored phase. |
| ca | count | no | Common address. |
| ioa | count | yes | Information object address being read. |
| raw_data | count | yes | Raw data returned by the read response (if present in the response). |
| actcon_ts | time | yes | Timestamp when the response was seen (used analogously to “confirmation”). |
| duration | interval | yes | actcon_ts - ts when both request and response are present. |
| missing | string | yes | Missing parts: request, response. |
| error | bool | yes | Derived error indicator (PN/TEST/hard-error COT). |
| error_reason | string | yes | Comma-separated error reasons. |
| status | string | yes | ok, incomplete, error, timeout(...). |
This log summarizes file segment transfers (F_SG_NA_1) and emits a single row
per (connection UID, direction, IOA, file name) “transfer burst”. The summary is
flushed when an end marker is observed (F_LS_NA_1 or F_AF_NA_1) or when the
transfer stalls for file_seg_timeout.
Columns:
| Field | Type | Optional | Meaning |
|---|---|---|---|
| ts | time | no | Timestamp of the first observed segment in the burst. |
| ts_end | time | yes | Timestamp of the last observed segment (or last activity before timeout). |
| duration | interval | yes | ts_end - ts when available. |
| uid | string | no | Connection UID. |
| id | conn_id | no | Connection 4-tuple. |
| direction | string | no | control_to_station if is_orig=T, else station_to_control. |
| is_orig | bool | no | Direction flag used for the segment stream. |
| ca | count | no | Common address seen for the segments (tries to prefer non-broadcast over 65535). |
| ioa | count | no | Object address (file object). |
| fname | count | no | File name / identifier (as carried in the protocol). |
| segments | count | no | Number of F_SG_NA_1 segments seen. |
| bytes | count | no | Total bytes of segment payloads (sum of |seg|). |
| status | string | no | ok (end marker seen) or timeout (flushed due to inactivity) or in_progress (internal state, typically not emitted). |
- Wireshark IEC 104 Dissector: https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-iec104.c
- Matoušek, Petr. “Description and analysis of IEC 104 Protocol.” Faculty of Information Technology, Brno University o Technology, Tech. Rep (2017). https://www.fit.vut.cz/research/publication/11570/.en
- https://infosys.beckhoff.com/content/1033/tf6500_tc3_iec60870_5_10x/984444939.html?id=6858453402777673110
- The trace under testing/Traces/first has been acquired form https://github.com/automayt/ICS-pcap/blob/master/IEC%2060870/iec104/iec104.pcap
- The traces under the testing/Traces/second, testing/Traces/third and testing/Traces/fourth directories come from the “20200608_UOWM_IEC104_Dataset_mitm_drop” in: Panagiotis, Konstantinos, Thomas, Vasileios, & Panagiotis. (2022). IEC 60870-5-104 Intrusion Detection Dataset [Data set]. https://doi.org/10.21227/fj7s-f281 and https://zenodo.org/record/7108614#.ZFR6oJHML0o