「ライブラリを使えばOT機器との通信なんて簡単でしょ?」
そう思っていた時期が、私にもありました。でも実は、ライブラリはアーキテクチャを隠してしまうんですよね。海外のセキュリティ研究者 RUGERO Tesla(@404Saint)さんが書いた記事が、まさにその落とし穴をリアルに語っていて、めちゃくちゃ刺さりました 🔥
今回は、産業制御システム(ICS)向けプロトコル「EtherNet/IP」のパケットを手動で生バイト列から組み立てたという話を紹介しつつ、Pythonで実際にどんなコードになるのかを解説していきます。
🏭 EtherNet/IPって何?

EtherNet/IPは、工場や設備で使われるPLC(プログラマブルロジックコントローラ)と通信するための産業用プロトコルです。
イメージとしては「工場のロボットアームや生産ラインの機械に命令を送るための専用通信言語」、そんな感じです。
一般的な開発では pycomm3 などの高レベルライブラリを使います。でも、ライブラリに頼るだけではパケットの内部構造・シーケンス番号・コマンド体系を本当には理解できない、というのが今回の核心です。
🔬 手動パケット組み立て:Pythonで再現してみる
EtherNet/IPのセッション確立には、まず Register Session(コマンド 0x0065) を送る必要があります。ポイントをまとめるとこんな感じです。
- ヘッダは 24バイト固定
- コマンド・長さ・セッションハンドル・ステータスなどをリトルエンディアンで詰める
- ペイロードはわずか4バイト(プロトコルバージョン+オプションフラグ)
import socket
import struct
# EtherNet/IP の Register Session パケットを手動で組み立てる
def build_register_session():
command = 0x0065 # Register Session コマンド
length = 0x0004 # ペイロード長(4バイト)
session_handle = 0x00000000 # 初回は0
status = 0x00000000
sender_context = b'\x00' * 8
options = 0x00000000
# ヘッダを struct でリトルエンディアン詰め
header = struct.pack(
'
ここが重要です ✅
struct.packのフォーマット文字列'<'でリトルエンディアンを明示している- ポート番号 44818 がEtherNet/IPの標準ポート
- ライブラリなしでも、バイト列の意味が完全に把握できる
💡 「抽象化を捨てる」ことで何が見えてくるの?
ライブラリを使わずに手を動かすと、こんなことが分かってきます。
- ⚙️ セッション確立→サービスリクエスト→切断という通信シーケンスの全体像
- 🔍 エラーコードが返ってきたとき、どのフィールドが原因なのかが特定できる
- 🛡️ セキュリティ研究において「なぜこの脆弱性が生まれるか」の根っこの理由が見える
「むずかしそう」と感じるのは当然です。でも、一度バイトレベルで通信を追いかけると、ライブラリのドキュメントの読み方まで変わりますよ 😊
まとめ
抽象化ライブラリは便利だけど、本当の理解は生バイトの中にあるというのが今回の核心です。EtherNet/IPに限らず、MQTTやModbusなど産業系プロトコルを扱う機会がある方は、ぜひ一度「ライブラリなし」で触ってみてください。
きっと「こういう仕組みだったのか!」という発見が待っています。一緒に深掘りしていきましょう 🚀
📡 Arduinoをもっと深く学ぼう!
Arduino・ラズパイ・ロボットプログラミングを体系的に学びたい方へ。おすすめのUdemyコースや電子部品もまとめています。








