5/18/2026

TL;DR
UniFi IDS signature 2069175 (ET MALWARE BPFDoor ICMP Echo Request, X:[COMMAND]) fired on a Roborock Q10 robot vacuum on my IoT VLAN. After a week of “investigation” — packet captures, custom Scapy trigger scripts, isolated test VLANs, the works — the root cause turned out to be a Suricata false positive with an identifiable mathematical cause: the vacuum sends an ICMP heartbeat every 20 seconds whose payload includes a 32-bit microsecond timestamp counter, and that counter rolls through values containing 0x58 0x3a (“X:”) at the rule’s match offset approximately once every few thousand packets.
This post documents how I got there, why I was briefly convinced of the wrong answer, and how to determine if you’re seeing the same pattern on your own network.

The Setup
- Gateway: UniFi Dream Machine (UDM), firmware v5.0.16
- IDS: Suricata 6.x with Emerging Threats ruleset, IPS mode (Detect and Block)
- Device of interest: Roborock Q10 robot vacuum (MAC
24:9e:7d:4c:72:7d) on isolated IoT VLAN - Alert:
IPS Alert 1: A Network Trojan was detected. Signature ET MALWARE BPFDoor ICMP Echo Request, X:[COMMAND] (Inbound). From: 192.168.3.244:0, to: 192.168.3.1:0, protocol: ICMP
The alert grabs attention for obvious reasons. BPFDoor is a real, well-documented Linux backdoor attributed to Red Menshen / Earth Bluecrow, originally seen in espionage operations against telco and government targets. “Inbound ICMP backdoor on a Chinese-manufactured IoT device” is the kind of phrase that makes a security person sit up.

What the Signature Actually Looks For
The rule, in its essential form:
alert icmp $EXTERNAL_NET any -> $HOME_NET any (
msg:"ET MALWARE BPFDoor ICMP Echo Request, X:[COMMAND] (Inbound)";
itype:8;
content:"X:";
depth:2;
...
metadata: signature_severity Critical, confidence High,
malware_family BPFDoor, created_at 2026_05_05;
sid:2069175; rev:2;
)
Two important properties:
- Content match is just two bytes at
depth:2— the first two bytes of the ICMP payload must be0x58 0x3a. - Signature metadata declares “confidence: High” and “signature_severity: Critical.” ET signatures get these labels when their analysts believe the content match is specific enough to avoid common false positives. That made me take this seriously, and rightly so — but as we’ll see, “specific enough” depends on what traffic you’re inspecting.
Why I Almost Filed an FBI Report
Three things conspired to make this look real:
Confounder #1: The “trigger test” that fooled itself.
I wrote a Scapy script to send crafted ICMP packets to the vacuum: X:id, X:whoami, X:cat /etc/passwd, fifteen commands total. Every single reply contained the exact bytes I sent. My script flagged each one as [!!!] SUSPICIOUS RESPONSE.
# What I thought I was detecting
if b"X:" in payload:
is_suspicious = True
reason = "Contains 'X:' BPFDoor magic bytes"
What I was actually detecting was RFC 792, which has been the spec for ICMP since 1981: “The data received in the echo message must be returned in the echo reply message.” Every Linux device on Earth will faithfully echo back whatever bytes you put in a ping request. The vacuum was doing exactly what every other device does.
The lesson: any active test that involves sending crafted traffic to a device needs a control. Run the same script against your router, your laptop, a Raspberry Pi. They’ll all “respond suspiciously” the same way. If your control fires the same alert, you’re measuring how ping works, not malware.
I eventually ran this exact test against an Arch Linux laptop to verify. The results were identical: 15 “suspicious responses” detected, all containing the X: bytes I had sent, all flagged for unusual payload lengths and non-standard patterns. The script’s detection heuristics fired on both the vacuum and the laptop equally — which proves the detection is measuring traffic shape abnormality, not device compromise.
This is actually correct behavior from the script. It detected three markers of non-standard ICMP:
- Presence of
0x58 0x3a(the “X:” magic bytes) - Payload length differing from the standard 56-byte ping
- Payload content differing from standard Linux ping patterns (
0x10 0x11 0x12...)
When you intentionally send abnormal ICMP traffic (as my test did), all three heuristics fire — on any device. This proved the detection isn’t device-specific; it’s pattern-specific. And since both the vacuum and a known-good Linux laptop produced identical results, the pattern alone cannot indicate compromise.

