Skip to content

Threat Hunt Plan: Siemens S7 Series PLCs — AI-Assisted S7comm Exploitation, snap7 Masquerading Tools, and Internet Exposure

Date: August 19, 2026 | Revision 1.0

Hunt Objective and Scope

This hunt seeks evidence that threat actors have located, enumerated, or manipulated Siemens S7 Series programmable logic controllers in the environment, using the tradecraft described in the joint advisory published on August 19, 2026 by NSA, CISA, FBI, DOE and EPA. That tradecraft has four defining characteristics, and the hunt is built around each in turn: targets are located through third-party Internet scanning services rather than by scanning the actors perform themselves; exploitation scripts are generated with AI assistance, compressing the interval between a publicly documented weakness and a working tool; the tooling is built on the legitimate open source snap7.dll and python-snap7 libraries and deliberately masqueraded as ordinary OT monitoring software; and access frequently requires no exploitation at all, because the controllers are reachable and their authentication is unconfigured or minimally configured.

The advisory names no CVE, attributes the activity to no named actor, and publishes no IP addresses, domains or file hashes. This shapes the hunt decisively. There is no indicator list to sweep and no patch level that resolves the question. Every hypothesis below is therefore behavioral, and the two highest-value legs are the ones that need no adversary telemetry at all: an inventory of what the Internet can see of the estate, and a baseline of which hosts legitimately speak S7comm to which controllers. A hunt that waits for a signature here will find nothing.

Environment in scope: every Siemens S7 Series controller — S7-200 across all CPU variants, S7-300 including the 314, 315 and 317 models, S7-400 across all variants, S7-1200 covering the CPU 1211C, 1212C, 1214C, 1215C and 1217C, and S7-1500 across all variants including the F-series safety controllers; every engineering workstation carrying TIA Portal, STEP 7 or any S7 programming capability; the Level 3 and Level 3.5 hosts, jump hosts, historians and vendor remote-access paths with a route to TCP port 102; and the organization's public IP space, ASN allocations and cloud estate for the external-exposure leg. Third-party systems integrators and managed service providers with remote access to controllers are explicitly in scope — the advisory identifies that relationship as the reason asset owners are frequently unaware their systems are exposed, so a hunt that stops at the organizational boundary will miss the most likely path.

Time window: 12 months of retained telemetry where available, with a mandatory minimum of 90 days. The activity described is persistent reconnaissance and capability development conducted over an extended period, not a single intrusion event, so a short window characterizes noise rather than the pattern. Note also that a controller reachable from the Internet has been enumerable for as long as it has been reachable, independent of any telemetry the organization holds.

Two scoping notes on protocol coverage. First, TCP port 102 carries both S7comm and IEC 61850 MMS; the two are distinguished by the protocol-ID byte at the start of the COTP payload — 0x32 for classic S7comm, 0x72 for S7CommPlus, and neither for MMS — so a port-based query alone will conflate them at a site running both. Second, S7-1200 and S7-1500 controllers programmed through TIA Portal use S7CommPlus, whose body is obscured by session-keyed integrity material on modern firmware. Semantic function-code analysis is therefore available for classic S7comm but not for S7CommPlus; on those controllers, treat the volume of 0x72 data frames to a CPU as a programming-session indicator and fall back to connection-level and endpoint evidence. Stating this limit up front prevents a clean-looking negative from being read as an absence of activity.

Out of scope: any active interrogation of a controller. No port scanning, no S7comm probing, no COTP connection requests, no active-query module pointed at an S7 device, and no authentication attempts. Active queries against S7 controllers carry genuine process risk and are prohibited for the duration of this hunt. The external-exposure leg reads third-party scan indices only and never touches target infrastructure.

Hypotheses and Hunt Procedures

Hypothesis 1: an external actor can identify an Internet-exposed or insufficiently segmented Siemens S7 Series PLC belonging to the organization through a third-party Internet scanning index, observable as an exposed TCP 102 service in those indices. Execute FIRST; its output scopes every internal hypothesis.

MITRE ATT&CK: Reconnaissance | T1596.005 Search Open Technical Databases: Scan Databases | this is the advisory's own first technique — the actors query scanning services rather than scanning themselves, so the defender's only view of this phase is the same index the actor reads. Initial Access | T1133 External Remote Services. ICS | T0883 Internet Accessible Device | an Internet-reachable PLC is a finding independent of any observed exploitation. ICS | T0846 Remote System Discovery.

Collection Queries — passive external indices (STRICTLY passive; never probe target infrastructure):

Shodan — run unscoped first to confirm the filter matches the product, then scoped to your estate:

port:102

 

port:102 "Siemens"

 

product:"Siemens S7"

 

port:102 net:<your_public_CIDR>

 

product:"Siemens S7" org:"<Your Org Name>"

 

port:102 asn:AS<your_ASN>

 

ssl.cert.subject.CN:"<your_domain>"

 

shodan count 'port:102 net:<your_public_CIDR>'

 

shodan search --fields ip_str,port,org,hostnames,product,version 'port:102 net:<your_public_CIDR>'

 

Censys CenQL — author the CenQL form (host.services.*), never the deprecated legacy CSL form. The nested parenthesized form is required so the criteria hold on the same service object:

host.services.port = "102"

 

host.services: (port = "102" and software.vendor: "Siemens")

 

host.services: (port = "102" and banner: "Siemens")

 

host.services: (port = "102") and (host.autonomous_system.asn = <your_ASN> or host.ip: "<your_public_CIDR>")

 

host.services.software.product: "S7" and host.dns.names: "<your_domain>"

 

Netlas — tie-breaker only, when Shodan and Censys disagree:

port:102

 

host:<your_public_CIDR> AND port:102

 

certificate.subject.common_name:"<your_domain>"

 

Cross-reference every exposed service and version banner against the CISA Known Exploited Vulnerabilities catalog (https://www.cisa.gov/known-exploited-vulnerabilities-catalog). A KEV match on the controller or on any co-resident service converts an exposure finding into a presumptive initial-access risk; rank by ransomware ratio, not by entry count.

Analysis Queries:

  • Attribute every hit with at least two independent ownership signals — PTR record, ASN, certificate CN or SAN, WHOIS/RDAP, and your own cloud and public-IP inventory — before reporting it. Shared hosting, cellular carrier NAT and integrator-owned address space break attribution in both directions, and an unattributable hit is inconclusive rather than negative. Integrator-owned space deserves specific attention here: a controller exposed on a service provider's address range is still your controller.
  • Version-triage each banner against the targeted model list and record the index scan timestamp with every hit. Siemens equipment order numbers of the 6ES7 form frequently appear in S7 banners and identify the exact CPU variant; capture them where present.
  • Corroborate in at least two of Shodan, Censys and Netlas before escalating. Absence from an index is evidence only that the index did not observe the host.
  • Historical pivot — establish how far back the exposure extends. Because target selection is performed by reading a scan index, continuous exposure means the asset has been on a published target list for the whole of that period and should be treated as presumptively enumerated.
  • Pivot into the internal hypotheses — every confirmed-owned exposed controller becomes a priority target hunted exhaustively under Hypotheses 2, 3 and 4 rather than sampled, and its full S7comm conversation history for the retained window is reviewed rather than summarized.

Favicon-hash pivoting is available for stripped banners and non-standard ports, but the hash is deployment-specific and the engines hash differently (Shodan MurmurHash3 versus Censys sha256/md5). Derive it passively from an index-computed hash on an instance you own — never by fetching /favicon.ico from a target, and never by copying a hash from a third-party write-up, which silently hunts an unrelated population.

API keys are referenced by environment-variable name only — SHODAN_API_KEY, CENSYS_API_SECRET, CENSYS_ORG_ID, NETLAS_API_KEY. A missing key is non-blocking: skip that engine and record the coverage gap in the report.

Hypothesis 2: an actor has conducted reconnaissance against S7 controllers from inside the environment, observable as sequential connection attempts to TCP port 102 across multiple addresses, COTP connection requests presenting a programming-device role, and S7comm enumeration functions issued from hosts that are not sanctioned engineering workstations.

MITRE ATT&CK: Discovery | ICS T0846 Remote System Discovery | BlockList, GetBlockInfo and GetDiagData calls plus ReadVar sweeps and COTP connection-request probing are the advisory's enumeration of CPU properties. Collection | ICS T0893 Data from Local System | read operations on data blocks, which the agencies assess as reconnaissance. Lateral Movement | T1694 Insecure Credentials | reaching devices whose authentication is unconfigured or minimally configured.

Collection Queries:

CrowdStrike Falcon LogScale (CQL) — every endpoint-observed connection to an S7 controller. This is the baseline query; the sanctioned engineering set falls out of it and everything else is the hunt:

#event_simpleName = NetworkConnectIP4
| RemotePort = 102
| groupBy([aid, ComputerName, LocalAddressIP4, RemoteAddressIP4], function=count(as=cnt), limit=max)
| sort(cnt, order=desc, limit=max)

 

CrowdStrike Falcon LogScale (CQL) — sequential scanning, expressed as one source reaching many distinct controllers on port 102. A legitimate engineering workstation talks to a small, stable set of CPUs; a scanner talks to many:

#event_simpleName = NetworkConnectIP4
| RemotePort = 102
| groupBy([aid, ComputerName], function=count(RemoteAddressIP4, distinct=true, as=UniqueTargets), limit=max)
| UniqueTargets > 5
| sort(UniqueTargets, order=desc, limit=max)

 

CrowdStrike Falcon LogScale (CQL) — process attribution for port 102 connections. NetworkConnectIP4 does not carry ImageFileName, and the reverse-direction join keeps the subquery well under the 100,000-row cap. Run at Last 90d or longer, because long-running SCADA and historian daemons may have started before a shorter window opens:

#event_simpleName = ProcessRollup2
| join({
#event_simpleName = NetworkConnectIP4
| RemotePort = 102
},
field=[aid, TargetProcessId], key=[aid, ContextProcessId], mode=inner)
| groupBy([ComputerName, UserName, ImageFileName, CommandLine], function=count(as=cnt), limit=max)
| sort(cnt, order=desc, limit=max)

 

CrowdStrike Falcon LogScale (CQL) — controllers or soft-PLC instances accepting inbound connections on 102, which enumerates the listener population from the endpoint side:

#event_simpleName = NetworkReceiveAcceptIP4
| LocalPort = 102
| groupBy([aid, ComputerName, RemoteAddressIP4], function=count(as=cnt), limit=max)
| sort(cnt, order=desc, limit=max)

 

CrowdStrike Falcon LogScale (CQL) — hosts resolving Internet scanning services, which would indicate someone inside the environment consulting the same indices the actors use:

#event_simpleName = DnsRequest
| DomainName = /(censys|zoomeye|shodan)/i
| join({#event_simpleName = /ProcessRollup2/},
field=[aid, ContextProcessId], key=[aid, TargetProcessId], mode=left,
include=[ImageFileName, CommandLine, UserName])
| groupBy([ComputerName, DomainName, ImageFileName, UserName], function=count(as=cnt), limit=max)
| sort(cnt, order=desc, limit=max)

 

BPF packet capture for tcpdump — rolling capture of all port 102 traffic. Capture full payloads with -s 0 so that COTP TSAPs and S7 function codes can be decoded and program blocks carved. The -G rotation requires a strftime format in the -w filename or each rotation overwrites the last; -C counts in units of 1,000,000 bytes:

tcpdump -i eth1 -nn -s 0 -G 3600 -C 500 -w /captures/s7_%Y%m%d_%H%M%S.pcap 'tcp port 102'

 

tcpdump -i eth1 -nn -s 0 -w /captures/s7_syn.pcap 'tcp port 102 and tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack = 0'

 

tcpdump -i eth1 -nn -s 0 -w /captures/s7_cotp_cr.pcap 'tcp port 102 and tcp[((tcp[12] & 0xf0) >> 2) + 5] = 0xe0'

 

Windows Event IDs to collect — on engineering workstations, jump hosts and any Level 3 host with a route to port 102:

  • 4688 — process creation with command line auditing, to capture Python, script hosts, and any S7 client tooling
  • 4104 — PowerShell script block logging, for scripted S7 or socket activity
  • 5156 — Windows Filtering Platform permitted connection, the host-side record of every outbound session to port 102
  • 5158 — Windows Filtering Platform bind to local port, useful for identifying listeners and tunnels
  • 7045 — new service installed, in case S7 tooling was persisted as a service
  • 4624 and 4625 — successful and failed logons on the engineering workstation, to attribute the operator behind a session
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=5156; StartTime=(Get-Date).AddDays(-90)} |
ForEach-Object { $x=[xml]$_.ToXml(); [pscustomobject]@{
Time=$_.TimeCreated; Host=$_.MachineName
App=$x.Event.EventData.Data[1].'#text'
DestIP=$x.Event.EventData.Data[5].'#text'
DestPort=$x.Event.EventData.Data[6].'#text' } } |
Where-Object { $_.DestPort -eq '102' } |
Export-Csv -NoTypeInformation C:\hunt\s7_port102_5156.csv

 

Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4688; StartTime=(Get-Date).AddDays(-90)} |
Where-Object { $_.Message -match 'python|snap7|s7|step7|TIA' } |
Select-Object TimeCreated, MachineName, @{n='User';e={$_.Properties[1].Value}}, @{n='CmdLine';e={$_.Properties[8].Value}} |
Export-Csv -NoTypeInformation C:\hunt\s7_tooling_4688.csv

 

