SuperAPI AI Directory

re-ics

General & other

View repository

Updated 10-15 16:44

About this skill

Industrial control protocol reverse engineering (ICS/SCADA)

When to use / when not to use

  • Use: when you have industrial control (ICS/SCADA) traffic (pcap / live capture) and need to parse Modbus / DNP3 / OPC UA semantics
  • Use: to reconstruct a PLC/RTU point table (coils/registers/object mapping) from traffic
  • Use: for industrial control device (PLC/HMI/RTU) firmware and communication analysis (together with [[re-firmware]])
  • Use: for authorized-scope ICS security testing (see step 5 boundaries)
  • Don't use: when the traffic is ciphertext/has an encryption layer (first [[re-crypto-id]] → [[re-crypto-keys]] → [[re-crypto-decrypt]])
  • Don't use: for non-industrial-control custom protocols (that's [[re-proto-rev]])
  • Don't use: when you only need packet capture (that's [[re-netcap]])

Tool preparation

Verify all tools before use. ICS analysis red lines:Only test authorized systems, default to isolated networks ([[re-analyze/platform-tips]] highest principle) — the test environment must never connect to a production OT network (see step 5).

wireshark / tshark —— the mainstay for ICS parsing (modbus/dnp3/opcua dissectors built in)

  • Linux: apt install wireshark tshark / dnf install wireshark-cli wireshark / pacman -S wireshark-cli wireshark-qt(Arch has split it into wireshark-cli + wireshark-qt; when installing on Debian, choose "allow non-root packet capture", or sudo dpkg-reconfigure wireshark-common then add the user to the wireshark group)
  • macOS: brew install --cask wireshark(includes the tshark CLI; or brew install wireshark CLI only)
  • Windows: choco install wireshark(or the official installer package); use the Linux version inside WSL
  • Verify: tshark --version;tshark -G fields | grep -iE 'modbus|dnp3|opcua' lists ICS field names (field names may be slightly adjusted between versions; the output of that command is authoritative)

scapy —— modbus layer parsing and construction (contrib layer)

  • All platforms: pip install scapy(Python 3, recommended)
  • Linux: apt install python3-scapy(Debian-based; includes the contrib layer)
  • Verify: python3 -c "from scapy.contrib.modbus import ModbusADURequest, ModbusADUResponse; print('ok')"
  • Note: the modbus layer is in scapy.contrib.modbus(not a default layer, requires explicit import,ModbusADURequest/ModbusADUResponse + ModbusPDU0X*Request/Response various function code classes);no version of scapy has DNP3 / OPC UA layers → for DNP3/OPC UA use Wireshark parsing or write a custom parser (see step 4)
  • To read pcap use rdpcap(pure Python parsing, no libpcap needed)

python3 —— runtime environment for parsing scripts

  • For installation and verification see [[re-proto-rev]] tool preparation

Operating steps