Confounder #2: Closed TCP ports felt like “hiding.”
nmap against the vacuum returned all 65,535 TCP ports closed. I initially read this as backdoor evasion. It’s actually the correct posture for an IoT device — Roborocks talk outbound to cloud over TLS; they shouldn’t be running listening services on the LAN. Closed ports are good security, not suspicious behavior.
Confounder #3: Confirmation bias.
Once a few hours into “this looks bad,” every benign observation became another data point. The legitimate Roborock root-password derivation file /opt/rockrobo/vinda (documented in Dennis Giese’s public Roborock research) became a “BPFDoor auth mechanism.” The marketing hostname AutoPack_Sweeper became “process masquerading.” None of these were ever evidence; they were vibes filtered through expectation.
Doing the Actual Forensic Work
After several rounds of being correctly pushed back on by an AI assistant who kept asking “but what does the data actually show?”, I ran the real investigation:
Step 1: Inventory the alarms
UniFi stores IPS alerts in MongoDB at /data/unifi/db/. Accessing the database directly:
ssh root@<your-udm-ip>
mongo --port 27117 ace
db.alarm.count() // 15 alarms total ever
db.alarm.find({"msg": /BPFDoor/i}).count() // 1 BPFDoor alert
db.alarm.find({"msg": /BPFDoor/i}).pretty()
The full alert document revealed several things I’d missed in the dashboard view:
{
"icmp_type" : 8, // echo REQUEST, not reply
"src_ip" : "192.168.3.244", // vacuum initiated
"dest_ip" : "192.168.3.1", // gateway as destination
"flow" : {
"bytes_toclient" : 200214,
"bytes_toserver" : 200312,
"pkts_toclient" : 2043,
"pkts_toserver" : 2044,
"start" : "2026-05-08T22:02:25-0400"
},
"inner_alert_metadata" : {
"confidence" : ["High"],
"signature_severity" : ["Critical"],
"created_at" : ["2026_05_05"],
"malware_family" : ["BPFDoor"]
}
}
The flow had been running for 15+ hours and exchanged ~4,000 ICMP packets in each direction. That’s not the gateway occasionally probing the device — that’s a sustained, high-volume, vacuum-initiated ICMP conversation. Worth understanding.

Step 2: Capture spontaneous traffic
I moved the vacuum to its own isolated VLAN (192.168.4.0/24) and ran two captures on the gateway, with filters that excluded my own test machine so I’d only see what the vacuum did spontaneously:
# ICMP-only capture
nohup tcpdump -i br400 -w /tmp/roborock_spontaneous.pcap -s 0 \
'icmp and host 192.168.4.244 and not host 192.168.4.83' &
# All traffic except ARP
nohup tcpdump -i br400 -w /tmp/roborock_all.pcap -s 0 \
'host 192.168.4.244 and not host 192.168.4.83 and not arp' &
I left these running for 14 hours overnight. Crucially: I did not generate any traffic to the vacuum during this window. Clean baseline data is the whole point.
Step 3: Analyze the ICMP capture
Results:
- 4,994 ICMP packets in 14 hours
- 2,497 from vacuum, 2,497 from gateway (perfect 1:1 echo request/reply ratio)
- The vacuum sends an echo request to the gateway every ~20 seconds, all night
- Bytes
0x58 0x3a(the BPFDoor “X:”) appear ZERO times in 14 hours of capture
Wait — zero hits in 14 hours, but the alarm did fire once previously? Let’s look at the payload structure to understand why.
0x0000: 4500 0054 2de0 4000 4001 8283 c0a8 04f4 E..T-.@.@.......
0x0010: c0a8 0401 0800 8004 0e88 0000 6df8 fb7a ............m..z <-- ICMP header + payload starts
0x0020: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x0030: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x0040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x0050: 0000 0000 ....
The 56-byte ICMP payload has:
- Bytes 0–3: Vary every packet (
6d f8 fb 7ain this example) - Bytes 4–55: All zeros
That’s not the standard Linux ping payload (which fills bytes 0x10 through 0x37). It’s a custom-crafted ICMP packet from a minimal embedded ping implementation, padded with zeros.
Step 4: Decode the varying bytes
I extracted the first 4 bytes from each consecutive vacuum ping and looked at the differences:
| Packet # | Bytes 0-3 (LE) | Decimal | Diff from prev | Real time delta |
|---|---|---|---|---|
| 0 | 6d f8 fb 7a | 2,063,333,485 | — | — |
| 1 | 1d d3 31 7c | 2,083,640,093 | +20,306,608 | 20.304s |
| 2 | 83 ae 69 7d | 2,104,077,955 | +20,437,862 | 20.438s |
| 3 | 28 04 9f 7e | 2,124,350,504 | +20,272,549 | 20.275s |
| 4 | ea 18 d5 7f | 2,144,671,978 | +20,321,474 | 20.318s |
The “differences” column, in microseconds, matches the wall-clock interval between pings almost exactly.
The first 4 bytes of the payload are a little-endian uint32 microsecond counter — almost certainly clock_gettime(CLOCK_MONOTONIC, ...).tv_nsec / 1000 (or tv_usec from gettimeofday), with the low 32 bits used as the ping payload’s “identifier.” This is a common pattern in minimalist ping implementations on embedded Linux — and it’s stable enough that you can derive the device’s uptime from the counter value.
Step 5: Do the math on why the signature fired
With a 4-byte rolling counter incrementing by ~20 million each packet, every possible 32-bit value will eventually appear. The signature looks for 0x58 0x3a at depth 2 — the first two bytes.
Probability of 0x58 0x3a appearing in any specific 2-byte position: 1 / 65536.
Probability of it appearing somewhere in the first 4 bytes: ~1 / 16384 (three possible windows, minus negligible overlap).
At one ping every 20 seconds: 4,320 pings per day. So you’d expect a hit roughly every 16384 / 4320 ≈ 3.8 days.
Over a year, you’d expect about 95 false positives from this one device. The fact that you’d only see it once on a UniFi dashboard at any given time is because the dashboard ages off old events.
That matches my data exactly: the gateway has logged BPFDoor exactly once in its history (May 9), and after isolating the vacuum I observed zero hits in 14 hours — perfectly consistent with the predicted ~3.8-day average.