OT Data Collection: Claroty xDome — Network > Communication > Communication Analysis. Leave Side A empty on the first pass, set Side B to Manufacturer = "Siemens AG" or the network carrying the controllers, set Communication to Port = 102, and set Time Frame to Past Quarter. Use Client / Server rather than Direction = From A to B to identify the true initiator, since the Direction operator matches bidirectional flows. Scope by Network or VLAN rather than by Category, because category-scoped clauses miss misclassified assets. Reconcile the exported session and byte counts against an independent source — xDome under-reports both silently.

OT Data Collection: Claroty CTD — the on-prem platform retains continuous full PCAP that xDome does not. Pull the retrospective capture for every segment carrying port 102 across the full hunt window and export through the CTD-only /v1/events/<id>/pcap surface for offline S7 function-code decoding. Do not treat a CTD result as interchangeable with an xDome result.

OT Data Collection: Dragos Platform — Communications Hub with source zone = Level 3 or Level 3.5, destination zone = Level 2 or Level 1, and a protocol filter for S7comm, over a 90-day window; pivot every cross-Purdue session to its asset record and event PCAP. In Assets, scope to Siemens controllers and carry that list forward as the hunt population. Query the Industrial Protocols QFD for S7 function-code activity outside maintenance windows, and check the Knowledge Pack version before concluding that an absent detection is a capability gap rather than a stale pack.

OT Data Collection: Nozomi Guardian — N2QL against the links table, which is where traffic lives; nodes returns endpoints, not conversations. Bound every query, because head with no argument silently returns 10 rows, and use the $ prefix for any field-to-field comparison or the query returns a clean-looking nothing:

links | where protocol == "s7" | join nodes to ip | join nodes from ip | head 1000

 

links | where time >= days_ago(90) | where from_zone != to_zone | head 1000

 

nodes | where vendor == "Siemens" and type == "PLC" | head 1000

 

alerts | where time >= days_ago(90) | group_by type_id avg(risk) sum(risk)

 

OT Data Collection: Armis Centrix — ASQ on the ipConnections surface; omit the boundary literal on the first pass and set the time window in the UI time-picker rather than in the query body:

in:ipConnections serverPort:102

 

in:ipConnections serverPort:102
endpointA:(networkLocation:"External")

 

in:activity type:"Port Scan Detected"

 

OT Data Collection: Tenable One OT Exposure — Inventory > All Assets is both the asset view and the export point; filter to Siemens assets and export, using the Details Query output for firmware version to establish which controllers match the targeted variants. Events carries policy violations including PLC Start/Stop and Project Upload, which are the platform's native expression of the Hypothesis 4 signals. All programmatic access is GraphQL at the single /graphql endpoint — OT Security has no REST /api/v1/ endpoints — and pyTenable is the supported wrapper. Do not enable or run active queries against S7 controllers during this hunt.

OT Data Collection: Forescout eyeInspect — the Command Center is filter-driven and exposes no analyst query language, so export asset, alert and vulnerability data through the eyeInspect REST API, or forward it by syslog/CEF and hunt in the SIEM; the OT Network Security Monitoring App for Splunk with its CIM-mapped OT Asset Data Model is the canonical pattern. Leave ICS Patrol active queries disabled for S7 devices for the duration.

YARA file-system scan — sweep engineering workstations, jump hosts and any Level 3 host with a route to port 102:

yara -r /opt/hunt/rules/s7_hunt.yar /home/ /opt/ /usr/local/ >> /opt/hunt/hits_home.txt

 

yara -r C:\hunt\rules\s7_hunt.yar C:\Users\ >> C:\hunt\hits_users.txt

 

Analysis Queries:

CrowdStrike Falcon LogScale (CQL) — rarity analysis. A source-destination pair seen once is the shape of reconnaissance; the frequent pairs are the operational baseline the hunt negates:

#event_simpleName = NetworkConnectIP4
| RemotePort = 102
| groupBy([ComputerName, RemoteAddressIP4], function=count(as=cnt), limit=max)
| sort(cnt, order=asc, limit=max)

 

CrowdStrike Falcon LogScale (CQL) — temporal anomalies. The advisory calls out S7comm activity during off-hours and connection patterns consistent with automated scripting rather than human operators:

#event_simpleName = NetworkConnectIP4
| RemotePort = 102
| formatTime(format="%H", as=HourOfDay)
| in(HourOfDay, values=["00","01","02","03","04","05","22","23"])
| groupBy([ComputerName, RemoteAddressIP4, HourOfDay], function=count(as=cnt), limit=max)
| sort(cnt, order=desc, limit=max)

 

Wireshark display filters — the S7comm semantic layer. These decode what the connection-level queries can only count, and are the only way to separate a read from a write:

s7comm

 

s7comm.param.func == 0x2f

 

s7comm.param.func == 0x31

 

s7comm.param.func == 0x32

 

s7comm.param.func == 0x04

 

cotp.dst-tsap

 

s7comm-plus

 

tshark -r /captures/s7_20260819_000000.pcap -Y 's7comm' -T fields -e frame.time -e ip.src -e ip.dst -e s7comm.param.func -e s7comm.header.rosctr

 

tshark -r /captures/s7_20260819_000000.pcap -Y 'cotp.type == 0x0e' -T fields -e ip.src -e ip.dst -e cotp.src-tsap -e cotp.dst-tsap

 

tshark -r /captures/s7_20260819_000000.pcap -Y 's7comm' -T fields -e ip.src | sort | uniq -c | sort -rn

 

The destination TSAP in a COTP connection request decodes to the rack and slot of the CPU being addressed, and the source TSAP carries a role hint — 0x01 indicates a programming device, 0x02 an operator panel, 0x10 an HMI. A client presenting a programming-device role TSAP that is not the sanctioned engineering workstation is a finding on its own, regardless of what functions followed.

Datadog Log Search and Live Process Monitoring — the log-side view of the same activity. Note the reserved-attribute rule: source, host, service, status and message take no @ prefix; every other attribute requires one:

source:windows @network.destination.port:102
// time range: last 90 days

 

source:kubernetes message:("snap7" OR "s7comm" OR "iso-on-tcp")
// time range: last 90 days

 

