DARK MATTER

CDI Engineer's Technical Blog

社内PKIを支えるYubiKeyのAttestation

はじめに。

はじめまして。技術部のishizukaです。社内でYubiKeyを使って遊ぶ機会に恵まれ、クライアント証明書の発行のフローと遊んでいる最中に気づいた面白いことをまとめていきます。基本CLIベースな記事なので画像がほとんどないです。

概要

CyberDefense社内では、YubiKeyに入れた証明書を利用した個人認証を行っています。これによりパスワードからの脱却を目指しております(多少誇張)。一方で、ICカード等の物理デバイスの運用は、PINの誤入力によるロック、物理的な紛失といった別のリスクが存在します。

これらのリスクに対して社内では証明書のリモート発行を準備することで、誤ってロックアウトされてしまった際でも出社することなく証明書を再発行する運用ができるように準備を整えました。この証明書発行フローを簡略化して説明していきます。

f:id:m-ishizuka:20200526123123j:plain
yubikey_farm

CDI社内で、YubiKeyにより認証している箇所

社員はWindows/Mac/Linuxのいずれかを利用し以下のリソースをYubiKeyの証明書で利用しています。

  • VPN
  • 802.1X(有線・無線の双方)
  • 一部Webサービス
  • Kerberos
  • メール
  • SSH(実際は証明書ではないですが)
  • 一部OSのログイン(U2Fの場合も)

YubiKeyのAttestation

公式のAttestationに記載の通り、秘密鍵がYubiKey内部で生成された際に、Attestationが生成できます。このAttestationの機能のおかげで、その秘密鍵が、外部で生成され、importされたものではないことが保証できます。これにより、秘密鍵がマルウェア等により作成されているリスクや、ファイルベースでコピーできるリスクなどをほぼ無視できます。リモート証明書発行にはこのAttestationの機能を利用します。

Attestationスロット(F9)にそのYubiKeyの中間CA証明書と秘密鍵が入っており、各種スロット(PIV Authentication, Card Authentication, Key Management, Digital Signature)のAttestationを生成する・・・ようです。(ブラックボックスのため推測)

今回の内容はYubiKey5をベースに記載してますが、openssl1.0を利用するか、本家のPythonスクリプトを利用することでYubiKey4でも確認が可能です。

確認環境

ベースOS