Step 6: Verify the rest of the traffic is clean
The non-ICMP capture (5,272 packets over 14 hours) had a single external destination:
3440 192.168.4.244:39246 -> 34.202.57.108:8883 (vacuum -> AWS)
1830 34.202.57.108:8883 -> 192.168.4.244:39246 (AWS -> vacuum)
34.202.57.108→ec2-34-202-57-108.compute-1.amazonaws.com(AWS US-East-1)- Port
8883→ standard IANA-assigned port for MQTT over TLS - Every TCP packet started with
0x17 0x03 0x03— TLS 1.2 application data record. No plaintext anywhere.
One long-lived MQTT/TLS connection to AWS. That is exactly how every cloud-connected IoT device on the planet works. Nothing else outbound, no DNS to suspicious domains, no second channel.
The network behavior is, in a word, boring.
How to Verify This Pattern on Your Own Network
If you see SID 2069175 fire on your UniFi, here’s a 10-minute sanity check:
Step 1: Identify the source IP
ssh root@<udm-ip>
mongo --port 27117 ace
> db.alarm.find({"msg": /BPFDoor/i}).pretty()
- If the source IP is internal and the destination is your gateway → you’re seeing the same pattern. Probably an IoT device with a custom ICMP heartbeat.
- If the source IP is external (from the public internet) and the destination is something on your network that doesn’t expose ICMP services → that warrants real investigation.
Step 2: Capture spontaneous traffic from the suspected device
tcpdump -i <your-iot-bridge> -w /tmp/dev.pcap -s 0 \
'icmp and host <device-ip> and not host <your-workstation-ip>'
Let it run for at least a few hours. Don’t ping the device yourself during the capture.
Step 3: Check for “X:” in payloads
# Convert pcap to hex, search for 58 3a
tcpdump -r /tmp/dev.pcap -X 2>/dev/null | grep "58 3a"
If you find any hits, look at what comes after the 0x58 0x3a. Real BPFDoor command packets contain readable shell commands (X:id, X:ls /, X:cat /etc/passwd). Random bytes look like random bytes.
Step 4: Check the payload structure
If the device sends ICMP echo requests with payloads that aren’t the standard Linux 0x10 0x11 0x12 ... 0x37 pattern, look at what the varying bytes encode. If they look like a monotonically incrementing counter — particularly a microsecond timer — you have the same false positive cause I did.
# Quick check: extract first 4 bytes of each ICMP payload and look at diffs
import struct
from scapy.all import rdpcap
pkts = rdpcap('/tmp/dev.pcap')
vals = []
for p in pkts:
if p.haslayer('ICMP') and p['ICMP'].type == 8:
payload = bytes(p['ICMP'].payload)
if len(payload) >= 4:
vals.append((p.time, struct.unpack('<I', payload[:4])[0]))
for i in range(1, min(10, len(vals))):
t_diff = vals[i][0] - vals[i-1][0]
v_diff = vals[i][1] - vals[i-1][1]
print(f"time_delta={t_diff:.3f}s, payload_value_delta={v_diff} ({v_diff/1e6:.3f}s if usec)")
If v_diff / 1e6 matches t_diff, you’ve identified a microsecond counter in the payload, which means you’ll get periodic random hits on the rule.
Suppressing the Rule Without Losing Coverage
The cleanest mitigation is to allow-list the specific src/dst pair where the false positive occurs, not disable the rule entirely. Real BPFDoor C2 packets would originate from the public internet, not from devices on your IoT VLAN talking to your own gateway.
In the UniFi UI: Settings → Security → Threat Management → Allow List → add the IoT VLAN subnet and gateway IP.
Via Suricata’s threshold.config (requires SSH, may be reset by firmware updates):
suppress gen_id 1, sig_id 2069175, track by_src, ip 192.168.3.0/24
This silences the rule for traffic originating from your IoT VLAN while preserving detection from external sources — which is the threat surface BPFDoor C2 would actually use.