Execute in order, and record the result of each step. Prerequisites: the traffic has been captured ([[re-netcap]]) and is plaintext (for ciphertext first go through [[re-crypto-decrypt]]).

  1. Protocol identification (port + characteristics):

    tshark -r cap.pcap -T fields -e tcp.dstport | sort | uniq -c | sort -rn   # 端口统计
    tshark -r cap.pcap -q -z io,phs                                          # 协议分层统计(dissector 命中情况)
    
    • Quick port reference: Modbus/TCP 502 (Modbus over TLS 802), DNP3 20000/TCP,UDP,SCTP (secure DNP3 dnp-sec 19999/TCP,UDP,SCTP), OPC UA binary 4840/TCP, S7comm 102, EtherNet/IP 44818, BACnet/IP 47808/UDP
    • Ports serve only as an initial judgment (industrial control often uses non-standard ports); confirm with characteristics: Modbus/TCP transaction = transId(2) + protoId 0x0000(2) + len(2) + unitId(1) + function code(1); the first 2 bytes of a DNP3 link frame are fixed 05 64; the OPC UA binary header has a 3-byte message type (HEL/ACK/OPN/CLO/MSG)
  2. Wireshark parsing (dissector first):

    • GUI: open the pcap; standard ports automatically hit the modbus/dnp3/opcua dissectors; non-standard ports → right-click Decode As to specify the protocol (or command line tshark -d tcp.port==5020,modbus)
    • Filtering and export:
      tshark -r cap.pcap -Y 'modbus' -T fields -e modbus.func_code -e mbtcp.unit_id   # unit id 官方字段在 mbtcp 层(modbus.unit_id 非官方)
      tshark -r cap.pcap -Y 'dnp3' -T fields -e dnp3.ctl -e dnp3.dst -e dnp3.src       # 控制字 dnp3.ctl(子字段 dnp3.ctl.dir/prm/fcb/fcv/prifunc/secfunc);站地址在顶层 dnp3.dst/dnp3.src
      tshark -r cap.pcap -Y 'opcua' -T fields -e opcua.nodeid.numeric -e opcua.nodeid.string -e opcua.nodeid.guid   # nodeid 无顶层字段,按类型用子字段
      
    • When the field name is uncertain, use tshark -G fields | grep -iE 'modbus|dnp3|opcua' as the authoritative output
  3. Function Codes and Object Point Tables:

    • Modbus Function Code Quick Reference: 01 Read Coils / 02 Read Discrete Inputs / 03 Read Holding Registers / 04 Read Input Registers / 05 Write Single Coil / 06 Write Single Register / 0F Write Multiple Coils / 10 Write Multiple Registers; Exception Response = 0x80 | Function Code
    • Point Table Construction: Collect (unitId, function code, starting address, quantity) from the traffic, map register/coil addresses to tags (temperature, pressure, valve position, etc.) against the engineering documentation, and produce an "address → type → value → meaning" table
    • Value Parsing: registers are 16-bit big-endian; for 32-bit combinations (IEEE754 floating point / int32) pay attention to word order (see Pitfall 2)
    • DNP3 Object Point Table: group-variation number.Static quantities and event quantities are paired, different groups—Group 1 Binary Input ↔ Group 2 Binary Input Event;Group 3 Double-bit Binary Input (static) ↔ Group 4 Double-bit Input Event; Group 10 Binary Output Status ↔ Group 11 Binary Output Event; Group 30 Analog Input ↔ Group 32 Analog Input Event.Don't mistake static quantity groups for event groups(Group 3 is not an "event"); when reconstructing the point table, model it in static/event pairs, otherwise the event cache and variation semantics will be judged entirely reversed
    • OPC UA: NodeId (NamespaceIndex:Identifier) + BrowseName is the point identifier
    • Difference Method: Compare the traffic of normal operation vs. triggered action; the differing messages = key points
  4. scapy parser (modbus) + custom DNP3 parsing:

    from scapy.all import rdpcap
    from scapy.contrib.modbus import ModbusADURequest, ModbusADUResponse
    
    for p in rdpcap('cap.pcap'):
        if 'TCP' in p and p['TCP'].dport == 502 and len(p['TCP'].payload) >= 6:
            mb = ModbusADURequest(bytes(p['TCP'].payload))   # MBAP+PDU 一起解
            mb.show()        # transId/protoId/len/unitId + PDU(功能码与寄存器字段)
    
    • DNP3 (scapy has no layer): the link layer header structure is fixed (05 64 | len | ctrl | dst(2) | src(2) | CRC(2), after the source address comes the mandatory header CRC), so parsing with struct is sufficient; subsequent data is chunked with a 2-byte CRC every 16 bytes; the transport layer/application layer proceed according to the group-variation format of IEEE 1815
    • Construct test messages (authorization and isolation must be confirmed before sending to the testbed PLC, see Step 5):
      from scapy.contrib.modbus import ModbusPDU03ReadHoldingRegistersRequest
      req = ModbusADURequest() / ModbusPDU03ReadHoldingRegistersRequest(startAddr=0, quantity=10)   # 字段名是 camelCase startAddr(scapy 定义),不是 start_addr
      
  5. Authorization Scope and Security Testing Boundaries:

    • Only test authorized systems: owned/laboratory testbeds (PLC/RTU/HMI simulators), vendor-authorized test equipment, and targets within the scope of vulnerability disclosure;Strictly prohibitedto conduct unauthorized testing on production SCADA/DCS and critical facilities such as power grids/water plants/power plants (involves law and public safety)
    • Physical/logical isolation: separate the test network from the production OT network (independent subnet/VLAN, firewall); the packet capture and analysis machine connects only to the testbed; by default follow the [[re-sandbox]] network isolation approach
    • Sending abnormal messages (fuzzing, illegal function codes, unauthorized writes) is limited to the testbed, with full audit logging throughout; if uncertain, fall back to passive analysis (capture and parse only)
    • Report sanitization: do not expose production environment site/personnel/network topology details; comply with local industrial control security testing regulations and retain written authorization