command:python user:root

 

If Live Process Monitoring is not enabled on OT hosts, use the log-search fallback:

source:windows message:("snap7" OR "python-snap7")
// time range: last 90 days

 

Datadog Log Analytics — Table view, group by host and @network.destination.ip, mirroring the CQL groupBy:

source:windows @network.destination.port:102
// Analytics: Table view, group by host, @network.destination.ip; time range: last 90 days

 

Datadog Audit Trail — confirm that no monitor, integration or access change quietly reduced OT log coverage during the window:

source:datadog @evt.name:("Access Management" OR Monitor OR Integration)
// Analytics: Table view, group by @asset.type, @action; time range: last 90 days

 

Datadog CloudTrail — cloud-hosted bastions and integrator jump hosts that front the OT network:

source:cloudtrail @evt.name:(StartSession OR GetSessionToken) -@network.client.ip:10.* -@network.client.ip:172.16.* -@network.client.ip:192.168.*
// Analytics: Table view, group by @network.client.ip, @userIdentity.arn; time range: last 90 days

 

Datadog Monitor definition — Hypothesis 2:

Type: Log Alert
Query: source:windows @network.destination.port:102 -host:(ews01 OR ews02 OR historian01)
Evaluation window: last 15 minutes
Alert condition: count > 0
Message: "ALERT: S7comm connection to a Siemens controller from a host outside the sanctioned engineering set — immediate investigation required @ot-soc-pagerduty"
Prerequisites: Windows and Linux Agent logs forwarded from all Level 3 and Level 3.5 hosts; network destination attributes populated by the Agent network module
Create via: Monitors > New Monitor > Log Alert OR POST /api/v1/monitors

 

Windows Event Log PowerShell analysis — rarity ranking of port 102 talkers from the host-side firewall record:

Import-Csv C:\hunt\s7_port102_5156.csv |
Group-Object Host, App, DestIP |
Sort-Object Count |
Select-Object Count, Name |
Export-Csv -NoTypeInformation C:\hunt\s7_port102_rarity.csv

 

SNMP polling — poll the switch port facing each controller and diff successive readings at 60-second intervals across the hunt window. On links above 1 Gbps the 32-bit octet counters wrap, so use the 64-bit high-capacity counters for volumetric baselining. The advisory also calls for removing or changing default SNMP community strings, so treat any device still answering a default community as a finding of its own:

snmpwalk -v3 -l authPriv -u hunt-ro -a SHA-256 -A "$SNMP_AUTH_PASS" -x AES-256 -X "$SNMP_PRIV_PASS" 10.20.1.1 IF-MIB::ifTable

 

snmpget -v3 -l authPriv -u hunt-ro -a SHA-256 -A "$SNMP_AUTH_PASS" -x AES-256 -X "$SNMP_PRIV_PASS" 10.20.1.1 IF-MIB::ifHCInOctets.7 IF-MIB::ifHCOutOctets.7 IF-MIB::ifInErrors.7 IF-MIB::ifOutErrors.7

 

snmpwalk -v3 -l authPriv -u hunt-ro -a SHA-256 -A "$SNMP_AUTH_PASS" -x AES-256 -X "$SNMP_PRIV_PASS" 10.20.14.7 system

 

Treat any -v1 or -v2c community-string access to OT assets as a finding in its own right; those transit essentially in cleartext and RFC 3410 recommends v3 authPriv. Where a device offers only stock crypto, fall back to -a SHA -x AES (SHA-1 and AES-128), and provision credentials through snmp.conf or createUser rather than inline where the platform allows, since inline passphrases are visible in the process table.

Collect SNMP trap logs from the trap receiver for the hunt window and filter to the controller address ranges. Flag coldStart (1.3.6.1.6.3.1.1.5.1) and warmStart (1.3.6.1.6.3.1.1.5.2) traps, which correspond directly to the PLCColdStart and PLCHotStart operations hunted under Hypothesis 4; linkDown and linkUp (1.3.6.1.6.3.1.1.5.3 and .4) for port flaps around times of interest; and authenticationFailure (1.3.6.1.6.3.1.1.5.5) for unauthorized-access attempts on OT network interfaces.

Hypothesis 3: an actor has deployed AI-generated tooling built on the snap7 library onto a host in the environment, masqueraded as legitimate OT monitoring software, observable as Python or compiled processes importing snap7 and speaking S7comm from hosts that are not approved engineering workstations.

MITRE ATT&CK: Execution | ICS T0834 Native API | AI-generated Python scripts incorporating the snap7.dll library from public repositories. Defense Evasion | ICS T0849 Masquerading | the tools deliberately mimic legitimate OT monitoring solutions to evade security teams. Resource Development | T1587.004 Develop Capabilities: Exploits and T1588.007 Obtain Capabilities: Artificial Intelligence | the development method that produced the tooling.

This is the hardest hypothesis to run cleanly, and it is worth being explicit about why. snap7 and python-snap7 are legitimate, widely used open source industrial automation libraries; the traffic they produce is well-formed S7comm indistinguishable from ordinary engineering activity; and the tools are named to look like monitoring software. Neither the library, the protocol, nor the process name is a reliable discriminator on its own. The only durable discriminator is context: which host, whether that host is an approved engineering workstation, and whether an approval record exists. Build the approved-workstation inventory before running these queries, and negate it — never allowlist by hostname pattern inside the query itself.

Collection Queries:

CrowdStrike Falcon LogScale (CQL) — direct references to the library on any monitored host:

#event_simpleName = ProcessRollup2
| CommandLine = /snap7/i
| groupBy([ComputerName, UserName, ImageFileName, CommandLine], function=count(as=cnt), limit=max)
| sort(cnt, order=desc, limit=max)

 

CrowdStrike Falcon LogScale (CQL) — the library loaded as a module. Confirm the ModuleLoad event name against the tenant's event catalog before treating a zero result as a negative:

#event_simpleName = ModuleLoad
| ImageFileName = /snap7/i
| groupBy([ComputerName, ImageFileName], function=count(as=cnt), limit=max)
| sort(cnt, order=desc, limit=max)

 

CrowdStrike Falcon LogScale (CQL) — Python processes that actually spoke to a controller, which is far more selective than either signal alone:

#event_simpleName = ProcessRollup2
| ImageFileName = /(python|pythonw)\.exe$/i
| join({
#event_simpleName = NetworkConnectIP4
| RemotePort = 102
},
field=[aid, TargetProcessId], key=[aid, ContextProcessId], mode=inner)
| groupBy([ComputerName, UserName, CommandLine], function=count(as=cnt), limit=max)
| sort(cnt, order=desc, limit=max)

 

CrowdStrike Falcon LogScale (CQL) — the masquerading signal directly: processes named like monitoring software that speak S7comm. Review every result by hand; the point is not that the name is suspicious but that the combination warrants attribution to a known product:

#event_simpleName = ProcessRollup2
| CommandLine = /(monitor|watchdog|telemetry|collector|agent)/i
| join({
#event_simpleName = NetworkConnectIP4
| RemotePort = 102
},
field=[aid, TargetProcessId], key=[aid, ContextProcessId], mode=inner)
| groupBy([ComputerName, UserName, ImageFileName, CommandLine], function=count(as=cnt), limit=max)
| sort(cnt, order=desc, limit=max)

 

CrowdStrike Falcon LogScale (CQL) — the library or tooling arriving on disk, with hashes for pivoting:

in(field=#event_simpleName, values=["NewScriptWritten","PeFileWritten","NewExecutableWritten"])
| TargetFileName = /snap7/i
| groupBy([ComputerName, TargetFileName, SHA256HashData], function=count(as=cnt), limit=max)
| sort(cnt, order=desc, limit=max)

 

CrowdStrike Falcon LogScale (CQL) — the legitimate Siemens engineering software population, which defines the approved baseline the other queries are measured against:

#event_simpleName = ProcessRollup2
| ImageFileName = /(siemens\.automation|s7tgtopx|plcsim|step7|tiaportal)/i
| groupBy([ComputerName, UserName, ImageFileName, CommandLine], function=count(as=cnt), limit=max)
| sort(cnt, order=desc, limit=max)

 

Windows Event IDs to collect:

  • 4688 — process creation, for Python, script hosts and any binary invoking snap7
  • 4104 — PowerShell script block logging, for scripted socket activity to port 102
  • 7045 — new service installed, for tooling persisted as a masqueraded monitoring service
  • 4697 — service installed (Security log equivalent), where audit policy provides it
Get-WinEvent -FilterHashtable @{LogName='System'; Id=7045; StartTime=(Get-Date).AddDays(-365)} |
Where-Object { $_.Message -match 'monitor|telemetry|collector|agent|s7|snap7' } |
Select-Object TimeCreated, MachineName, Message |
Export-Csv -NoTypeInformation C:\hunt\masq_services_7045.csv

 

Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational'; Id=4104; StartTime=(Get-Date).AddDays(-90)} |
Where-Object { $_.Message -match 'snap7|102|TcpClient|Socket' } |
Select-Object TimeCreated, MachineName, Message |
Export-Csv -NoTypeInformation C:\hunt\s7_scriptblocks_4104.csv

 

OT Data Collection: Claroty xDome — use the Applications filter chip on the Devices surface to enumerate installed software on engineering workstations, which requires the Edge or Endpoint Sensor; where only network visibility exists, fall back to Device Type = "Industrial Workstation" combined with Manufacturer = "Siemens AG" and OS Category = "Windows" to bound the population.

OT Data Collection: Dragos Platform — the optional Active Collection agent adds software and module inventory to the Assets record on Windows and Linux hosts, which is the platform's path to enumerating snap7 presence; without it, Dragos sees the S7comm traffic but not the library. Register a validated pattern as a custom analytic through the analytic manager so it becomes standing detection content.

OT Data Collection: Nozomi Guardian — the Arc host agent covers the OT-locked Windows and Linux workstations an IT EDR cannot reach, collecting process execution with image path, command line, parent and hashes, plus USB and file events. Pull Arc telemetry for the engineering workstation population; on hosts where the OEM forbids third-party agents, network-only visibility is the ceiling and should be recorded as a coverage gap.

OT Data Collection: Armis Centrix — Armis has no host-agent view of loaded libraries, so scope the device population and correlate against the endpoint evidence from Falcon or Arc:

in:ipConnections serverPort:102
endpointA:(device:(category:"Computers"))

 

OT Data Collection: Tenable One OT Exposure — the asset record carries installed-software and module data where credentialed collection is configured; export via GraphQL and join to the port 102 talker list on asset ID rather than on IP address, since addresses churn in OT.

OT Data Collection: Forescout eyeInspect — asset fingerprinting will identify the engineering workstation population but not library imports; use it to bound scope and rely on endpoint telemetry for the library evidence.

YARA file-system scan — locate masqueraded S7 tooling and the scripts that drive it:

yara -r /opt/hunt/rules/s7_hunt.yar /home/ /opt/ /tmp/ /var/tmp/ >> /opt/hunt/hits_tooling.txt

 

yara -r C:\hunt\rules\s7_hunt.yar C:\Users\ C:\ProgramData\ C:\Windows\Temp\ >> C:\hunt\hits_tooling.txt

 

Analysis Queries:

CrowdStrike Falcon LogScale (CQL) — rarity of the process population that touches port 102. A single-occurrence image name in this list deserves individual attribution:

#event_simpleName = ProcessRollup2
| join({
#event_simpleName = NetworkConnectIP4
| RemotePort = 102
},
field=[aid, TargetProcessId], key=[aid, ContextProcessId], mode=inner)
| groupBy([ImageFileName], function=count(as=cnt), limit=max)
| sort(cnt, order=asc, limit=max)

 

Wireshark display filters — distinguish tooling from genuine engineering software by how the session is constructed. Legitimate TIA Portal sessions against S7-1200 and S7-1500 controllers use S7CommPlus and negotiate a session key; snap7 speaks classic S7comm:

s7comm and not s7comm-plus

 

s7comm.param.setup.pdu_length

 

cotp.src-tsap

 

tshark -r /captures/s7_20260819_000000.pcap -Y 's7comm.header.rosctr == 1' -T fields -e ip.src -e ip.dst -e s7comm.param.func | sort | uniq -c | sort -rn

 

A host speaking classic S7comm to an S7-1500 controller that the site otherwise programs exclusively through TIA Portal is a strong signal, because it indicates a client that is not the engineering software.

Datadog Log Analytics and Monitor:

source:windows message:("snap7" OR "python-snap7")
// Analytics: Top List view, group by host; sort ascending for rarest-first; time range: last 90 days

 

Type: Log Alert
Query: source:windows message:("snap7" OR "python-snap7") -host:(ews01 OR ews02)
Evaluation window: last 10 minutes
Alert condition: count > 0
Message: "ALERT: snap7 library reference on a host outside the approved engineering workstation set — possible masqueraded S7 tooling @ot-soc-pagerduty"
Prerequisites: Windows Agent logs from all engineering workstations and Level 3 hosts; process collection enabled where licensing permits
Create via: Monitors > New Monitor > Log Alert OR POST /api/v1/monitors

 

YARA memory scan — scan resident processes that hold S7 client state. Classic YARA 4.5 takes the PID as a positional argument; there is no -p flag for process scanning, and YARA-X cannot scan process memory at all:

for p in $(pgrep -f 'python|s7|snap7'); do yara /opt/hunt/rules/s7_hunt.yar $p >> /opt/hunt/mem_hits.txt; done

 

Get-Process | Where-Object { $_.ProcessName -match 'python|s7|monitor' } | ForEach-Object { & yara.exe C:\hunt\rules\s7_hunt.yar $_.Id >> C:\hunt\mem_hits.txt }

 

CrowdStrike Falcon Real Time Response (RTR) can execute these YARA scans on remote hosts without an on-site analyst, and a validated pattern can be promoted to a Custom IOA so the finding becomes standing detection content.

Hypothesis 4: an actor has issued write, program-transfer, or CPU-state operations against a Siemens S7 controller, observable as S7comm function codes outside the read set arriving from an unsanctioned source or outside an approved change window.

MITRE ATT&CK: Execution | ICS T0821 Modify Controller Tasking | the advisory's own mapping for write operations on data blocks, assessed as pre-positioning for effects operations. ICS T0836 Modify Parameter | WriteVar and variable-table force. ICS T0843 Program Download and T0845 Program Upload | block transfer in either direction. ICS T0858 Change Operating Mode and T0816 Device Restart/Shutdown | cold and warm restart. ICS T0813 Denial of Control | PLCStop halting the CPU. ICS T0855 Unauthorized Command Message | any control job from a source that is not authorized.

Falcon and Datadog cannot see S7 function codes — they see that a connection occurred, not what it carried. This hypothesis therefore runs primarily on packet capture and the OT monitoring platforms, with endpoint telemetry supplying attribution for whichever host the network evidence implicates. The controlling function codes are listed below; the read set is the baseline and everything else warrants a change-record correlation.

Collection Queries:

BPF packet capture for tcpdump — full-payload capture is mandatory here, because the function code sits inside the S7 parameter block and a truncated capture cannot decode it:

tcpdump -i eth1 -nn -s 0 -G 1800 -C 500 -w /captures/s7_full_%Y%m%d_%H%M%S.pcap 'tcp port 102'

 

tcpdump -i eth1 -nn -s 0 -w /captures/s7_job.pcap 'tcp port 102 and tcp[((tcp[12] & 0xf0) >> 2) + 7] = 0x32'

 

Wireshark display filters — the write, transfer and CPU-state function codes. These are the codes the hunt exists to find:

s7comm.param.func == 0x05

 

s7comm.param.func == 0x1a

 

s7comm.param.func == 0x1b

 

s7comm.param.func == 0x1c

 

s7comm.param.func == 0x1d

 

s7comm.param.func == 0x1e

 

s7comm.param.func == 0x1f

 

s7comm.param.func == 0x28

 

s7comm.param.func == 0x29

 

s7comm.param.func == 0x2b

 

s7comm.param.func == 0x2c

 

WriteVar is 0x05, the download sequence is RequestDownload 0x1A, DownloadBlock 0x1B and DownloadEnded 0x1C, the upload sequence is StartUpload 0x1D, UploadBlock 0x1E and EndUpload 0x1F, PIService is 0x28, PLCStop is 0x29, and PLCHotStart and PLCColdStart are 0x2B and 0x2C. Enumeration functions — BlockList 0x2F, GetBlockInfo 0x31, GetDiagData 0x32 — belong to Hypothesis 2. In the UserData family, watch the Security and CpuPassword exchanges, which precede authentication bypass or password attacks, and SetClock, which alters the CPU clock and can defeat time-based logic or obscure a timeline.

tshark -r /captures/s7_full_20260819_000000.pcap -Y 's7comm.param.func in {0x05 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f 0x28 0x29 0x2b 0x2c}' -T fields -e frame.time -e ip.src -e ip.dst -e s7comm.param.func -e s7comm.header.rosctr

 

tshark -r /captures/s7_full_20260819_000000.pcap -Y 's7comm.header.rosctr == 3' -T fields -e frame.time -e ip.src -e ip.dst -e s7comm.param.func -e s7comm.header.errclass -e s7comm.header.errcod

 

The second command matters as much as the first. Pair each request to its acknowledgement by PDU reference: a job that was issued is an attempt, but a job whose AckData returns a success error class of 0x00 is a completed operation, and the severity is materially different. An error class of 0x87 indicates an access or protection denial, which is itself evidence of authentication probing against a controller.

Windows Event IDs to collect — attribution for whichever host the packet evidence implicates:

  • 4688 — process creation on the implicated host at the time of the S7 job
  • 5156 — the permitted connection matching the observed session
  • 4624 — the interactive or network logon that placed an operator on that host
  • 4648 — explicit-credential logon, where the session was launched with alternate credentials
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4624; StartTime=(Get-Date).AddDays(-90)} |
Where-Object { $_.Message -match 'ews01|ews02' } |
Select-Object TimeCreated, MachineName, @{n='User';e={$_.Properties[5].Value}}, @{n='LogonType';e={$_.Properties[8].Value}}, @{n='SourceIP';e={$_.Properties[18].Value}} |
Export-Csv -NoTypeInformation C:\hunt\ews_logons_4624.csv

 

OT Data Collection: Claroty xDome — the Alerts surface carries OT Activity Alerts, which is the category covering controller operations; filter All Alerts to Alert Category = "OT Activity Alert" and review the MITRE ATT&CK ICS column for T0843, T0845, T0858 and T0813 mappings. Note that All Alerts offers no time-frame selector, so sort by ALERT UPDATED descending or use the API detected_time filter for a hard window, and remember that the category chip counts are lifetime rather than windowed.

OT Data Collection: Dragos Platform — Notifications filtered to ATT&CK-ICS Execution and Impair Process Control techniques; every notification ships an event-based PCAP of the triggering conversation, which is the fastest route to the function code. Open a Case on any confirmed write or program transfer, attach the event PCAP and the Industrial Protocols QFD extract, and follow the attached playbook.

OT Data Collection: Nozomi Guardian — the platform hard-codes known-bad OT patterns including S7 PDU function 0x1A and 0x1B download-block operations from unexpected sources, and its process-variable baselining will surface the downstream effect of a write even where the write itself was missed:

alerts | where mitre_attack ~= "T0843" | sort record_created_at asc | head 500

 

alerts | where time >= days_ago(90) | where risk > 7 | head 1000

 

variables | where last_change > days_ago(1) | head 500

 