Lessons Learned
A few things I’d tell someone starting a similar investigation:
Look at the source IP first. Internal-only source for a “C2 inbound” alert immediately changes the interpretation.
Run controls. Any active test against a target needs an equivalent test against a known-good device. If both fire your detection, you’re measuring something else.
Search for the SID and rule history before assuming the alert is high-fidelity. ET signatures vary widely. Even “High confidence” rules have known false-positive patterns when applied to traffic profiles the rule author didn’t anticipate (here: embedded ICMP implementations using timestamp counters as payload).
Consider base rates. State-sponsored implants in mass-market consumer vacuums are not impossible, but they’re vastly less likely than coincidental signature matches. Bayesian thinking applies to threat hunting.
Listen when something pushes back on your conclusion. I had an AI assistant repeatedly tell me my evidence didn’t support my conclusion. I argued. The AI was right. The hours I spent arguing were hours I should have spent capturing more data — which is what ultimately resolved the question.
Acknowledgments
- To Claude (Anthropic), for refusing to draft the FBI report I would have regretted. The skepticism was warranted.
- To the Emerging Threats project for the open ruleset that gives home operators meaningful network visibility, even when (and especially when) individual rules need tuning.
- To Dennis Giese for the years of public Roborock security research that helped me distinguish “weird vacuum behavior” from “normal vacuum behavior.”
- To my Roborock, which is just a vacuum.
Appendix A: Rule Reference
- Rule:
ET MALWARE BPFDoor ICMP Echo Request, X:[COMMAND] (Inbound) - SID:
2069175 - Source: Emerging Threats OPEN ruleset
- Created: 2026-05-05
- Updated: 2026-05-08
- Severity: Critical (per rule metadata)
- Confidence: High (per rule metadata)
- Affected products: Linux
- Original BPFDoor research: PwC “Tracking Red Menshen” (2022), Sandfly Security BPFDoor analyses
- Public BPFDoor samples: Malware Bazaar, VirusTotal
Appendix B: The Math, Spelled Out
Given:
- Payload first 4 bytes =
uint32microsecond counter, effectively uniform random over time - Rule content match:
0x58 0x3aat depth 2 (first 2 bytes of payload) - Heartbeat rate: 1 ping every 20 seconds = 4,320 pings/day
Probability of 0x58 0x3a appearing at offset 0–1 in a uniformly random 4-byte value:
P(match) = 1 / 256^2 = 1 / 65,536
Expected pings between hits: 65,536
Expected days between hits: 65,536 / 4,320 ≈ 15.2 days
If you also count 0x58 0x3a appearing at offset 1–2 (i.e., the second and third bytes), and offset 2–3, that’s three windows. Approximately:
P(match in any of first 4 bytes) ≈ 3 / 65,536 ≈ 1 / 21,845
Expected days between hits ≈ 21,845 / 4,320 ≈ 5.1 days
The signature, however, has depth:2 — meaning it only matches at offset 0. So the more accurate prediction is one false positive every ~15 days per device with this payload pattern. Adjust your suppression rules accordingly.
Appendix C: Tooling Used
- UniFi Dream Machine (UDM) with Suricata IPS
tcpdump(on-gateway packet capture)mongoshell (UniFi alarm database queries)- Python 3 with stdlib
struct(custom pcap parsing) - Wireshark (visual packet inspection)
- Patience and a willingness to be wrong