Help us understand the problem. What is going on with this article?

Amazon Dash ButtonでRouter Crash Buttonを作ってみた with Ruby

More than 3 years have passed since last update.

この記事は、NetOpsCoding Advent Calendar 2016の2016年12月17日の記事です。

Amazon Dash Button」を入手したので、ルータ系で何かできないかと考えた結果、ルータをクラッシュさせる「Router Crash Button」を作ってみました。ボタンを押すと、ルータがクラッシュします。

仕組み

ボタンを押してからルータがクラッシュまで、下記の通りで実行します。

  1. 人がボタンを押す
  2. スクリプトがボタンが押されたことを検知(ARPパケット検知)
  3. スクリプトがルータにTelnetする
  4. スクリプトがルータの隠しコマンド(test crash )を発行し、Bus Errorを強制的に発生
  5. ルータがクラッシュする
  6. ルータが再起動する
  7. また、人がボタンを押す。。。。
  8. 2に戻る

Amazon Dash Button

Amazon Dash Buttonは、インターネットと通信するために、IPアドレスがDHCPから割り当てられます。IPアドレスが割り当てられる機器のため、ボタンごとに、MACアドレスが異なります。このMACアドレスを利用して、ボタンを識別します。今回は「消臭力」のAmazon Dash Buttonを入手しました。設定方法は参考文献を参照してください。
Amazon Dash Button.png

ボタンが押されたことの検知(ARPパケット検知)

Amazon Dash Buttonが押された場合、ARPパケットがブロードキャストされます。このARPパケットをパケットキャプチャすることで、ボタンが押されたことを検知します。
Amazon Dash Buttonは、電源OFFの状態で、ボタンが押されると電源ONし、DHCPでIPアドレスの割当が行われた後、IPアドレスの重複確認のために、ARP Requestパケットが送信されます。このARP Requestパケットの送信元MACアドレスを確認し、Amazon Dash ButtonのMACアドレスと比較して、合致していればボタンが押されたと判断します。

環境

無線LAN環境で事前にAmazon Dash Buttonの設定をします。設定方法は参考文献を参照してください。

  • Ubuntu Linux 16.04
  • libpcap0.8 - パケットキャプチャ用
  • Ruby 2.3.0
    • expect4r 0.0.11 - Telnetライブラリ
    • pcaprub 0.12.4 - パケットキャプチャライブラリ
    • pio 0.30.0 - パケットパースライブラリ

コード

コードはgithubにありますので参照してください。
https://github.com/kooshin/router-crash-button

スクリプトの配置は下記のとおりです。

.
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── README.md
├── crash_button.rb     - Ruby実行ファイル(root権限で)
└── lib
    └── dash_button.rb  - Amazon Dash Button用のライブラリ

crash_button.rb

Amazon Dash Buttonが押されたらルータをクラッシュさせるスクリプトです。

下記の通り、パラメータを設定します。デモコードのため、すべてスクリプトに埋め込んであります。

DASH_BUTTON_MAC_ADDR  = '88:71:e5:63:aa:d5' # ボタンのMACアドレス
NETDEV                = 'enp0s25'           # パケットキャプチャするインタフェース

ROUTER_HOST           = '192.168.88.5'      # クラッシュさせるルータのIPアドレス
ROUTER_USER           = 'cisco'             # Telnet時のユーザ名
ROUTER_PASSWORD       = 'cisco'             # Telnet時のパスワード
ROUTER_ENABLE         = 'cisco'             # enableパスワード

自作DashButtonクラス(lib/dash_button.rb)のインスタンスを作成します。引数はパケットキャプチャするインタフェースと、検知するAmazon Dash ButtonのMACアドレスです。MACアドレスは、Wireshark等でパケットキャプチャすることでわかります。

dash_button = DashButton.new(NETDEV, DASH_BUTTON_MAC_ADDR)

DashButtonクラスのmonitorメソッドにブロックを渡します。Amazon Dash Buttonを検知するとブロックが実行されます。
今回は、ルータをクラッシュさせるコードを実行します。expect4rライブラリを使ってTelnetしています。詳細はRubyのexpect4rでCiscoルータにTelnet/SSHしてコマンド実行するを参照してください。
ルータにログイン後、ios.exp_printでtest crashコマンドを送信します。「Enable crash router selection marked with (crash router)」するために「C」を送信し、「(crash router) Bus Error, due to invalid address access」を引き起こすために、1を送信します。

dash_button.monitor do
  begin
    puts "#{DateTime.now} Crash Button pressed!! Force crash the Router"
    ios = Expect4r::Ios.new_telnet(
      host: ROUTER_HOST,
      user: ROUTER_USER,
      pwd:  ROUTER_PASSWORD,
      enable_password: ROUTER_ENABLE
    )
    ios.login
    ios.exp_print("test crash\rC\r1\r")
  rescue Expect4r::ExpTimeoutError, Errno::EIO => e
    puts "#{DateTime.now} Failed: #{e}"
  end
end

lib/dash_button.rb

pcaprubライブラリとpioライブラリを使って、Amazon Dash ButtonのARP Requestパケットを検出します。

PCAPRUBY::Pcap.open_liveでパケットキャプチャを開始します。この際、インタフェースの指定と、BPFフィルタでARPパケットのみパケットキャプチャするよう指定します。

  def pcap
    return @pcap if @pcap

    @pcap = PCAPRUB::Pcap.open_live(
      netdev,
      SNAPLENGTH,
      PROMISCOUS_MODE,
      TIMEOUT
    )
    @pcap.setfilter(BPF_FILTER)
  end

monitorメソッド内のpcap.each_packetでパケットを受信するたびに、ボタンが押されたかを判断します。パケットをPio::Parser.readでパースします。パースしたパケットを、dash_button_pressed?メソッドで、ボタンが押されたかどうかを判断します。ボタンが押されたと判断した場合、monitorメソッドの呼び出し元のブロックが実行されます。

  def monitor
    pcap.each_packet do |raw_packet|
      begin
        packet = Pio::Parser.read(raw_packet.data)
        yield if dash_button_pressed?(packet)
      rescue Pio::ParseError
        next
      end
    end
  end

dash_button_pressed?メソッド内では、ARP Requestパケットであるか、指定のMACアドレスであるかを判断します。

  def dash_button_pressed?(packet)
    arp_packet?(packet) && dash_button_mac_addr?(packet)
  end

  def arp_packet?(packet)
    Pio::Arp::Request == packet.class
  end

  def dash_button_mac_addr?(packet)
    dash_button_mac_addr == packet.source_mac
  end

実行

パケットキャプチャのために、libpcap-devを予めインストールし、コードをcloneして、bundleコマンドでRubyのライブラリをインストールします。

$ sudo apt install libpcap-dev
$ git clone https://github.com/kooshin/router-crash-button
$ bundle

パケットキャプチャするため、スクリプトの実行にはroot権限が必要です。sudoで実行すると良いでしょう。説明は省略していますが、Rubyはrbenvを用いてインストールしています。rbenvの場合、別途rbenv-sudoプラグインが必要です。

$ sudo ruby crash_button.rb
or
$ rbenv sudo ruby crash_button.rb
Waiting for Crash Button press...

実行結果

上記のTwitterの動画を参照してください。

参考文献

kooshin
ネットワークのエンジニアです。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away