OT Data Collection: Armis Centrix — Armis performs no S7 function-code decode, so its contribution here is the connection record and the device context; correlate its ipConnections export against the packet-capture findings by source address and timestamp:

in:ipConnections serverPort:102

 

in:alerts type:"Multiple Failed Login Attempts"

 

OT Data Collection: Tenable One OT Exposure — this is the platform's strongest contribution to the hunt. Configuration-change and code-snapshot tracking diffs ladder logic, function blocks, tag tables and diagnostic buffers between snapshots, and it catches changes made locally by someone plugged into the front of the controller that never appear on the network at all. Pull the code-snapshot history for every S7 controller across the hunt window and diff against the known-good baseline; the Events view carries PLC Start/Stop and Project Upload policy violations directly.

OT Data Collection: Forescout eyeInspect — the Industrial Threat Library covers S7 control operations and the LAN Communications Profile baseline will flag a first-seen S7 conversation pair; export both and diff the profile across the hunt window.

YARA file-system scan — carve and inspect any block content recovered from a download or upload session, and sweep engineering workstations for exported project material:

yara -r /opt/hunt/rules/s7_hunt.yar /captures/carved/ >> /opt/hunt/hits_blocks.txt

 

yara -r C:\hunt\rules\s7_hunt.yar C:\Users\ C:\ProgramData\Siemens\ >> C:\hunt\hits_projects.txt

 

Analysis Queries:

Carve the block transfer. DownloadBlock and UploadBlock data-transfer frames carry the OB, FC, FB, DB and SDB block bytes, and the RequestDownload and StartUpload parameters name the target block by type and number. Reassemble the byte stream by following the TCP stream and compare the recovered logic against the gold-copy project file. A download from anywhere other than the approved engineering workstation is potential logic injection and should be treated as an incident until a change record proves otherwise.

tshark -r /captures/s7_full_20260819_000000.pcap -Y 's7comm.param.func in {0x1b 0x1e}' -T fields -e tcp.stream | sort -u

 

tshark -r /captures/s7_full_20260819_000000.pcap -q -z follow,tcp,raw,0

 

CrowdStrike Falcon LogScale (CQL) — attribute the implicated source address to a host and a user, joining from the process side so the subquery stays small:

#event_simpleName = ProcessRollup2
| join({
#event_simpleName = NetworkConnectIP4
| RemotePort = 102
},
field=[aid, TargetProcessId], key=[aid, ContextProcessId], mode=inner)
| groupBy([ComputerName, UserName, ImageFileName, CommandLine], function=count(as=cnt), limit=max)
| sort(cnt, order=desc, limit=max)

 

CrowdStrike Falcon LogScale (CQL) — external-sourced logons, which would place the operator outside the environment entirely:

#event_simpleName = UserLogon
| in(LogonType, values=[3, 10])
| RemoteAddressIP4 = *
| !cidr(RemoteAddressIP4, subnet=["10.0.0.0/8","172.16.0.0/12","192.168.0.0/16","127.0.0.0/8","169.254.0.0/16"])
| groupBy([ComputerName, UserName, LogonType, RemoteAddressIP4], function=count(as=cnt), limit=max)
| sort(cnt, order=desc, limit=max)

 

OT process correlation — for every write, program transfer or CPU-state operation identified, pull the historian trend and the SCADA alarm record for a window bracketing the event. A parameter write followed by a process excursion, or a cold start followed by an alarm flood, converts a network observation into a demonstrated operational impact and changes both the escalation path and the regulatory reporting obligation. Where a safety controller in the S7-1500 F-series is involved, engage process safety engineering immediately rather than completing the technical analysis first.

Datadog Monitor definition — Hypothesis 4. This monitor depends on OT platform alerts being forwarded into Datadog, since Datadog itself cannot decode S7:

Type: Log Alert
Query: source:claroty OR source:dragos OR source:nozomi "T0843" OR "T0845" OR "T0813" OR "PLC Stop"
Evaluation window: last 5 minutes
Alert condition: count > 0
Message: "CRITICAL: OT platform reported an S7 program transfer or CPU-state change — engage OT on-call and process operations immediately @ot-soc-pagerduty"
Prerequisites: Claroty, Dragos or Nozomi alert forwarding configured to Datadog via syslog or the vendor integration; without it this monitor cannot fire
Create via: Monitors > New Monitor > Log Alert OR POST /api/v1/monitors

 

Threat Actor Profile

The advisory attributes this activity to no named group, and this plan does not speculate beyond it. What the authoring agencies do state is the assessed intent: the pattern is likely persistent reconnaissance in targeted sectors and facilities, conducted to develop capability and prepare to cause operational effects against critical infrastructure. Read access is being used to understand target environments so that future write operations can be positioned. That assessment, rather than an actor name, is what should drive the hunt's priorities — it means read-only findings must be escalated with the same seriousness as writes, because the agencies characterize the reads as preparation.

The state-aligned OT-focused actor class fits the described tradecraft most closely. Sophistication is high in tradecraft and patience even where individual techniques are simple, dwell is measured in months, and the operational objective is pre-positioning rather than immediate disruption. The access path is precisely the one described: locate exposed controllers through a scan index, connect where authentication is absent or default, enumerate the CPU and its blocks, read data blocks to understand the process, and retain the access. The characteristic TTPs are T1596.005, T1694, T0846, T0893 and eventually T0821 — and, notably, the deliberate masquerading of tooling as monitoring software (T0849), which indicates an actor who expects the environment to be monitored and is planning to remain.

The AI-assisted capability development the advisory highlights materially widens the actor set rather than describing a single group. The agencies are explicit that AI dramatically reduces the technical skill and time required to produce working ICS exploitation scripts and enables rapid adaptation to defensive measures. The practical consequence is that the historical protective assumption — that ICS exploitation demands rare, specialized skill and therefore only nation-state actors could attempt it — no longer holds. Moderate-sophistication criminal actors, access brokers, and hacktivist groups can now generate functional S7 tooling from public documentation, and the snap7 library gives them a tested, well-documented foundation to build on. For hacktivist actors in particular, an exposed controller with no authentication is a low-effort, high-publicity target, and their characteristic pattern is a fast, visible action shortly after access rather than patient pre-positioning.

Integrator and vendor-adjacent access is the class hardest to separate from the threat, and the advisory makes the point directly: asset owners working with third-party service providers or systems integrators may not realize their systems are exposed and at risk. An integrator's engineering workstation will present a programming-device role TSAP, will download blocks, and will do so from an address the asset owner may not recognize — the same observable profile as the threat. The discriminator is never the traffic; it is whether an authorization record exists. Section 7 accordingly treats an uncorrelated integrator session as a finding rather than as noise to suppress.

Data Sources Required

Network: full-payload packet capture on every segment carrying TCP port 102, which is non-negotiable for this hunt because S7 function codes cannot be recovered from a truncated capture or from flow records; NetFlow or IPFIX from OT-facing switches and firewalls for connection-level coverage where full capture is unavailable; firewall permit and deny logs for every path toward port 102, including the perimeter rules the advisory recommends auditing; and SNMP interface counters and trap-receiver logs from the switches facing the controllers.

Endpoint: CrowdStrike Falcon telemetry from engineering workstations, jump hosts, historians and any Level 3 host with a route to the controllers — specifically ProcessRollup2 and SyntheticProcessRollup2, NetworkConnectIP4, NetworkReceiveAcceptIP4, ModuleLoad, the file-write family, DnsRequest, UserLogon and UserLogonFailed2. Windows Security and PowerShell Operational logs with command-line auditing (4688) and script block logging (4104) enabled, plus System log 7045 for service installation. Datadog Agent logs from OT Windows and Linux hosts with Live Process Monitoring where licensing and operational policy permit. Nozomi Arc host telemetry on the OT-locked workstations where an IT EDR cannot be installed.

OT/ICS: process historian trends and SCADA alarm records for the hunt window, required to establish whether any observed write produced a process effect; the controllers' own diagnostic buffers, retrieved through the engineering software rather than by active query; gold-copy project files and firmware baselines for every controller, without which a code-snapshot diff has nothing to compare against; and exports from every deployed OT monitoring platform — Claroty xDome Communication Analysis and OT Activity Alerts plus CTD retrospective PCAP where the on-prem platform exists, Dragos Assets, Notifications, Communications Hub and the Industrial Protocols QFD with event PCAPs, Nozomi Guardian links, nodes, alerts and variables tables plus Arc telemetry, Armis Centrix ipConnections and activity, Tenable One OT Exposure asset inventory and code-snapshot history via GraphQL, and Forescout eyeInspect asset, alert and LAN Communications Profile exports.

Vendor, change and access records: the Siemens controller inventory with CPU model and firmware version per device, which the advisory makes the first mitigation and which this hunt requires as its population definition; the engineering workstation inventory with TIA Portal and STEP 7 installation status; the change management record covering the full hunt window, since almost every discriminator in this plan reduces to whether an authorization record exists; maintenance window schedules; VPN and remote-access logs including integrator and vendor sessions; and the contractual inventory of which third parties hold remote access to which controllers.

External attack surface: the passive scan indices queried read-only — Shodan, Censys and Netlas — with keys supplied by environment variable and any unavailable engine recorded as a coverage gap rather than silently skipped; the CISA Known Exploited Vulnerabilities catalog for cross-referencing observed services; and the organization's authoritative public IP allocations, ASN registrations, DNS zone data and cloud asset inventory. That last item is load-bearing rather than optional: without an authoritative ownership record an index hit cannot be attributed, and an unattributable hit is inconclusive rather than negative. Because integrators frequently host connectivity on their own address space, the inventory must extend to provider-hosted assets or the exposure determination will be incomplete — and where it is incomplete, say so.

Detection Signatures

The SIGMA rules span process creation, image load, network connection and file event so that a single logging gap cannot blind the whole set.