Mac or Linuxを推奨します。opensslとyubico-piv-tool(https://developers.yubico.com/yubico-piv-tool/Releases/) が手軽に利用できるからです。WSL2でも利用できるかもですが、非推奨というか利用方法がよくわからないです。

またDockerコンテナ内で作業もできますが、USBをコンテナ側から利用できるようにする必要がありますので、これも非推奨というかハードル高いです。 一見ハードルが高そうなVMのLinuxが一番ハードル低いかもです。

今回はUbuntu20.04がリリースされて間もないので、折角なのでVirtualBoxにこれを入れ使います。インストール手順は省略します。

プログラム

  • openssl
    • mac/ubuntu ともに初期で入っているはず。
  • yubico-piv-tool
    • ubuntuなら sudo apt install -y yubico-piv-tool で入ります。
    • macは適当にインストーラを使うなど。

手順

今回は分かりやすいようにリリースされている既存のツールのみを利用し、PIV authenticationのスロット(9a)のみ説明していきます。

なお、実際の会社では以下のようなフローになるかなと思います。

  1. 資産管理チーム
    1. シリアルと、利用者と紐付け、証明書発行チームに連絡
  2. 証明書発行チーム
    1. Attestationの中間CAを保存
    2. CSR生成
    3. Attestation 生成
    4. 証明書発行
    5. YubiKeyに証明書の導入、ユーザにYubiKeyを渡す
  3. ユーザ
    1. 利用

再発行時は、

  1. ユーザ
    1. CSR生成
    2. Attestation 生成
    3. 証明書発行チームにCSRとAttestationを送付・依頼
  2. 証明書発行チーム
    1. Attestationの検証
    2. CSRとAttestationの確認
    3. 証明書発行
    4. ユーザに証明書送付
  3. ユーザ
    1. YubiKeyに証明書の導入
    2. 利用

Attestationの中間CA証明書を保存

社員に証明書を発行する前にYubiKeyからAttestationの中間CA証明書を保存しておきます。これを利用し、そのYubiKeyが会社から支給されたYubiKeyか否かの判断材料になります。

$ yubico-piv-tool -a read-certificate -s f9 -o test-intca.crt
$ openssl x509 -in test-intca.crt -noout -issuer -subject
issuer=CN = Yubico PIV Root CA Serial 263751
subject=CN = Yubico PIV Attestation

このようにIssuerがYubico PIV Root CA Serial 263751の中間CA証明書が出力されます。先に記載したAttestationにRootCAの証明書があるので、検証できるか確認してみます。

$ wget -q https://developers.yubico.com/PIV/Introduction/piv-attestation-ca.pem
$ openssl verify -CAfile piv-attestation-ca.pem test-intca.crt 
test-intca.crt: OK

Verify OKになりました。これでこの中間CA証明書は適切に利用可能であることがわかりました。

CSR生成

この作業は利用者側で実行でも構わない箇所です。というのも在宅勤務でYubiKeyをロックさせてしまう可能性を考慮してです。なお私もこのコロナ禍の初期に一度YubiKeyの証明書を消失させてしまった経験があります。VPNが利用できず辛みしかありませんでした。

ECDSAのほうが秘密鍵作成時間が短いので、意図的にECDSAを利用しています。

PINは初期値の123456を利用しています。

$ yubico-piv-tool -a generate -s 9a -A ECCP256 -o 9a.pub
Successfully generated a new private key.
$ yubico-piv-tool -a verify-pin -a request-certificate -i 9a.pub -s 9a -S "/CN=test/" -o 9a.csr -P 123456
Successfully verified PIN.
Successfully generated a certificate request.

さてCSRが作成できました。合わせてCSRの中身を見てみます。

$ openssl req -in 9a.csr -noout -subject
subject=CN = test 

適切なPEMフォーマットのCSRだとわかります。

Attestation 生成

され、これだけだと誰がどのようにして作成したCSRかわからないため、Attestationを生成します。ついでに、以前に取得した中間CA証明書を利用して、検証できるか確認します。

$ yubico-piv-tool -a attest -s 9a -o 9a.attest.crt
$ openssl verify -CAfile <(cat piv-attestation-ca.pem test-intca.crt ) 9a.attest.crt 
9a.attest.crt: OK

Attestationの検証に成功しました。この9a.attest.crtと9a.csrを証明書発行チームに送ります。

証明書発行チーム

証明書発行チームが見る内容は以下です。

  1. Attestationの検証に通ること
  2. Attestationの中身の公開鍵と、CSRの公開鍵が一致すること
  3. Attestationの証明書内のYubiKeyのシリアルが配布したYubiKeyのシリアルであること

1により、秘密鍵がYubiKey内部で生成されており、複製が計算量的に非常に困難であることが保証できます。 2により、提出されたCSRの公開鍵がAttestationで保証できます。 3により他の人のYubiKeyではないことが確認できます。1

この2つを確認することで、提出されたCSRが会社支給のYubiKeyで生成された秘密鍵を利用して作成されたものであることがリモートから検証できます。

Attestationの検証

$ openssl verify -CAfile <(cat piv-attestation-ca.pem test-intca.crt ) 9a.attest.crt
9a.attest.crt: OK

Attestationの中身の公開鍵とCSRの公開鍵の比較

$ diff <(openssl req -in 9a.csr -noout -pubkey) <(openssl x509 -in 9a.attest.crt -noout -pubkey)

Attestationの証明書内のYubiKeyのシリアルの確認

Attestationの中身 を見る限り、単純なシリアルナンバーのはずだが、頭2バイトに不要な値が。

$ openssl asn1parse -in 9a.attest.crt | grep -A1 "1.3.6.1.4.1.41482.3.7"
  473:d=5  hl=2 l=  10 prim: OBJECT            :1.3.6.1.4.1.41482.3.7
  485:d=5  hl=2 l=   6 prim: OCTET STRING      [HEX DUMP]:020347B06E
$ printf "%d\n" 0x47B06E                                                                                                                                                   
4698222

検証に通ること、diffによる差がないことを持って提出されたCSRがYubiKey内部で生成されたことが保証されました。これをもってCSRに署名をします。ここは省略します。

YubiKeyへの証明書のimport

証明書発行チームから9a.crtというファイルが届いたという前提で後続作業をします。

以下の可能性を考慮し正当性を確認しましょう。

  • 証明書発行チームが悪意を持って違う証明書を送ってきた
  • 証明書発行チームのヒューマンエラーで違う証明書を送ってきた
  • 途中経路で改竄が発生し、異なる証明書になってしまった

証明書発行チームが行っているCSRとAttestationの比較を今度は送付された証明書で行います。自身のYubiKeyに入っているAttestationのPubkeyと送られてきた証明書のpubkeyを比較します。

$ diff <(yubico-piv-tool -a attest -s 9a | openssl x509 -in - -noout -pubkey) <(openssl x509 -in 9a.crt -noout -pubkey)

さて、これで送られてきた証明書が正しいものだと確認できました。証明書をyubikeyに入れ、最後に確認します。

$ yubico-piv-tool -a import-certificate -s 9a -i 9a.crt 
Successfully imported a new certificate.
$ yubico-piv-tool -a status

Attestation周りの確認

  • 外から鍵をimportするとAttestationは生成されない。というよりエラーが帰ってくる。
$ openssl genpkey -paramfile <(openssl ecparam -name prime256v1 ) -out 9a.key
$ yubico-piv-tool -a import-key -i 9a.key -s 9a
Successfully imported a new private key.
$ yubico-piv-tool -a attest -s 9a
Failed to attest data.

持っているYubiKeyの秘密鍵が外からimportされたものではないことを確認する意図なら、証明書が出力されればそれでOKと考えることもできます。

結び

YubiKey5を利用したオンライン証明書発行を支えるAttestationの機能の確認と実際の利用の仕方についてまとめました。次回はYubiKeyを破壊します。


  1. 確認した限り、60本のYubiKeyで同一の中間CA証明書でした。単位については不明(同一ロットでなのか、注文時のまとめ買い数なのか、Firmwareなのか)

LANケーブルをニッパーで切断し5秒でネットワークへ侵入・盗聴できるか実験してみました

f:id:yasuikj:20200330193144j:plain
サイバーセキュリティにおいてLANケーブル(有線LAN)からの侵入について考えたことがあるでしょうか?本稿では、LANケーブルをニッパーで切断してネットワークへ侵入・盗聴した実験結果を紹介します。切断してから何秒で侵入・盗聴できたのでしょうか?

本記事は、ケーブルを切断してネットワークへ侵入・盗聴されるリスクがある事を知っていただく事を目的としています。
ご自身の環境以外では試さないようお願いします。

なぜLANケーブルからの侵入?

技術部の安井です。長年制御システムを開発してきた経験から制御システムセキュリティ向上に取り組んでいます。制御システムの業界では、近年外部ネットワークを経由しての侵入や内部に持ち込まれたUSBメモリからの侵入が注目されています。一方で、なぜかネットワークを構成する大きな要素であるLANケーブルや光ケーブルからの侵入への注目度は低いようです。制御システムに関わらず、サイバー攻撃といえばインターネットを介した攻撃が主流なため、物理的な侵入を要するLANケーブルや光ケーブルからの侵入はあまり注目されていないのかもしれません。無線LANのセキュリティについてはよく語られていますが、建物内にひかれたLANケーブルや街中にひかれた光ケーブルについては話題が少ないと感じています。攻撃者は、防御側が見過ごしているような弱点をついて攻撃してくるものです。注目度が低いと感じるLANケーブルや光ケーブルについても必ずしも安全というわけではないことは知った上でセキュリティ対策を考えていただきたいと考えた次第です。

LANケーブルってセキュリティ上安全?

パソコンをスイッチングハブにLANケーブルで接続していたとします。LANケーブルをコネクタから抜いたり、LANケーブルを物理的に切断してハブを挟み込めば、そこからネットワークにアクセスできてしまいます。
このような攻撃に対し、ネットワークの知識がある人なら、「LANケーブルをコネクタから抜けないように鍵をかければ抜かれることはないし、定周期で死活監視をしていれば、なにかおかしなことが起こっていることに気づくことができる。」と考えかもしれません。はたして、本当に検出できるでしょうか?

本稿では、まずLANケーブルから侵入できることを知らない人向けに、LANケーブルを切断してLANケーブルを自作する方法で侵入・盗聴できる事を実験しました。次に、「ぐっとすプラグ」という器具を使い短時間で侵入・盗聴できてしまう事を実験しました。

侵入・盗聴実験

実験では、侵入・盗聴する対象として、図1に示すノートPCをスイッチングハブにLANケーブルで接続し、ノートPCからスイッチングハブにPing通信し続けている環境を用意しました。Ping通信は1秒周期でノートPCからパケットを送信し受信したスイッチングハブがノートPCに応答パケットを返す状態としました。
f:id:yasuikj:20200330184913p:plain:w300

f:id:yasuikj:20200401200406j:plain
図1:侵入・盗聴対象ネットワーク

盗聴するための装置として図2に示すリピータハブと盗聴用PCを準備しました。
f:id:yasuikj:20200330185507p:plain:w100
f:id:yasuikj:20200401200719j:plain

図2:リピータハブと盗聴用PC

盗聴用PCはWindowsPCにパケットキャプチャソフトのWiresharkをインストールしたPCです。リピータハブを使ったパケットキャプチャのイメージを図3に示します。リピータハブはLANにつなげた場合、通信パケットを全てのポートに送り出す装置であり通信パケットを解析する際に使用される装置です。

f:id:yasuikj:20200330185921p:plain
図3:リピータハブを使ったパケットキャプチャのイメージ

この後に実施する実験の前に、ネットワークへの侵入と盗聴の概要を説明します。ネットワークへの侵入は、ノートPCとスイッチングハブ間のLANケーブルをニッパーで切断し、切断したLANケーブルの間にリピータハブを設置します。このようにすると、ノートPCとスイッチングハブ間に流れる通信パケットはリピータハブを経由して盗聴用PCにも流れ込むためWiresharkで盗聴が行えます。この実験では、ケーブルを切断してからどの程度の時間でWiresharkでPing通信の盗聴が行えるかを測定します。

LANケーブルを切断してLANケーブルを自作する方法での実験

まず、LANケーブルを切断してLANケーブルを自作する方法で試してみました。

使用した機器
サンワサプライ かしめ工具(ラチェット付) LAN-TL8 3,482円
エレコム RJ45コネクタ 単線仕様 10個入り LD-RJ45T10A 192円
ニッパー 持っていた物

f:id:yasuikj:20200330000538j:plain
ケーブル自作道具

リピータハブの設置

ノートPCとスイッチングハブの間のLANケーブルをニッパーで切断します。
f:id:yasuikj:20200330221649j:plain
切断した2箇所にLANコネクタをかしめ工具でとりつけます。
f:id:yasuikj:20200330221741j:plain
2つのLANコネクタを盗聴用PCとつなげたリピータハブに接続します。
f:id:yasuikj:20200401201414j:plain

ニッパーで切断してから、LANコネクタをとりつけるまで、日頃このような作業を行なっていないこともあり約20分かかりました。慣れた人でも数分はかかるのではないかと思います。

侵入・盗聴時間の測定

上記の作業後、盗聴用PCのWiresharkの画面でノートPCとスイッチングハブのPingパケットがキャプチャできていることを確認しました。
f:id:yasuikj:20200331133144p:plain
ニッパーで切断してから、盗聴用PCで、ノートPCとスイッチングハブ間の通信のパケットをキャプチャできるまで、20分以上かかりました。

20分以上物理的に通信断の状態が継続しているので、例えば1分周期に死活監視をおこなっていれば、十分異常を検出できることがわかります。

「ぐっとすプラグ」を使う方法での実験

前述の実験のとおり、LANケーブルを切断すると数分単位の通信断が発生します。これならば、定周期で死活監視をしていれば通信断を容易に発見できそうですが、本当にそうでしょうか?
作業的にはケーブルを切断し、間にリピータハブを挟むだけですから高速に作業ができれば通信断の時間は短くできてしまいそうです。なにか道具がないかと探したところ「ぐっとすプラグ」というものを発見しました。
(注)「ぐっとすプラグ」は専用工具なしでLANケーブルを接続するための製品であり、ここで紹介するような利用を目的とした製品ではありません。

使用した機器
パナソニック(Panasonic) ぐっとすプラグ CAT6 NR3555 × 2 1,238円
カッター 持っていた物
ニッパー 持っていた物

f:id:yasuikj:20200330002849j:plain
ぐっとすプラグ

LANケーブル切断前の準備

カッターでLANケーブルの被覆を剥がし中のより線をひっぱりだします。
f:id:yasuikj:20200401201629j:plain

LANケーブルの被覆をはがした状態

より線をほぐしてぐっとすプラグを設置します。

f:id:yasuikj:20200401151919p:plain
LANケーブルはキレてないです!

f:id:yasuikj:20200401201830j:plain
ぐっとすプラグ装着後

この状態では、LANケーブルはキレてないです!このため、ノートPCとスイッチングハブの間のPing通信は途切れず通信している状態です。

リピータハブの設置

2つのぐっとすプラグをリピータハブに接続する準備をし、LANケーブルをニッパーで切断する直前の図です。

f:id:yasuikj:20200401202454j:plain
ぐっとすプラグの間のLANケーブルを切断する直前

まだ、ぐっとすプラグはリピータハブのコネクタにしっかり刺さっていません。さあ、ここからLANケーブルをニッパーで切断し、直後に2つのぐっとすプラグをリピータハブのコネクタに差し込みます。 3・2・1・・・・ バチンッ グッ

ぐっとすプラグの間のLANケーブルをニッパーで切断した後、2つのLANコネクタをリピータハブに接続した図です。

f:id:yasuikj:20200401202606j:plain
ぐっとすプラグの間のLANケーブルを切断しコネクタ接続した直後

ニッパーで切断してから、ぐっとすプラグをリピータハブのコネクタに接続するまで1〜2秒かかりました。

侵入時間の測定

切断してから、盗聴用PCで、ノートPCとスイッチングハブの間の通信のパケットをキャプチャできるまで、5秒でした
参考として、実験時に採取していた盗聴された側のノートPCのログと、盗聴用PCのWiresharkの画面を示します。


f:id:yasuikj:20200402155643p:plain
ケーブル切断から盗聴までの時間
盗聴された側のノートPCでは、16:36:58までPingが成功しており、16:36:59にケーブル切断によるリンク断を検出し、16:37:04からpingが成功しています。盗聴用PCのWiresharkの画面でも16:37:04からPing(ICMP echo)のパケットがキャプチャできています。

通信断時間が5秒しかないため、例えば1分周期に死活監視をおこなっていたとすると、通信断の検出は難しいかもしれません。

物理対策と監視運用

本稿で記載したような手口もあることを知っておくと、万一セキュリティインシデントが発生した際の侵入口の特定の役に立つかもしれません。いろいろな可能性があることを体験として知っておくと対策を検討する上でも地に足がついた検討が可能になります。

今回、Ping通信の盗聴を行いましたが、もし重要なデータを平文で通信していた場合、今回の手口で盗聴されてしまう可能性があります。
本手口に対してはどのような対策が効果があるでしょうか?推奨対策としては暗号化があります。とはいえ予算的に暗号化できない場合もあるでしょう。LANケーブルを敷設した部屋に鍵をかけLANケーブルに近づけないようにすることも効果があるでしょうが、部屋間を床下や天井をLANケーブルでつないでおり全てのLANケーブルを鍵付きの部屋に設置することは難しいかもしれません。もちろん怪しい人を建物に入れないようにすることも効果があるでしょう。LANに何かおかしなものが物理的に接続されていないか定期点検するというのも1つの案かもしれません。リンク断を検出するというアプローチもあります。現状を許容するがインシデント発生時の調査対象にLANケーブルの点検を含めるという考えもあります。答えは1つではないので何が現実的かを考えてみてください。

なお、本稿ではLANケーブルを切断しましたが、異なる方法で切断せずに盗聴することも可能です。また、冒頭で触れた光ケーブルについては、このLANケーブルの実験のように侵入は簡単ではなく時間や技術が必要ですが、仕組み的には切断して侵入することは可能であることを補則しておきます。

まとめ

LANケーブルをニッパーで切断し5秒でネットワークへ侵入・盗聴できることを実験で確認しました。
本記事は、ケーブルを切断してネットワークへ侵入・盗聴されるリスクがある事を知っていただく事を目的としています。ケーブルから侵入・盗聴される脅威を正しく把握してセキュリティ対策に役立ててください。
本稿が、セキュリティ対策検討の一助になれば幸いです。

追記:切断時ニッパーを介したPoE電流による装置破壊の懸念のコメントをいただきました。実験する際は気をつけてください。

(続)WiresharkのDissectorを使った独自プロトコル解析をやさしく解説してみました

本稿では、初めて実際に独自プロトコルのDissectorを作る人が最初にぶつかるであろう壁を乗り越える方法を紹介します。

Dissectorって何?という人は、先にWiresharkのDissectorを使った独自プロトコル解析をやさしく解説してみましたを読んでください。

前回は、符号なし整数、文字列を題材にDissectorを説明しました。しかし、実際にDissectorを作ろうとすると、Bit列の解析や時刻の解析など様々なデータ型への対応や、エンディアン(Endian)の解析の壁にぶつかったりします。
今回紹介する内容を理解いただくと、これらの壁を乗り超えてDissectorを作る力がつきます。
サンプルのキャプチャデータも添付するのでWiresharkを使って試してみてください。

サンプルを探す前に、あと一歩力をつけて

技術部の安井です。長年、制御システムを開発した経験から、現在は制御システムセキュリティを見ています。

前回、予想以上に多くの人に読んでいただきありがとうございました。Dissector を作ろうとされている方が大勢いることが分かり嬉しい限りです。

多くの方は、初めて独自プロトコルのDissectorを作ろうとしたときに、サンプルコードを検索して読解できず混乱する人が多いのではないでしょうか?私がそうでした。そうならないために、あと一歩、今回記載する3つのポイントを理解して力をつけてから、検索に出発することをお勧めします。

  • 1. 記法の違いによる混乱に陥らない方法
  • 2. 様々なデータ型の記法の見つけ方
  • 3. エンディアンの理解と対応方法

これを理解すると、サンプルコードを探し回らなくてもDissectorが作成できますし、サンプルコードを見たときに、だいぶストレスなく読めるようになると思います。

できるようになること

様々なデータ型と、エンディアンの違いを含む独自プロトコルを解析するためのDissector作成

記載していないこと

フォーマットが条件により変化するプロトコルのDissectorの作り方。
・例1:パケット内部の値の条件によりフォーマットが変わる内容を表示する。
・例2:データサイズが大きく可変であり、パケット内部のデータサイズ値の条件により複数パケットに分割(フラグメント)された内容をもとに戻して(リアセンブル)表示する。

Dissectorを作る際の3つのポイント

この章は、本ページ内に記載したサンプルコードと見比べて読んでください。

1. 記法の違いによる混乱に陥らない方法

参考サイト毎の異なるコーディングについて

初めて実際にDissectorを作成しようとする人がネット上でサンプルコードを探すと、同じ事をしているようなのに記法が異なるコードが見つかり、どれを参考としてよいか混乱することがあります。

代表的なものが、「ProtoField」と「treeitem:add」の書き方です。「ProtoField」に関しては、例えばProtoField.new("サイズ","origin.uint",ftypes.UINT32)と書いている場合と、ProtoField.uint32("origin.uint","サイズ") と書いている場合があります。両者は同じことを意味しており、前者がデータ型を引数(ftypes.UINT32)で指定しているのに対し、後者はメソッド(uint32)で指定しています。

結局わかりやすいのは公式サイトのマニュアル

コードの記法の違いで混乱しないためには、コードの仕様を確認する方法を知っておく必要があります。コードの仕様を確認するにはWiresharkの公式サイトの「Wireshark Developer’s Guide」が便利です。「ProtoField」の章を見ると、引数で指定することもメソッドで指定することもできることが分かります。筆者はデータ型を引数渡しする方が好みですのでProtoField.newの記法を使っています。treeItem:addについても公式サイトに記載されています。

2. 様々なデータ型の記法の見つけ方

実際にDissectorを書こうと思うと、bit列や、時刻など、様々なデータ型に対応したい場面が出てきます。例えば、ネットワーク上で時刻の情報がUNIX型のバイト列「5e 71 7b 3f」で流れているものを人間が分かる形式の「Mar 18, 2020 10:37:03」と表示したい場合などです。 ネット上でコードを探すのもよいですが、ここでもWireshark の公式サイトの「Wireshark Developer’s Guide」を読むと記法を見つけられるでしょう。「ProtoField」の章を見ると、以下のとおり利用できるデータ型が記載されています。上記の例の時刻はftypes.ABSOLUTE_TIMEが使えます。

Field Type: one of: ftypes.BOOLEAN, ftypes.CHAR, ftypes.UINT8, ftypes.UINT16, ftypes.UINT24, ftypes.UINT32, ftypes.UINT64, ftypes.INT8, ftypes.INT16, ftypes.INT24, ftypes.INT32, ftypes.INT64, ftypes.FLOAT, ftypes.DOUBLE , ftypes.ABSOLUTE_TIME, ftypes.RELATIVE_TIME, ftypes.STRING, ftypes.STRINGZ, ftypes.UINT_STRING, ftypes.ETHER, ftypes.BYTES, ftypes.UINT_BYTES, ftypes.IPv4, ftypes.IPv6, ftypes.IPXNET, ftypes.FRAMENUM, ftypes.PCRE, ftypes.GUID, ftypes.OID, ftypes.PROTOCOL, ftypes.REL_OID, ftypes.SYSTEM_ID, ftypes.EUI64 or ftypes.NONE.

出展:Wireshark Developer's Guide Version 3.3.0

3. エンディアンの理解と対応方法

独自プロトコルの通信データを解析する際は、エンディアンについて理解する必要があります。

エンディアンについて

エンディアンとは、複数のバイトを並べる順序のことで、ビッグエンディアンとリトルエンディアンがあります。プロセッサの違いにより、ビッグエンディアンのプロセッサとリトルエンディアンのプロセッサが存在し、Intelはリトルエンディアンで、SPARCはビッグエンディアンです。
例えば、10進数で51814は16進数で表すと「ca 66」となりますが、この値を4バイトのメモリに格納すると、ビッグエンディアンのプロセッサでは「00 00 ca 66」と格納されるのに対し、リトルエンディアンのプロセッサでは、「66 ca 00 00」と格納され、16進数表記と反対の並びになります。
ネットワーク上にデータを送る場合に、どちらで送るかを意識して通信する必要があります。
なお、ネットワーク通信において、TCP/IPでは、パケットのヘッダ部はビッグエンディアンが用いられていますが、ヘッダ以降の部分(ペイロード)は、通信を行うアプリケーションの仕様で決められます。

Wiresharkでリトルエンディアンのデータを表示する場合は、リトルエンディアン専用のメソッドを利用して変換する必要があります。ビッグエンディアンとリトルエンディアンの表示イメージを図1に示します。

f:id:yasuikj:20200318003938p:plain
図1:ビッグエンディアンとリトルエンディアンの表示

現実世界のエンディアンの実情の例

現在、Intel系のプロセッサが多いためか、ネットワーク上に流れるパケットのペイロード部分が、リトルエンディアンとなっている事も多いです。通信相手がお互いリトルエンディアンであれば、誰もエンディアンを意識しなくてよいので都合が良いというのが理由の1つでしょう。

では、ペイロードをビッグエンディアンとするのはどのような場合があるかというと、Javaアプリケーションはプロセッサによらずビッグエンディアンです。また、制御システムでは現在でもビッグエンディアンを用いている場合が比較的多いようです。

昔は、サーバといえばビッグエンディアンであるSun MicrosystemsのSPARCのサーバが多く、TCP/IPがパケットのヘッダ部がビッグエンディアンであることもあり、ネットワーク上のパケットは全てビッグエンディアンという仕様が多くなったという事もあるのではないかと思います。パケットのバイト列を見て脳内解析する際に、ビッグエンディアンだと16進数と同じ並びなので人間が理解しやすいというのも理由だったのかもしれません。制御システムはライフサイクルが長いですし多くの装置間で通信しているので、一度通信仕様がビッグエンディアンと決まると、装置の一つがリトルエンディアンの新しい装置に変わったとしても、相手がビッグエンディアンのままであれば、通信仕様をリトルエンディアンに変えるわけにはいきません。このような経緯も制御システムでビッグエンディアンを用いているものが比較的多い理由の1つでしょう。

昔よくあったエンディアン問題

SPARC勢が減りだしIntel勢が増えてきたころ、Intlの計算機どおしでエンディアンなど何も考えずにリトルエンディアンで通信しているシステムにて、あるとき、熟練さんがSPARCの装置をネットワークに繋げて通信すると繋がらず、「なんでリトルで流すんだ!昔からネットワークはビッグが普通だろ!」と怒り出すというような場面が見られていたものです。最初に作った方は運用してしまっているので変更できず、後から作った方は、泣く泣く、リトルエンディアンにあわすとかあったものです。
こんなことにならないよう、エンディアンの仕様は明確にしましょう。

リトルエンディアンのデータの表示について

パケット上の各データのエンディアンは、通信仕様によるので、リトルエンディアンのデータは、Dissectorでリトルエンディアンであることを意識して表示する必要があります。ここでは2種類の記法を説明します。
1つ目は、ツリーにaddする際に、 addメソッドの代わりにadd_leメソッドを使う方法です。
2つ目は、ツリーにaddする際に、バッファの値をbuffer(0,4):le_uintなどのメソッドで変換したものを、addメソッドの引数として渡す方法です。具体的なコードは、以下に示すサンプルコードを参照してください。

様々なデータ型(Uint32型、Bit型、bool型、時刻型、byte型)および リトルエンディアンのサンプル

参考として、ここまでの説明した内容のうち、各データ型の使い方、および、add_leメソッドを使ったエンディアン変換のサンプルコードを記します。

-----------------------------------------------------------
-- プロトコルの定義
proto = Proto("originalCS","独自制御プロトコル")

-- プロトコルフィールド定義
-- ProtoField.new(name,abbr,type,[valuestring],[base],[mask],[desc])
-- 引数:
--      name: プロトコルフィールド名、treeに表示される
--      abbr: プロトコルフィールドのフィルタ名
--      type: データ型
--      valuestring(省略可): (本サンプルでは使わないので説明省略) (本サンプルではbit表示時のみnilとして使用)
--      base(省略可): 表示オプション  例: base.DEC 10進表示, base.HEX 16進表示 (本サンプルではbit表示時のみ使用)
--      mask(省略可): データ型のうち、表示対象とするbit位置を指定する (本サンプルではbit表示時のみ使用)
--      desc(省略可): プロトコルフィールドの説明 (本サンプルでは未使用)
--
uint_F  = ProtoField.new("uint符号なし4byte整数","originalCS.uint",ftypes.UINT32)
-- uint_F  = ProtoField.uint32("originalCS.uint","uint符号なし4byte整数") --参考:メソッド記法 上の行と同じ意味
flag1_F  = ProtoField.new("bitフラグ1","originalCS.flag1",ftypes.UINT8,nil,base.DEC,0x80)
-- flag1_F  = ProtoField.uint8("originalCS.flag1","bitフラグ1",base.DEC,nil,0x80) --参考:メソッド記法 上の行と同じ意味
flag2_F  = ProtoField.new("bitフラグ2","originalCS.flag2",ftypes.UINT8,nil,base.DEC,0x40)
-- flag2_F  = ProtoField.uint8("originalCS.flag2","bitフラグ2",base.DEC,nil,0x40) --参考:メソッド記法 上の行と同じ意味
bool_F  = ProtoField.new("bool真/偽","originalCS.bool",ftypes.BOOLEAN)
-- bool_F  = ProtoField.bool("originalCS.bool","bool真/偽") --参考:メソッド記法 上の行と同じ意味
time_F  = ProtoField.new("日時分秒","originalCS.time",ftypes.ABSOLUTE_TIME,nil,base.LOCAL)
-- time_F  = ProtoField.absolute_time("originalCS.time","日時分秒",base.LOCAL) --参考:メソッド記法 上の行と同じ意味
value_F = ProtoField.new("BCD制御値","originalCS.value",ftypes.BYTES)
-- value_F = ProtoField.bytes("originalCS.value","BCD制御値") --参考:メソッド記法 上の行と同じ意味

uint_LittleEndian_F  = ProtoField.new("uint符号なし4byte整数(LittleEndian)","originalCS.uint_LittleEndian",ftypes.UINT32)

-- プロトコルフィールド定義をプロトコルフィールド配列へ登録
proto.fields = {uint_F, flag1_F, flag2_F, bool_F, time_F, value_F, uint_LittleEndian_F}

-- Dissector
function proto.dissector(buffer, pinfo, tree)

    -- パケットインフォメーション情報のprotocolヘッダに表示する名称を設定
    pinfo.cols.protocol = "ORIGINAL_CS"

    -- パケット詳細部のツリーの登録
    local subtree = tree:add(proto,buffer())
    subtree:add(uint_F, buffer(0,4))
    subtree:add(flag1_F, buffer(4,1))
    subtree:add(flag2_F, buffer(4,1))
    subtree:add(bool_F, buffer(5,1))
    subtree:add(time_F, buffer(8,4))
    subtree:add(value_F, buffer(12,2))

    subtree:add("------エンディアン変換の例-----")
    subtree:add_le(uint_F,buffer(14,4))
    --参考:表示内容を、第3引数に直接渡す記法。 表示内容は 14から4byte分をエンディアン変換したもの 上の行と同じ表示となる
    -- subtree:add(uint_F,buffer(14,4),buffer(14,4):le_uint())
end

--  定義したプロトコル(Proto:original)をTCPポート番号を指定して既存のTCPのDissectorに紐づける
tcp_table = DissectorTable.get("tcp.port")
tcp_table:add(3334, proto)

このDissectorを使って「00 00 00 12 80 01 00 00 60 00 40 47 07 53 12 00 00 00」というデータを表示したものを図2に示します。

f:id:yasuikj:20200318164557p:plain
図2:独自プロトコルツリー
図の中のbyte型で表示しているBCDとは、二進化十進数です。
リトルエンディアンの表示は、add_le メソッドで表示しています。

上記の説明で用いた[ORIGINAL_CS]行のキャプチャデータをテキスト形式で以下に記載します。
記載内容をsample.txtというファイル名で空のテキストファイルに転記し保存した上、Wiresharkのメニューの[ファイル]-[開く]からsample.txtを選択してください。
上記の内容と同じ内容がWireshark上で表示されます。テキストファイルの内容を変更してどのような表示になるか確認してみてください。

+---------+---------------+----------+
10:38:17,642,540   ETHER
|0   |08|00|27|c8|48|c2|0a|00|27|00|00|00|08|00|45|02|00|46|00|00|40|00|40|06|49|52|c0|a8|38|01|c0|a8|38|0a|c8|82|0d|06|f0|28|34|e9|84|a0|e3|b5|80|18|08|0a|9c|59|00|00|01|01|08|0a|10|13|88|fe|00|f1|5b|3c|00|00|00|12|80|01|00|00|60|00|40|47|07|53|12|00|00|00|

+---------+---------------+----------+

まとめ

本稿では、実際の独自プロトコルのDissectorを作る際に必要となる3つのポイントを説明しました。

  • 1. 記法の違いによる混乱に陥らない方法
  • 2. 様々なデータ型の記法の見つけ方
  • 3. エンディアンの理解と対応方法

本稿を理解することで、実際に独自プロトコルのDissectorを作れるようになっていただけたら幸いです。

 制御システムセキュリティに関わる者として、このようなノウハウを使って独自プロトコルの平文通信が解析されうることを手を動かして実体験として体感した上で、暗号化の要否などセキュリティ対策を検討いただくことを願います。わからない敵は怖いですが、一つ一つ仕組みを理解し基礎体力をつけていくことで、不安を一つ一つ減らしていくことができるのではないでしょうか?

今回記載しなかったことは、ほぼプログラミングの内容ですので、いつか機会があれば紹介したいと思います。

株式会社サイバーディフェンス研究所 / Cyber Defense Institute Inc.