Cross-domain combination

  • [[re-protocol]]: this skill is its industrial control branch (the entry point of the decision tree after Step 5 of the workflow)
  • [[re-netcap]]: source of captured raw material (capture points/sandbox isolation)
  • [[re-sandbox]]: security testing boundaries—testbed isolation, only test authorized systems ([[re-analyze/platform-tips]] highest principle)
  • [[re-crypto-id]] / [[re-crypto-keys]] / [[re-crypto-decrypt]]: encryption variant pre-check (Modbus over TLS, OPC UA secure channel; DNP3 Secure Authentication is an authentication extension rather than default encryption, and whether TLS/VPN is layered on top needs separate confirmation)
  • [[re-firmware]]: PLC/RTU/industrial control device firmware analysis (extraction → rootfs → emulation)
  • [[re-proto-rev]]: state machine reconstruction for non-standard industrial control protocols
  • [[re-hardware-io]]: capturing Modbus RTU / fieldbus over serial

Common pitfalls and traps

  • Mixing up function code semantics: symptom—treating 01 read coils as 03 read registers, all value types wrong (coils 1 bit vs registers 16 bits); cause—function codes determine data type and address space, and statistics were not bucketed by function code; countermeasure—first tally by function code (tshark -Y modbus.func_code), and separate point table spaces by function code (01/02 coil space, 03/04 register space)
  • Big-endian byte order: symptom—register values huge/garbled (e.g. temperature 0x0128=296 read as little-endian becomes 0x2801=10241); cause—Modbus registers default to big-endian, and multi-register combinations (32-bit/float) also have vendor-defined high/low word order layered on top; countermeasure—validate with known values (reasonable range/constants/precision), try all big/little-endian and high/low word order combinations, and once determined, fix it and write it into the parsing script
  • Mixing up protocol variants: symptom—dissector misses or parsing is misaligned (Modbus RTU frames parsed as TCP, DNP3 over TLS port); cause—the same protocol has multiple carriers: Modbus RTU/ASCII (serial) vs TCP (502) vs TLS (802); DNP3 serial/TCP 20000 (TLS often reuses 20000 or dnp-sec 19999); OPC UA binary (4840) vs HTTP endpoint; countermeasure—step 1 confirm both port and signature, Wireshark Decode As / -d force the dissector
  • Treating encrypted variants as plaintext: symptom—high traffic entropy, dissector outputs only garbage or marks it as encrypted; cause—Modbus over TLS / OPC UA secure channel are encrypted by default; DNP3 Secure Authentication is an authentication/integrity extension rather than a confidentiality wrapper, and when entropy is high one should first confirm whether a separate TLS/VPN confidentiality layer exists; countermeasure—first determine via [[re-crypto-id]], extract keys from HMI/client process memory and certificates ([[re-crypto-keys]]), and run the decryption chain via [[re-crypto-decrypt]]
  • Point table mapping misalignment: symptom—addresses do not match, value meanings wrong; cause—vendors have different addressing conventions (0-based vs 1-based, separate address spaces for coils/registers, mirrored addresses); countermeasure—cross-validate across multiple packets (repeated reads of the same address should be stable), combine with engineering documentation, and build tables separated by space
  • S7CommPlus authentication chain (S7-1200/1500): symptom—S7 traffic version mismatch, authentication data cannot be decoded, flows captured with PLCSIM do not match the real device; cause—S7CommPlus is a layered authentication system, with large differences between V1(0x01)/V2(TLS,0x02)/V3(0x03); structure: OpenSSL standard layer (AES-128-ECB/SHA-256/HMAC) + BigInt big number layer (192-bit 6 dword encoding) + Monolith function family (1-11) + key derivation transform pipeline (TPre/T7/T12/T13/KeyD/LutG/Csum/Seed, 8 Transform types) + encrypted authentication data (AES-ECB chain + Blob metadata header + AES-CTR/HarpoHash checksum); key constants: session key 24B, challenge 20B, real PLC public key 40B vs PLCSIM 64B, authentication Blob 180 vs 216B; countermeasure—first confirm the protocol version and target (real device vs PLCSIM, the flow and key lengths differ), and restore along version-specific lines: plaintext structure → key derivation pipeline → Session Key rotation → Packet Digest