title: Snap7 Library Loaded Outside Approved Engineering Workstation
id: 3f8a1c74-52e9-4b60-a1d7-9c04e6b28f15
status: experimental
description: Detects the snap7 industrial automation library being loaded by a process, consistent with AI-generated S7 tooling masquerading as monitoring software as described in joint advisory AA26-231A. Requires an approved-engineering-workstation list to be maintained in the filter.
references:
- https://www.cisa.gov/news-events/cybersecurity-advisories/aa26-231a
author: 1898 & Co. Threat Hunt Team
date: 2026/08/19
tags:
- attack.execution
- attack.t0834
- attack.defense_evasion
- attack.t0849
logsource:
category: image_load
product: windows
detection:
selection:
ImageLoaded|contains:
- 'snap7.dll'
- 'snap7'
filter_approved:
Computer|startswith:
- 'EWS'
condition: selection and not filter_approved
falsepositives:
- Approved OT monitoring or historian products that legitimately bundle snap7
- Integrator-supplied engineering tooling on a documented workstation
level: high

 

title: Python or Script Host Connecting to Siemens S7 Controller on TCP 102
id: 6d2b9e05-7a41-48cf-b3e2-1f5807a9dc36
status: experimental
description: Detects a scripting interpreter establishing a connection to TCP port 102, the S7comm and ISO-on-TCP port, consistent with AI-generated Python tooling built on snap7 rather than legitimate engineering software.
references:
- https://www.cisa.gov/news-events/cybersecurity-advisories/aa26-231a
author: 1898 & Co. Threat Hunt Team
date: 2026/08/19
tags:
- attack.execution
- attack.t0834
- attack.discovery
- attack.t0846
logsource:
category: network_connection
product: windows
detection:
selection:
DestinationPort: 102
Image|endswith:
- '\python.exe'
- '\pythonw.exe'
- '\powershell.exe'
- '\pwsh.exe'
- '\wscript.exe'
- '\cscript.exe'
condition: selection
falsepositives:
- Sanctioned Python-based historian collectors or asset-inventory scripts on approved hosts
- Vendor diagnostic tooling run during a documented maintenance window
level: high

 

title: S7comm Connection From Host Outside Sanctioned Engineering Set
id: 8c5f3d19-4e72-4a08-95b6-d20e1738ba47
status: experimental
description: Detects any connection to TCP port 102 from a host that is not an approved engineering workstation or historian, covering the reconnaissance and insecure-credential access described in joint advisory AA26-231A.
references:
- https://www.cisa.gov/news-events/cybersecurity-advisories/aa26-231a
author: 1898 & Co. Threat Hunt Team
date: 2026/08/19
tags:
- attack.discovery
- attack.t0846
- attack.lateral_movement
- attack.t1694
logsource:
category: network_connection
detection:
selection:
DestinationPort: 102
filter_sanctioned:
SourceIp|cidr:
- '10.30.10.0/28'
condition: selection and not filter_sanctioned
falsepositives:
- Newly commissioned engineering workstations not yet added to the sanctioned range
- IEC 61850 MMS traffic, which shares TCP port 102 and must be excluded by protocol-ID byte at sites running both
level: high

 

title: Snap7 Library or S7 Tooling Written to Disk
id: 1e7c04a6-9b38-4d52-8fa1-63d905c7e2b8
status: experimental
description: Detects the snap7 library or S7 client tooling being written to a workstation, particularly to a staging or user-writable path, consistent with deployment of AI-generated exploitation tooling.
references:
- https://www.cisa.gov/news-events/cybersecurity-advisories/aa26-231a
author: 1898 & Co. Threat Hunt Team
date: 2026/08/19
tags:
- attack.execution
- attack.t0834
- attack.resource_development
- attack.t1588.007
logsource:
category: file_event
product: windows
detection:
selection_name:
TargetFilename|contains:
- 'snap7'
- 'python-snap7'
selection_path:
TargetFilename|contains:
- '\Temp\'
- '\Downloads\'
- '\ProgramData\'
- '\Public\'
condition: selection_name and selection_path
falsepositives:
- Legitimate installation of an OT monitoring product that bundles snap7 through a temporary staging directory
- Developer or integrator workstations where snap7 is an accepted dependency
level: medium

 

Snort and Suricata rules use local SIDs starting at 1,000,000. Set OT_NET to the controller address range and EWS_NET to the approved engineering workstation range before deployment. Validate every content offset against your own captures before enabling these in blocking mode — the S7 function byte must fall at the parameter function position, not merely anywhere in the payload, and an unvalidated offset produces both false positives and false negatives.

alert tcp !$EWS_NET any -> $OT_NET 102 (msg:"S7comm PLCStop job from non-engineering host (T0813 Denial of Control)"; flow:to_server,established; content:"|03 00|"; offset:0; depth:2; content:"|32 01|"; distance:0; content:"|29|"; classtype:attempted-dos; sid:1000101; rev:1; reference:url,www.cisa.gov/news-events/cybersecurity-advisories/aa26-231a;)

 

alert tcp !$EWS_NET any -> $OT_NET 102 (msg:"S7comm program download from non-engineering host (T0843 Program Download)"; flow:to_server,established; content:"|03 00|"; offset:0; depth:2; content:"|32 01|"; distance:0; pcre:"/\x32\x01.{8}[\x1a\x1b\x1c]/s"; classtype:attempted-admin; sid:1000102; rev:1; reference:url,www.cisa.gov/news-events/cybersecurity-advisories/aa26-231a;)

 

alert tcp !$EWS_NET any -> $OT_NET 102 (msg:"S7comm program upload from non-engineering host (T0845 Program Upload)"; flow:to_server,established; content:"|03 00|"; offset:0; depth:2; content:"|32 01|"; distance:0; pcre:"/\x32\x01.{8}[\x1d\x1e\x1f]/s"; classtype:attempted-recon; sid:1000103; rev:1; reference:url,www.cisa.gov/news-events/cybersecurity-advisories/aa26-231a;)

 

alert tcp any any -> $OT_NET 102 (msg:"Sequential S7 connection attempts consistent with port 102 scanning (T1596.005 follow-on)"; flow:to_server; flags:S; detection_filter:track by_src, count 10, seconds 60; classtype:attempted-recon; sid:1000104; rev:1; reference:url,www.cisa.gov/news-events/cybersecurity-advisories/aa26-231a;)

 

alert tcp !$EWS_NET any -> $OT_NET 102 (msg:"S7CommPlus session from non-engineering host - programming-session indicator"; flow:to_server,established; content:"|03 00|"; offset:0; depth:2; content:"|72|"; distance:5; within:3; threshold:type limit, track by_src, count 1, seconds 600; classtype:attempted-admin; sid:1000105; rev:1;)

 

The first YARA rule targets AI-generated S7 client tooling on disk. Its condition requires a snap7 import together with an S7-specific API call and a controller-addressing artifact, because any one of those alone matches legitimate automation code — snap7 is a real library with real users, and a rule keyed on the import alone would flag every genuine engineering script in the estate. The masquerading branch is separated deliberately: a script that imports snap7 and also names itself as monitoring or telemetry software is matching the advisory's specific T0849 behavior, which warrants a different escalation path from ordinary snap7 usage. The file-size ceiling keeps the rule off large compiled applications that merely bundle the library.

rule S7_Snap7_Masquerading_Client_Tool
{
meta:
description = "Script or tool built on snap7/python-snap7 that addresses Siemens S7 controllers, including variants masqueraded as OT monitoring software (joint advisory AA26-231A)"
author = "1898 & Co. Threat Hunt Team"
date = "2026-08-19"
reference = "https://www.cisa.gov/news-events/cybersecurity-advisories/aa26-231a"
strings:
$imp1 = "import snap7" ascii nocase // python-snap7 import form
$imp2 = "from snap7" ascii nocase
$imp3 = "snap7.dll" ascii nocase // direct library reference
$imp4 = "libsnap7" ascii nocase // Linux build of the same library
$api1 = "Cli_ConnectTo" ascii // snap7 client connect entry point
$api2 = "db_read" ascii nocase // data block read
$api3 = "db_write" ascii nocase // data block write
$api4 = "read_area" ascii nocase // area read (I/Q/M/DB)
$api5 = "write_area" ascii nocase // area write
$api6 = "plc_stop" ascii nocase // CPU stop
$api7 = "full_upload" ascii nocase // block upload
$addr1 = "rack" ascii nocase // controller addressing: rack/slot pair
$addr2 = "slot" ascii nocase
$addr3 = ":102" ascii // explicit ISO-on-TCP port
$masq1 = "monitor" ascii nocase // masquerading indicators
$masq2 = "telemetry" ascii nocase
$masq3 = "collector" ascii nocase
$masq4 = "watchdog" ascii nocase
condition:
filesize < 2MB and
any of ( $imp* ) and
(
// Tooling branch — an S7 API call plus a controller-addressing artifact
( any of ( $api* ) and any of ( $addr* ) )
// Masquerading branch — the advisory's T0849 behavior, escalated separately
or ( any of ( $api* ) and any of ( $masq* ) )
)
}

 

The second YARA rule targets the same tooling once it is resident, which matters because a script can be run from memory, deleted after execution, or delivered by an interpreter that never wrote it to disk. The condition anchors on snap7 client symbols that appear in the loaded library's export table alongside a live session artifact, requiring two of the S7 API strings rather than one so that the mere presence of the library in a legitimate process's address space does not match. Scanning another process's memory requires SeDebugPrivilege on Windows, and Falcon Real Time Response can execute the scan remotely.

