alosafuzz
ICS/OT protocol security research — fuzzing, scanning, and coordinated disclosure.

A Researcher's Guide to Omron FINS

Vendor: omron · Sector: manufacturing · Transport: fins-udp · Project: icsfuzzer

Part of the alosafuzz ICS protocol-guide series — written for fuzzing and vulnerability researchers. Defensive posture: wire format, fuzz surface, and bug classes only; no weaponized exploit or DoS code.


1. What it is & where it runs

FINS (Factory Interface Network Service) is Omron’s PLC protocol — CJ/CS/CP and NJ/NX series controllers across packaging, automotive, and general discrete manufacturing. It carries memory read/write, CPU run-state control, clock, and status. FINS/UDP (this guide) runs on UDP port 9600; a FINS/TCP variant adds a small framing header on top of the same commands.

For a researcher FINS is the simplest target in the set — a 12-byte fixed header, fully stateless, no authentication — which makes it a clean place to study memory-area addressing bugs and CPU-control authorization: every command is a single self-contained datagram, and the whole surface is reachable from one sendto.

2. Session & transport model

Stateless UDP: no handshake, no session. Each request is an independent datagram; the response echoes the routing header (with source/destination swapped) plus a 2-byte end code. A SID (service id) byte correlates request and response — but because UDP ordering isn’t guaranteed, a SID mismatch is unexpected data, not a crash.

3. Wire format

12-byte FINS header: | Off | Field | Note | |—|—|—| | 0 | ICF | info-control (0x80 request, response 0xC0) | | 1 | RSV | reserved (0x00) | | 2 | GCT | gateway count (0x02) | | 3–5 | DNA / DA1 / DA2 | destination network / node / unit | | 6–8 | SNA / SA1 / SA2 | source network / node / unit | | 9 | SID | service id (request/response correlation) | | 10–11 | MRC / SRC | main + sub command code (the dispatch pair) |

Command body follows MRC/SRC. Response = ICF 0xC0 + swapped routing + SID + MRC/SRC + MRES/SRES end code at offset 12–13 (0x0000 = normal) + data.

Key command codes (MRC,SRC): Memory Area Read 01 01, Write 01 02, Fill 01 03; Run 04 01, Stop 04 02; Clock Read/Write; Controller Status Read. Memory-area codes (in the Read/Write body): CIO word 0x82 / bit 0x80, DM word 0x02 / bit 0x30, EM0 word 0x0A, HR word 0x52, Timer PV 0xB0, Counter PV 0xB1 — the address is area + 3-byte address + element count.

4. The fuzz surface

Classification line: a non-zero end code (MRES/SRES) is a spec-compliant error — exception, not a crash. A SID mismatch is UNEXPECTED_DATA (UDP reordering), not a crash. The crash signal over UDP is a timeout (no datagram) on a well-formed request, or a truncated/malformed reply.

Structured vs. coverage-guided: FINS is a pure structured-generator target — the surface is the command/area/count semantics, and it’s shallow and stateless. There is little open-source Omron-stack code to instrument, so coverage-guided fuzzing isn’t readily available; black-box structured generation against a real CJ/ CP PLC (or the responder below) is the path.

5. Known vulnerabilities & bug classes

FINS has no auth and no integrity, so the durable classes are:

(No single headline CVE is cited; FINS findings are per-device and the protocol’s lack of auth is the dominant exposure.)

6. Building a test harness

Robustness responder (scripts/fins_responder.py, port 9610, UDP): a single- threaded recvfrom loop; writable areas CIO/DM/EM0/HR word + DM bit; read-only rejection for Timer PV, Counter PV, CIO bit; CPU Run/Stop/Reset all rejected (safety); response header built by swapping DA1/SA1; sidecar JSONL.

Harness notes:

Calibration (software responder): ~180 cases across 8 categories (boundary, binary_corruption, sequence, state_machine, memory_read, memory_write, cpu_control, command_sweep); 62 tests. (No standalone research note — this guide is built from the module.)

7. Open-source implementations & tooling

8. Spec references & sources

9. Open questions / under-explored surface

Worked example: protocols/fins/fuzzer.py (8 categories, ~180 cases) with scripts/fins_responder.py (UDP robustness mock).