rule S7_Client_Session_Memory_Artifacts
{
meta:
description = "In-memory artifacts of an active snap7-based S7comm client session against a Siemens controller"
author = "1898 & Co. Threat Hunt Team"
date = "2026-08-19"
reference = "https://www.cisa.gov/news-events/cybersecurity-advisories/aa26-231a"
strings:
$sym1 = "Cli_ConnectTo" ascii wide // snap7 export symbols present in the mapped library
$sym2 = "Cli_DBRead" ascii wide
$sym3 = "Cli_DBWrite" ascii wide
$sym4 = "Cli_PlcStop" ascii wide
$sym5 = "Cli_FullUpload" ascii wide
$sym6 = "Cli_Download" ascii wide
$sess1 = "ISO-on-TCP" ascii wide nocase // live session artifacts
$sess2 = "S7comm" ascii wide nocase
$sess3 = { 03 00 00 16 11 E0 } // COTP connection request header seen in a send buffer
$lib = "snap7" ascii wide nocase
condition:
$lib and
2 of ( $sym* ) and
any of ( $sess1, $sess2, $sess3 )
}

 

The third YARA rule is the standing credential-dumping memory rule, required because this hunt covers lateral movement (T1694 Insecure Credentials) and the engineering workstations in scope are high-value credential targets whose compromise is the most likely route to a controller that is not directly exposed. It covers mimikatz, Windows Credentials Editor, gsecdump and the comsvcs MiniDump technique, with a catch-all branch pairing a memory-read API against LSASS with any one tool indicator so that renamed binaries still match. SeDebugPrivilege is required to open another process for memory read, and Falcon RTR can run the scan on remote hosts. Scope note: this rule targets Windows LSASS tooling and will not match Linux credential dumping from the /proc filesystem (T1003.007); on Linux engineering hosts, the preceding two rules plus the endpoint process telemetry carry that coverage.

rule Credential_Dump_Tool_Memory_Artifacts
{
meta:
description = "In-memory artifacts of common credential-dumping tooling on engineering workstations and jump hosts with a route to S7 controllers"
author = "1898 & Co. Threat Hunt Team"
date = "2026-08-19"
reference = "https://attack.mitre.org/techniques/T1003/001/"
strings:
$mk1 = "sekurlsa::logonpasswords" ascii wide nocase // mimikatz credential module
$mk2 = "lsadump::sam" ascii wide nocase // mimikatz SAM dump module
$mk3 = "privilege::debug" ascii wide nocase // mimikatz privilege escalation call
$mk4 = "mimikatz" ascii wide nocase // tool name string
$mkh = { 6D 69 6D 69 6B 61 74 7A } // "mimikatz" as hex, catches partial obfuscation
$wce1 = "wce.exe" ascii wide nocase // Windows Credentials Editor
$gs1 = "gsecdump" ascii wide nocase // gsecdump, name alone is sufficient
$cs1 = "MiniDump" ascii wide nocase // comsvcs.dll MiniDump export
$cs2 = "comsvcs" ascii wide nocase
$api1 = "NtReadVirtualMemory" ascii wide // memory-read APIs for the catch-all branch
$api2 = "ReadProcessMemory" ascii wide
$lsass = "lsass.exe" ascii wide nocase
condition:
any of ( $mk1, $mk2, $mk3, $mk4, $mkh )
or ( $wce1 and $lsass )
or $gs1
or ( $cs1 and $cs2 and $lsass )
// Catch-all — renamed tooling still needs a memory-read API against LSASS plus one indicator
or ( any of ( $api1, $api2 ) and $lsass and any of ( $mk4, $wce1, $gs1, $cs2 ) )
}

 

Indicators of Compromise

The advisory publishes no network or file indicators, so every indicator below is behavioral by necessity. This is a defining property of the hunt rather than a gap in it: target selection is performed by reading a third-party scan index, so there is no adversary infrastructure to blocklist, and the tooling is assembled from a legitimate open source library, so there is no malicious hash to sweep.

Network indicators: any TCP session to port 102 from a host that is not an approved engineering workstation, historian or HMI; connection attempts to port 102 across many distinct addresses from a single source within a short interval, which is the sequential-scanning pattern the advisory names; a COTP connection request whose source TSAP presents a programming-device role (high byte 0x01) from a host that is not the sanctioned engineering station; S7comm observed sourced from, or destined to, an IT segment, the DMZ, or any public address, which is an architecture violation regardless of function code; classic S7comm (protocol ID 0x32) directed at an S7-1200 or S7-1500 controller at a site that otherwise programs exclusively through TIA Portal, which uses S7CommPlus (0x72); repeated connection attempts with varying parameters against the same CPU; and S7 sessions whose inter-arrival timing is machine-regular rather than human-irregular.

S7comm function-code indicators: WriteVar (0x05) targeting outputs, safety-relevant data blocks, or any area outside the writing host's baseline; the download sequence RequestDownload (0x1A), DownloadBlock (0x1B) and DownloadEnded (0x1C) outside a scheduled change window; the upload sequence StartUpload (0x1D), UploadBlock (0x1E) and EndUpload (0x1F), which represents logic theft or pre-attack mapping; PIService (0x28); PLCStop (0x29); PLCHotStart (0x2B) and PLCColdStart (0x2C); variable-table force operations; UserData Security and CpuPassword exchanges, which precede authentication bypass or password attacks; SetClock, which alters the CPU clock and can obscure a timeline; and enumeration functions BlockList (0x2F), GetBlockInfo (0x31) and GetDiagData (0x32) from an unbaselined source. An AckData response carrying error class 0x87 indicates an access or protection denial and is direct evidence of authentication probing.

Host indicators: the snap7.dll or libsnap7 library present on any host that is not an approved engineering workstation or a documented monitoring product; Python or another interpreter importing snap7; a process named as monitoring, telemetry, collector or watchdog software that speaks to port 102 and cannot be attributed to a known product; scripts referencing rack and slot addressing or explicit port 102 connections; a service installed with a monitoring-style name whose binary connects to controllers; TIA Portal or STEP 7 present on a host with no engineering role; and any S7 project or block file written to a user-writable or staging path.

External-exposure indicators: any TCP 102 service on an IP address attributable to the organization by at least two independent ownership signals, observed in Shodan, Censys or Netlas; a Siemens equipment order number of the 6ES7 form or an S7 banner in the index record; the same exposure present continuously across an extended historical window, which means the controller has been on a published target list for that whole period; and any exposure on integrator-owned or provider-hosted address space serving the organization's controllers.

OT and operational indicators: a controller firmware version or block content differing from the gold-copy baseline; a code-snapshot diff showing changed ladder rungs, function blocks or tag definitions with no corresponding work order; an unexpected CPU mode change or restart evidenced by an SNMP coldStart or warmStart trap or a sysUpTime reset outside a maintenance window; a first-seen S7 conversation pair; a new asset appearing in a controller segment; a device still answering a default SNMP community string or with PLC password protection unconfigured; a process excursion or alarm flood correlating in time with an observed write; and any controller-touching session by an integrator or managed service provider with no matching authorization record.

False Positive Baseline

1. Sanctioned engineering activity from approved workstations. TIA Portal and STEP 7 sessions legitimately perform every operation this hunt looks for, including block download and CPU restart, and they present exactly the programming-device role TSAP the hunt flags. Suppress only on the tuple of approved workstation, approved operator and a correlated change record — never on the workstation alone. An operation from an approved workstation with no change record remains a finding, because a compromised engineering workstation is the single most likely route to a controller that is not directly exposed.

2. HMI, SCADA and historian polling. A steady stream of ReadVar and cyclic reads from HMI and SCADA hosts is the normal baseline of any S7 site and will dominate every port 102 query by volume. Baseline these sources by address, target CPU and polling cadence, then suppress by that tuple rather than by protocol. A poll from a known source at an unexpected cadence, to an unexpected CPU, or with a function outside the read set stays in scope.

3. Integrator and vendor engineering sessions. Third-party integrators legitimately connect to controllers, and the advisory identifies exactly this relationship as the reason asset owners are unaware of their exposure. Suppress only where the session correlates to an open work order and a documented access window, and record the source address. As with the first entry, an uncorrelated session matching the profile is a finding rather than a false positive — this is the most important discrimination in the plan, because the threat and the integrator are observationally identical and only the paperwork separates them.

4. IEC 61850 MMS traffic sharing TCP port 102. At sites running both S7 and IEC 61850 — common in generation and substation environments — MMS traffic will populate every port-based query in this plan and is entirely legitimate. Disambiguate on the protocol-ID byte at the start of the COTP payload rather than by host: 0x32 is classic S7comm, 0x72 is S7CommPlus, and MMS is neither. Suppressing by host address instead would blind the hunt to S7 activity from a host that also speaks MMS.

5. Authorized vulnerability scanners and asset-discovery tools. Nessus, Tenable One OT Exposure active queries, Forescout ICS Patrol and Claroty active queries all touch port 102 and will produce the sequential-scanning pattern by design. Their source addresses and scan windows are documented; suppress on that tuple. Traffic matching the pattern from an undocumented scanner source is a finding, and note that active queries against S7 controllers are prohibited for the duration of this hunt, so any observed during the window warrants an immediate check of who enabled them.

6. Legitimate snap7 usage. snap7 and python-snap7 are real libraries with real industrial users, and some commercial OT monitoring and historian products bundle them. Build an inventory of approved products that carry the library and their installation paths, then suppress on process identity and path rather than on the library name alone — path-only suppression would blind the rule to an attacker who drops tooling into the same directory.

7. Commissioning, migration and disaster-recovery activity. Controller commissioning, firmware upgrades and DR restoration testing all produce block downloads, CPU restarts and first-seen conversation pairs at volume. Correlate against the project schedule and the change record; a match inside a documented window on a controller with an open commissioning ticket is expected. Note that these windows are also the periods in which an attacker's activity would blend in most effectively, so they merit closer review rather than blanket suppression.

8. EASM attribution error. A scan-index hit on shared hosting, a cellular carrier NAT range, or an address recently reassigned by a provider can be attributed to the organization when it belongs elsewhere, and the reverse is equally common — particularly with integrator-hosted connectivity. Require two independent ownership signals before reporting any external hit, record the index scan timestamp with each, and treat an unattributable hit as inconclusive rather than as a clean negative.

Escalation Criteria

Escalate to incident response when any of the following conditions is met:

1. Any S7comm write operation (WriteVar 0x05) or variable-table force is confirmed against a controller with no corresponding change record, regardless of source.

2. Any program download sequence (0x1A, 0x1B, 0x1C) is confirmed from a source that is not the approved engineering workstation, or from any source outside an authorized change window. Treat as potential logic injection and carve the transferred blocks before remediating.

3. Any program upload sequence (0x1D, 0x1E, 0x1F) is confirmed from an unsanctioned source, indicating logic theft or pre-attack mapping of the control program.

4. Any CPU-state operation — PLCStop (0x29), PLCColdStart (0x2C), PLCHotStart (0x2B) or PIService (0x28) — is observed, whether or not the acknowledgement confirms success. A PLCStop job is catastrophic even once and is escalated on the job, with severity raised further when the AckData confirms it succeeded.

5. Any operation of any kind is identified against an S7-1500 F-series safety controller without a matching authorization record. Engage process safety engineering immediately rather than completing the technical analysis first.

6. Two or more passive scan indices independently confirm an Internet-exposed TCP 102 service on an address attributable to the organization by at least two ownership signals, including addresses on integrator-owned or provider-hosted space serving the organization's controllers.

7. An attributable external exposure is shown by historical pivot to have persisted across an extended window, meaning the controller should be treated as presumptively enumerated by actors reading the same scan indices.

8. A code-snapshot or gold-copy diff shows any change to ladder logic, function blocks, tag definitions or firmware with no corresponding work order — including changes with no associated network activity, which indicate local access at the controller.

9. UserData Security or CpuPassword exchanges are observed against any controller, or AckData responses carrying error class 0x87 indicate repeated access or protection denials, both consistent with authentication probing.

10. A SetClock operation is observed against any controller, given its anti-forensic effect on every subsequent timeline.

11. Any YARA hit on S7_Snap7_Masquerading_Client_Tool against a host that is not an approved engineering workstation or a documented monitoring product — with immediate escalation where the masquerading branch of the rule is the branch that matched.

12. Any YARA hit on S7_Client_Session_Memory_Artifacts against any resident process on a host outside the approved engineering set.

13. Any YARA hit on Credential_Dump_Tool_Memory_Artifacts against any process on an engineering workstation, jump host or Level 3 host with a route to port 102.

14. Sequential connection attempts to port 102 across multiple controllers are confirmed from a source that is not a documented scanner, or S7comm is observed sourced from an IT segment, the DMZ or a public address.

Hunt Completion Criteria and Reporting

The hunt is complete when every Siemens S7 Series controller has been enumerated with CPU model and firmware version and matched against the targeted variant list; when every engineering workstation carrying TIA Portal, STEP 7 or S7 programming capability has been identified and the approved-workstation baseline has been established and signed off by engineering; when the external-exposure sweep has run against all three passive indices, or the coverage gap from any unavailable engine has been recorded explicitly; when all four hypotheses have been executed across the full retained window on every platform holding the required telemetry; when every S7comm function-code observation outside the read set has been correlated against the change record and dispositioned; when every hit has been dispositioned as confirmed malicious, confirmed benign with a baseline entry, or inconclusive with the reason stated; and when every detection signature in Section 5 has been deployed or has an explicit deployment decision recorded against it.

The report must contain: the controller inventory with model, firmware and exposure status, expressed as a proportion of the believed total so that inventory gaps are visible rather than implied; the external-exposure sweep results with per-hit provenance — which engines returned the hit, the ownership signals used to attribute it, the banner and any 6ES7 order number observed, and the index scan timestamp — together with an explicit statement of which engines were queried and which were skipped; the approved-workstation baseline as an appendix, since every behavioral finding in this plan is defined relative to it and the finding cannot be evaluated without it; the query-by-query execution record with the exact time window run and the row counts returned, because a zero result is only meaningful when window and scope are stated; a full accounting of S7comm function-code activity observed, separated into read, write, transfer and CPU-state classes with the change-record correlation for each; the disposition of every hit with its supporting evidence; and the remediation status of each hardening action from the advisory.

Coverage limitations must be stated plainly and specifically. Name every segment lacking full-payload capture on port 102, since function-code analysis is impossible without it and a connection-only negative is not a negative on Hypothesis 4. Name every S7-1200 and S7-1500 controller whose engineering traffic is S7CommPlus, where the session-keyed integrity material prevented semantic decoding, and state what was concluded from connection-level evidence instead. Name every OT host without endpoint telemetry, every platform whose retention was shorter than the hunt window, every controller for which no gold-copy baseline existed to diff against, and every third-party integrator whose access records could not be obtained. Where the hunt bounded its own coverage — sampling a population, capping a result set, skipping an engine — say so. A silent truncation reads as complete coverage and is the most damaging thing a hunt report can do.

Three recommendations belong in the report regardless of outcome. First, the passive external-exposure sweep should become a recurring control on a defined cadence rather than a one-time artifact, because exposure is a state that changes without any action by the defender and because target selection in this campaign is driven entirely by what the scan indices show. Second, the approved-workstation baseline and the controller inventory should be maintained as living records with a named owner, since every detection in Section 5 degrades to noise the moment they go stale. Third, the validated hunt queries should be promoted to standing detection content — CrowdStrike NG-SIEM correlation rules or Custom IOAs, Datadog monitors, the Section 5 SIGMA rules deployed to the SIEM, and the Snort/Suricata rules deployed on the OT sensors — so that the next occurrence is detected rather than hunted.

Advisory IoC Reference

IOC Type IOC
CVE None named in source material — joint advisory AA26-231A describes tradecraft against known vulnerabilities, misconfigurations and weaknesses without assigning CVE identifiers; consult Siemens ProductCERT (https://www.siemens.com/cert) for per-model CVEs affecting your firmware
Threat Actor None attributed in source material — the authoring agencies assess the activity as persistent reconnaissance and capability development intended to prepare operational effects against critical infrastructure, but name no group or cluster
Malware snap7 / python-snap7 | legitimate open source library, abused | Open source S7comm client library from public repositories; incorporated into AI-generated Python scripts to gain read/write access to S7 PLC memory, configuration and ladder logic
Malware AI-generated S7comm exploitation scripts | custom tooling | Scripts generated with AI assistance from public S7 documentation, masqueraded as legitimate OT monitoring solutions to evade security teams (T0849)
Network IOC None published in source material — no IP addresses, domains or URLs are provided; monitor https://www.cisa.gov/known-exploited-vulnerabilities-catalog and https://www.cisa.gov/news-events/cybersecurity-advisories for indicators as the campaign is further characterized
File IOC None published in source material — no file hashes or filenames are provided; snap7.dll and libsnap7 are legitimate library names and must not be treated as malicious indicators on their own
Behavioral Affected models: Siemens S7-200 (all CPU variants), S7-300 (all variants incl. 314/315/317), S7-400 (all variants), S7-1200 (CPU 1211C/1212C/1214C/1215C/1217C), S7-1500 (all variants incl. F-series safety controllers)
Behavioral TCP session to port 102 from a host that is not an approved engineering workstation, historian or HMI
Behavioral Sequential connection attempts to TCP port 102 across many distinct addresses from a single source in a short interval
Behavioral COTP connection request presenting a programming-device role TSAP (high byte 0x01) from a host that is not the sanctioned engineering station
Behavioral S7comm observed sourced from or destined to an IT segment, the DMZ, or any public IP address — architecture violation regardless of function code
Behavioral Classic S7comm (protocol ID 0x32) directed at an S7-1200 or S7-1500 at a site that otherwise programs exclusively via TIA Portal (S7CommPlus, 0x72)
Behavioral S7comm WriteVar (function 0x05) or variable-table force against outputs or safety-relevant data blocks
Behavioral S7comm program download sequence RequestDownload 0x1A / DownloadBlock 0x1B / DownloadEnded 0x1C outside an approved change window
Behavioral S7comm program upload sequence StartUpload 0x1D / UploadBlock 0x1E / EndUpload 0x1F from an unsanctioned source — logic theft or pre-attack mapping
Behavioral S7comm CPU-state operation PIService 0x28, PLCStop 0x29, PLCHotStart 0x2B, or PLCColdStart 0x2C
Behavioral S7comm enumeration functions BlockList 0x2F, GetBlockInfo 0x31, GetDiagData 0x32, or ReadVar 0x04 sweeps from an unbaselined source
Behavioral S7comm UserData Security or CpuPassword exchange, or SetClock operation altering the CPU clock
Behavioral S7comm AckData response carrying error class 0x87 (access/protection denial) — evidence of authentication probing
Behavioral snap7.dll or libsnap7 present or loaded on a host that is not an approved engineering workstation or documented monitoring product
Behavioral Python or other interpreter importing snap7, or connecting directly to TCP port 102
Behavioral Process or service named as monitoring, telemetry, collector or watchdog software that speaks to TCP port 102 and cannot be attributed to a known product
Behavioral S7comm activity during off-hours, or connection timing that is machine-regular rather than human-irregular
Behavioral Connections to controllers originating from countries or IP ranges not associated with a known vendor or integrator
Behavioral TCP 102 service observed in two or more passive scan indices on an organization-attributable IP address, including integrator-owned or provider-hosted space
Behavioral Controller firmware, ladder logic, function block or tag definition differing from the gold-copy baseline with no corresponding work order — including changes with no associated network activity, indicating local access
Behavioral SNMP coldStart or warmStart trap, or sysUpTime reset, from an S7 controller outside a maintenance window
Behavioral Controller answering a default SNMP community string, or with PLC password protection and protection levels unconfigured