読者です 読者をやめる 読者になる 読者になる

DARK MATTER

CDI Engineer's Technical Blog

開発への取り組み

Development

こんにちは。技術部エンジニアの松永です。

セキュリティ診断作業の傍らで、攻撃・解析ツールや診断システムなどの開発を行っています。
セキュリティは総合格闘技と評される通り、これらのツール開発の分野もWebやNetworkに始まり、PC・スマホアプリといったバイナリ分野、組込・制御機器のハードウェア分野まで多岐に渡ります。

開発したコードは、昨年度から運用を開始したGitLabで管理しています。

f:id:yuji-matsunaga:20160512201024p:plain

サイバーディフェンス研究所では、既存のツールでは対応できないアプリケーションや機器の診断、攻撃者視点での診断ツールの高品質化や効率化に、積極的に取り組んでいます。

今後は公開可能なツールを弊社のGithubリポジトリで順次公開してまいります。
公開したツールの使い方などブログでご紹介することで、新たな貢献ができればと思います。

Burp Extension開発 - MessagePack

Development Burp Suite

こんにちは。技術部エンジニアの松永です。


PC・スマホアプリケーションや組込機器の脆弱性診断の現場では、クライアント(アプリや機器)とサーバー間の通信に Web API が使用されるケースによく遭遇します。
Web APIの診断には、弊社では主に Burp Suite Professional を使用しています。


通常のWebアプリケーションとは異なり、Web APIではバイナリフォーマットのメッセージが使用されたり、リクエストの暗号化や改ざん検知が施されていたりします。

今回はバイナリフォーマットのメッセージの例として、ボディ部が MessagePack でフォーマットされたHTTPリクエスト/レスポンスを、Burpを使って試験する方法を紹介します。
バイナリデータの可視化や編集を効率的に行うには、Burpを拡張するツール(Burp Extension)の開発が必要になります。


開発したExtensionをGitHubで公開しましたので、この記事ではExtensionの導入方法やテストアプリの使い方を紹介したいと思います。


MessagePackとは

一言で言うと、バイナリフォーマット版のJSONです。
軽量かつ高速に動作し、多数のプログラミング言語のライブラリが存在します。
詳細は公式ドキュメントをご覧ください。

Burp Extension

以下のGithubリポジトリで公開しています。
https://github.com/CyberDefenseInstitute/burp-msgpack


MessagePackはJSONとの親和性が高いことから、HTTPメッセージの送受信時はMessagePack、閲覧・編集時はJSONとすることで作業が容易になります。
burp-msgpackはこの変換を自動的に行うBurp Extensionです。


今回はPythonで実装しています。ちょっとした拡張の場合、Pythonが一番楽ではないかと思います。
Pythonの場合、 Burp Extender API はburpモジュールから使用できます。
また、BurpはPythonで書かれたExtensionの実行にJythonを使用するため、swing等javaのGUIを使用可能ですが、今回は特に触れません。
burp-msgpackの古い実装ではswingを使用していますので、興味のある方はご覧ください。


burp-msgpackを簡単に説明します。

  • Content-Typeヘッダで示されるMIMEが "application/*msgpack" (*はワイルドカード)であるリクエスト/レスポンスに対し、以下の処理を行う
  • MessageEditor(ProxyやRepeaterに表示されるRequest/Response)に "mpack" タブを追加する
    • mpackタブにはMessagePackからJSONに変換したRequest/Responseが表示される
    • mpackタブのリクエストはJSONの状態で編集が可能で、自動的に実際のリクエストに反映される
  • 以下のBurpツールから送信されるリクエストがBurpのターゲット・スコープに含まれる場合、JSONからMessagePackへの変換を行う
    • Scanner
    • Intruder
    • Extender


ここからはコードの解説をします。

Extensionのロード

JavaでBurp Extensionを実装する場合、IBurpExtenderという抽象クラス(interface class)を実装(implement)した具象クラス(concrete class)を作成します。
registerExtenderCallbacksというインターフェースがBurpから呼び出されるので、registerExtenderCallbacksをインプリメントして必要な初期化処理やExtensionの登録処理を実装することになります。
Pythonの場合も同様で、IBurpExtenderを継承したクラスがBurpから呼び出されます。


burp-msgpackでは、IBurpExtenderCallbacks#registerMessageEditorTabFactoryでメッセージエディタタブのファクトリークラスの登録を行い、IBurpExtenderCallbacks#registerHttpListenerでHTTPメッセージのリスナーをBurpに登録しています。
メッセージエディタタブのファクトリークラスを登録した場合、BurpからIMessageEditorTabFactory#createNewInstanceが呼び出されるので、メッセージエディタタブクラスを生成します。

class BurpExtender( IBurpExtender, IMessageEditorTabFactory ):
	
    def registerExtenderCallbacks( self, callbacks ):
        self._callbacks = callbacks
	callbacks.setExtensionName( "Burp MessagePack" )
        callbacks.registerMessageEditorTabFactory( self )
        callbacks.registerHttpListener( HttpListener( callbacks ) )

    def createNewInstance( self, controller, editable ):
        return MessageEditorTab( controller, editable, self._callbacks )

mpackタブ(エディタ)の処理

次はメッセージエディタタブクラス(MessageEditorTab)を見ていきます。

ここにはProxyやRepeaterなどに表示されるメッセージエディタへ追加する "mpack" タブの処理を記述します。
このタブにはMessagePackをJSONに変換したHTTPメッセージを表示し、内容が編集された場合はMessagePackへ再変換する役割があります。
テキストエディタのUIにはBurpが提供する IBurpExtenderCallbacks#createTextEditor を使用しています。

class MessageEditorTab( IMessageEditorTab, MpackJsonHelper ):
    def __init__( self, controller, editable, callbacks ):
        MpackJsonHelper.__init__( self, callbacks )
        self._controller = controller
        self._editable = editable
        self._editor = self._callbacks.createTextEditor()
        self._editor.setEditable( editable )

    def getTabCaption( self ):
        return "mpack"

    def getUiComponent( self ):
        return self._editor.getComponent()


IMessageEditorTab#isEnabled は、任意のHTTPメッセージが選択されたタイミングで呼び出されます。
ここで真(True)を返すとBurpがタブ(mpackタブ)を追加してくれます。
burp-msgpackは、Content-Type が MessagePack のものである場合、Trueを返します。
タブが表示される場合、後続の IMessageEditorTab#setMessage に続きます。

    def isEnabled( self, content, isRequest ):
        if content is None:
            return False

        info = self.analyzeMessage( content, isRequest )
        isMessagePack = self.isMessagePack( info.getHeaders() )
        return isMessagePack


IMessageEditorTab#setMessage ではHTTPメッセージをMessagePackからJSONに変換し、表示します。

    def setMessage( self, content, isRequest ):
        info = self.analyzeMessage( content, isRequest )
        newRaw = self.toJson( content, info )
        self._editor.setText( newRaw )
        self._content = content
        self._isRequest = isRequest


IMessageEditorTab#getMessage / IMessageEditorTab#isModified は、他のタブへの表示切り替えが発生したタイミングや、リクエストの送信が発生したタイミングでBurpから呼び出されます。
具体的にはメッセージエディタ内の mpackタブ から Rawタブ へ表示を切り替えた時や、RepeaterでGoボタンをクリックした時などです。
IMessageEditorTab#getMessage で返した内容は実際に送信するリクエストに反映されるので、burp-msgpackではJSONからMessagePackへ再変換したメッセージを返します。


MessagePackはバイナリ文字列(バイト配列)を扱うことができますが、JSONはバイナリを扱えないため、編集内容によっては変換に失敗することがあります。
もちろん、JSONフォーマットを崩した場合も変換はできません。
変換に失敗した場合は未編集のメッセージをそのまま返し、さらに内部的にはBurpのAlertsタブにアラートを表示するようにしています。

    def getMessage( self ):
        content = self._editor.getText()
        info = self.analyzeMessage( content, self._isRequest )
        try:
            newContent = self.toMpack( content, info )
        except:
            return self._content
        return newContent

    def isModified( self ):
        return self._editor.isTextModified()

 

HTTPリスナー

最後に、HTTPリスナークラスを説明します。

IHttpListener#processHttpMessage では、Burpが送受信するHTTPメッセージを取得・変更することが可能です。
Scanner や Intruder では上記の仕組みが使えないため、リクエストが送信されるタイミングで MessagePack への再変換が行えるようにしました。


使い方は後ほど解説しますが、mpackタブから直接ScannerタブやIntruderタブへリクエストを送る "Send to Intruder" のような機能は実装していないため、JSON形式の対象リクエストを一旦Rawタブへコピーし、 "Send to Intruder/Scanner" する必要があります。


ここでポイントになる処理を解説します。

  • 引数の toolFlag で Scanner/Intruder/Extender のいずれかのツールから送信されたメッセージであることを確認しています。
  • IExtensionHelpers#analyzeRequest には複数のオーバーロードがありますが、 IExtensionHelpers#analyzeRequest(byte[] request) を使うとAPIのドキュメントに書かれている通り IRequestInfo#getUrl が使用できなくなるので注意が必要です。

リクエストの内容だけでは、オリジンを決定できないためだと思われます。

  • IHttpRequestResponse#setRequest で MessagePack へ変換したリクエストをセットします。
class HttpListener( IHttpListener, MpackJsonHelper ):
    def __init__( self, callbacks ):
        MpackJsonHelper.__init__( self, callbacks )
        self._toolMask = self._callbacks.TOOL_SCANNER | \
            self._callbacks.TOOL_INTRUDER | \
            self._callbacks.TOOL_EXTENDER

    def processHttpMessage( self, toolFlag, isRequest, httpReqRes ):
        if False == isRequest:
            return
        if 0 == ( self._toolMask & toolFlag ):
            return
        requestInfo = self._helpers.analyzeRequest( httpReqRes )
        if False == self._callbacks.isInScope( requestInfo.getUrl() ):
            return
        if False == self.isMessagePack( requestInfo.getHeaders() ):
            return

        rawRequest = httpReqRes.getRequest()
        try:
            newRequest = self.toMpack( rawRequest, requestInfo )
            if None == newRequest:
                return
        except:
            return
        httpReqRes.setRequest( newRequest )

 

インストール

Python Environmentの設定

今回はPythonでの実装のため、BurpのPython環境設定から説明してみたいと思います。
公式なドキュメントはBurpのSupport Centerにあります。
https://portswigger.net/burp/help/extender.html#options_pythonenv

この説明の対象OSはLinuxです。WindowsやOSXをお使いの方は適宜読み替えて下さい。virtualenv環境への読み替えも同様にお願いします。
Free版のBurpで解説します(業務ではPro版を使用しています、念の為)。
また、Pythonとpipはインストール済であるものとします。

  1. pipからmsgpack-pythonをインストールします。
    $ pip2 install msgpack-python
  2. jython.orgからJythonのjarファイルをダウンロードします。(本稿執筆時点の最新はjython-standalone-2.7.0.jarです)
  3. Burpの[Extender - Options - Python Environment]から、ダウンロードしたJarファイルとPythonモジュールのパスを指定します。モジュールのパスは以下のコマンドで表示されると思います。
    $ python2 -c 'import sys; print(sys.path)'
    f:id:yuji-matsunaga:20160418184858p:plain

Extensionのインストール

  1. git cloneなどの方法でExtensionをダウンロードします。
  2. [Extender - Extensions - Burp Extensions]を開き、ダウンロードしたExtensionをAddします。Extension TypeはPythonを指定します。f:id:yuji-matsunaga:20160418191520p:plain

エラーが発生しなければひとまずインストール成功です。


テストアプリ

MessagePackで通信を行うRuby on Railsのサンプルアプリを作成しました。

https://github.com/jx6f/blog_mpac


サンプルアプリ起動までのコマンドを以下に示します。
Railsの実行環境が構築済であることが前提となっています。

$ git clone https://github.com/jx6f/blog_mpac.git && cd blog_mpac
$ git checkout add_request_parser
$ bundle install
$ rake db:migrate
$ rails s -b localhost


Railsの環境が無い場合、Dockerをご使用ください。

$ git clone https://github.com/jx6f/blog_mpac.git && cd blog_mpac
$ git checkout dockerize
$ docker build -t blog_mpac:latest .
$ docker run -d --name blog_mpac -p 127.0.0.1:3000:80 -e SECRET_KEY_BASE=$(head -c 30 /dev/urandom | xxd -p) blog_mpac:latest


無事起動できたら、ブラウザで http://localhost:3000/ を表示します。

f:id:yuji-matsunaga:20160421180508p:plain


このアプリは、Postリソースの表示と更新のリクエストでmsgpackフォーマットを指定することで、MessagePack形式のレスポンスを返します。
Railsのルーティング上は以下の2つです。

GET    /posts/:id(.:format)      posts#show
PATCH  /posts/:id(.:format)      posts#update


詳細は後ほどBurpを使いながら説明します。


MessagePack API を Burp で試験する

ここまでの手順でBurpでMessagePackを扱う準備ができました。
ここからは実際にBurpでメッセージの内容をJSONフォーマットで表示したり、MessagePackリクエストの改変を行います。
Proxy、Repeater、Intruderを使って説明します。

引き続きFree版のBurpにて解説します。

Proxy

localhost:3000 で起動中のテストアプリをBurpのTarget Scopeに含めるようにします。
[Target - Scope - Target Scope - Add] です。

f:id:yuji-matsunaga:20160504115608p:plain


また、Burpのデフォルト設定ではProxyタブにOther binary(MessagePackはこれに分類される)が表示されないのでチェックを入れておきます。

f:id:yuji-matsunaga:20160504120349p:plain


ブラウザから http://localhost:3000/posts/new を開き、データを作成します。
ブラウザのプロキシの設定を行い、Burpを通すようにしておいて下さい。

f:id:yuji-matsunaga:20160504132009p:plain


http://localhost:3000/posts/1 へ遷移したと思います。
フォーマット(パスの拡張子部分)を msgpack としてページを開くと、MessagePack形式のレスポンスが返ります。
http://localhost:3000/posts/1.msgpack を開いてください。

f:id:yuji-matsunaga:20160504132342p:plain


ProxyタブのResponseを確認します。
Rawタブにはburp-msgpackが変換を行う前の、MessagePack形式のままのレスポンスが表示されます。
mpackタブにはburp-msgpackがJSONへ変換を行った後のレスポンスが表示されます。

f:id:yuji-matsunaga:20160506130500p:plain
f:id:yuji-matsunaga:20160506130507p:plain


レスポンスがヒューマンリーダブルになったところで、次はリクエストを見ていきます。
今回はpryを使って、MessagePack over HTTPリクエストを対話的に作成します。


pryはテストアプリのDockerコンテナにインストール済です。
こちらを使用する場合はDockerのネットワークインターフェースに割り当てられたIPを調べて、Burpでbindしておきます。
ホスト側で直接pryを使用できる場合、この設定は特に必要ありません。

$ ip a show docker0
4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:9e:16:b0:7c brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 scope global docker0
       valid_lft forever preferred_lft forever

f:id:yuji-matsunaga:20160421210326p:plain


以下のオプションでpryのREPLを立ち上げます。

# Dockerコンテナを使わずに直接立ち上げる場合
$ pry -r msgpack -r uri -r 'net/http' -r json

# Docker内のpryの場合
$ docker exec -it blog_mpac pry -r msgpack -r uri -r 'net/http' -r json


pryにrubyのコードを打ち込み、Burpプロキシを通しながらMessagePack over HTTPリクエストを送信します。
#から始まる行はコードの説明のためのコメントです。

# http bodyデータの生成(MessagePackフォーマット) 
[1] pry(main)> body = MessagePack.pack( {"post":{"title":"update test","body":"Hi, MessagePack"},"commit":"Update Post"} )
=> "\x82\xA4post\x82\xA5title\xABupdate test\xA4body\xAFHi, MessagePack\xA6commit\xABUpdate Post"

# Proxy経由でhttpサーバに接続するクラスを生成
# Dockerの場合、最初の'localhost'の部分をBurpでbindしたDockerのIPに変更
[2] pry(main)> proxy = Net::HTTP::Proxy('localhost',8080).new('localhost',3000)
=> #<#<Class:0x000000023f23f0> localhost:3000 open=false>

# httpメッセージ送受信
# /post/1 のリソースを、PATCHメソッドで更新するリクエスト
[3] pry(main)> resp = proxy.patch( "/posts/1.msgpack", body, {'Content-Type' =>'application/msgpack'} )
=> #<Net::HTTPOK 200 OK  readbody=true>

# レスポンスをアンパックして内容を表示
[4] pry(main)> MessagePack.unpack(resp.body)
=> {"id"=>1,
 "title"=>"update test",
 "body"=>"Hi, MessagePack",
 "created_at"=>"2016-05-06T04:02:12.408Z",
 "updated_at"=>"2016-05-06T04:08:49.973Z"}


BurpでInterceptしている場合、リクエストを送信した時点でInterceptタブにはJSONに変換されたリクエストが表示されます。
この状態でリクエストを編集することも可能です。
HTTP historyタブにも同様の内容が表示されます。

f:id:yuji-matsunaga:20160506130913p:plain

 

Repeater

次はRepeaterを使ってリクエストの編集を行います。
先ほどのリクエスト(PATCH /posts/1.msgpack)を Send to Repeater でRepeaterに送ります。

f:id:yuji-matsunaga:20160506133334p:plain


mpackタブでリクエストを編集し、rawタブを確認します。

f:id:yuji-matsunaga:20160506133909p:plain
f:id:yuji-matsunaga:20160506133914p:plain


rawタブのMessagePack形式のリクエストに編集内容が反映されたことが確認できます。
Goボタンをクリックしてリクエストを送信し、レスポンスを確認します。

f:id:yuji-matsunaga:20160506134055p:plain


編集した内容でデータが更新されたことが、レスポンス(の雰囲気)からわかります。

Intruder

IntruderではProxyやRepeaterのように個別にメッセージを編集することが出来ません。
JSON形式のベースリクエストとペイロードを使ってリクエストを作成し、リクエスト送信のタイミングで burp-msgpack がMessagePackに変換するようにしています。

RepeaterのmpackタブのリクエストをRawタブにコピーし、 Send to Intruder でIntuderにリクエストを送ります。

f:id:yuji-matsunaga:20160506142155p:plain


Intruderの設定をします。

f:id:yuji-matsunaga:20160506142651p:plain

f:id:yuji-matsunaga:20160506182956p:plain


ここでのポイントは、JSON形式のWeb APIを試験する時と同じく、JSONフォーマットのリクエストを作ることを意識することです。


ペイロードのURLエンコード機能は、今回は不要なのでチェックを外しておきます。
[Intruder - タブ番号 - Payloads - Payload Encoding]です。
デフォルトでこの設定にしておきたい場合は、[メニューバー - Intruder - New tab behavior]からデフォルト設定を使用しないようにしておくといいでしょう。


ペイロードもJSONフォーマットに従ったエスケープが必要です。
今回はIPA ウェブ健康診断仕様に記載されているペイロードをJSONエスケープしたものを使用します。
パラメータ値にAppendすべきものとReplaceすべきものが混ざっていますが、今回はそれっぽくIntruderを動かしたいだけなのであまり気にしないでください。
また、エスケープを手動で行わずに Extension で実装することも可能ですが、今回は実装していないため触れません。

'
'and'a'='a
 and 1=1
'>\"><hr>
'>\"><script>alert(document.cookie)<\/script>
<script>alert(document.cookie)<\/script>
javascript:alert(document.cookie);
..\/..\/..\/..\/..\/..\/..\/bin\/sleep 20|
;\/bin\/sleep 20


Start attackをクリックしてIntruderによる試験を開始します。
残念ながら実際に送信したMessagePack形式のリクエストは確認できませんが、レスポンスから意図したリクエストが送信できていることがお分かり頂けるかと思います。

f:id:yuji-matsunaga:20160507000747p:plain
f:id:yuji-matsunaga:20160507000827p:plain

テストアプリのログを確認すると、より確認しやすいかもしれません。

Started PATCH "/posts/1.msgpack" for 127.0.0.1 at 2016-05-07 00:02:20 +0900
Processing by PostsController#update as MSGPACK
  Parameters: {"post"=>{"title"=>"'>\"><script>alert(document.cookie)</script>", "body"=>"Hi, MessagePack"}, "commit"=>"Update Post", "addParam"=>12345, "id"=>"1"}
  Post Load (0.1ms)  SELECT  "posts".* FROM "posts"  WHERE "posts"."id" = ? LIMIT 1  [["id", 1]]
   (0.1ms)  begin transaction
  SQL (0.1ms)  UPDATE "posts" SET "title" = ?, "updated_at" = ? WHERE "posts"."id" = 1  [["title", "'>\"><script>alert(document.cookie)</script>"], ["updated_at", "2016-05-06 15:02:20.493800"]]
   (31.2ms)  commit transaction
  Rendered text template (0.0ms)
Completed 200 OK in 34ms (Views: 0.5ms | ActiveRecord: 31.5ms)


Intruderの設定を見た時点でお気づきの方もいたかもしれませんが、JSONフォーマットが崩れてしまうペイロード挿入箇所があります。
文字列が二重引用符で囲まれていないため、burp-msgpackがJSONのパースに失敗してしまいます。

f:id:yuji-matsunaga:20160507000953p:plain

パースに失敗したリクエストはAlertsタブに表示されます(burp-msgpackがアラートを上げます)。

f:id:yuji-matsunaga:20160507001016p:plain


この場合、出来損ないのJSON文字列がそのまま送信されます。
ちなみにテストアプリもパラメータのパースに失敗します。

Started PATCH "/posts/1.msgpack" for 127.0.0.1 at 2016-05-07 00:02:51 +0900
Error occurred while parsing request parameters.
Contents:

{"post": {"title": "Intruder test", "body": "Hi, MessagePack"}, "commit": "Update Post", "addParam":  and 1=1}

ActionDispatch::ParamsParser::ParseError (109 extra bytes after the deserialized object):
 (スタックトレース)


MessagePackやJSONの場合は文字列型のパラメータしかインジェクションできないのか?というとそうでもないのですが、また別の機会にご紹介できればと思います。


おわりに

サイバーディフェンス研究所では、既存のツールでは対応できないアプリケーションや機器の診断、攻撃者視点での診断ツールの高品質化や効率化に、積極的に取り組んでいます。

今回はバイナリフォーマットなHTTPメッセージをBurpで試験する方法として、 MessagePack を例に Extension の実装方法と使用方法、実際に試験する上でポイントとなりそうな箇所をご紹介しました。
皆様のお役に立つ情報があれば幸いです。

スマホへのBurpの証明書のインストール

iOS Android Burp Suite

弊社ではPC上のブラウザで表示するWebアプリケーションの診断だけでなく、iOSやAndroid端末上のネイティブアプリケーションが使用するWeb APIもよく診断しています。
診断に使用するProxyツールはPCと同様にBurp(Burp Suite)を使用しています。
対象のネイティブアプリケーションがHTTPSを使用している場合、端末にBurpの証明書をインストールする必要があります。
本稿ではインストールの仕方を簡単に紹介します。

iOSの場合

端末のProxy設定でBurpの待ち受けているIPとポートを指定した状態で、Safariから http://burp/cert にアクセスします。
上記URLを指定すると、Burpから証明書が返されます。
iOSの場合はこの段階で証明書のインストール画面に遷移するので、そのままインストールすることが可能です。

インストールされた証明書は
設定 > 一般 > プロファイル で確認や削除が実施できます。

http://burp/cert というURLに対してBurpが特殊な対応をすることは、Burpの
Proxy > Options > Import / export CA certificate
を選択して表示される画面の上部に記載されています。

Androidの場合

http://burp/cert でBurpから返される証明書のファイル名はcacert.derです。

証明書を管理する - Nexus ヘルプには以下のように記載されています。

Android では、DER エンコードの X.509 証明書のうち、拡張子 .crt または .cer の付いたファイルに保存されたものをサポートしています。証明書ファイルの拡張子が .der などの場合は、.crt または .cer に変更しないとインストールできません。

拡張子がderのままではインストールすることができず、拡張子を変更する必要があります。
そこで一旦PCに証明書をダウンロードして、adb経由でAndroidに送ってインストールするというのが方法の1つになります。

PCでcacert.derをダウンロードした後、

$ adb push cacert.der /sdcard/cacert.cer

としてadb経由でAndroidの/sdcard直下にcacert.cerという名前で証明書を配置し、Android側で

メニュー > 設定 > セキュリティ > ストレージからのインストール

と操作することでインストールすることが可能です。

組込機器の診断を紹介 1/2

Embedded

こんにちは。
株式会社サイバーディフェンス研究所 技術部 手島と申します。

日頃は、Webサイトや組込機器に対して
セキュリティ診断やペネトレーションテストを担当しています。

 

弊社の課題として
かねてから、社外のお客様や取引先より
組込機器のセキュリティ診断について以下のような疑問を頂戴していました。

 

  • そもそも組込機器のセキュリティ診断って何?
  • どんなことするの?
  • どんなことが分かるの?

 

そこで、今回は診断そのものをイメージしやすくすることを目的として
実際の診断に近い環境を用意し、用語やツールと共に紹介します。

 

この記事は主にエンジニアを対象にしているため、
用語やツールの解説はかなり省いています。

 

 

■そもそも組込機器の診断って何?


みなさんの身の回りには、様々な組込機器が存在します。
身近な例を挙げると・・・

 

  • 道路の信号
  • 鉄道の制御システム
  • エアコンの制御機
  • 無線ルータ
  • 警備システム
  • 家庭用蓄電池システム
  • 気象観測機器


・・・などです。
組込機器といっても、明確なジャンルがある訳ではありません。
(これ以外にも挙げきれないほどいっぱいありますよ!)

 

道路の信号を例に取りましょう。
悪意を持ったテロリストが、全ての信号を青にしてしまったらどうなりますか?
たちまち交通は大混乱!大事故に繋がりますね。


そんなことができるわけがない?


いいえ。
確かに日本で聞いたことはないですが
アメリカでは、ミシガン州立大学の研究者が
"恐ろしいほど簡単(terrifyingly easy)" と発表しています。


外部サイト: ars technica
http://arstechnica.com/security/2014/08/researchers-find-its-terrifyingly-easy-to-hack-traffic-lights/

 

信号ほどクリティカルではありませんが
悪用されると困る機械はたくさんあります。
弊社では、このような組込機器を対象に
脆弱性の有無を調査・診断するサービスを提供しています。

 

 

■今回診断するターゲット


私の自宅で使用していた機器を更新するため不要になり、
今回のターゲットとして選定しました。


今回のターゲットは全国の様々な小売店で購入できるものですが
攻撃方法を公開すること、悪用された場合の影響を鑑みて
機種名は非公開とさせてください。

 

■開封

 

まずはターゲットを開封しました。 

使われていたのは少し特殊なヘックスローブネジ(通称・トルクスネジ)ですが
ホームセンターで誰でも購入可能です。

 

f:id:cdi-teshima:20160428164506j:plain

 


※注意 ターゲット選定について
  公共のものを勝手に分解してはいけません。
  分解するのは自分で購入したものにしましょう。
  また、機器によっては内部に高電圧が印加されている場合もあります。
  本記事の内容を真似する場合は、必ず感電防止手袋などをし、
  自己責任で行ってください。
  弊社では一切の責任を負えません。


※電波について
  今回のターゲットは電波を発射する機能がありますので
  分解に際して 50Ωのダミーロードを装着しています。

 

 

 

 

今回のターゲットには存在しませんでしたが、機器の用途によっては、
耐タンパを目的として開封検知機能が付いていることがあります。


開封検知機能の例:
  機械的機構 その1
    警報が鳴り響く。
    銀行のATMとかこんな機能がついてますね。
  
  機械的機構 その2
    爪が外れると内蔵フラッシュメモリ全消去する。
    一部のクレジットカード読取機は
    分解されていると判断したら、自壊させるようになっているそうです。
    
  機械的機構 その3
    機械が分解されたよ、とイベントが管理サーバへ通知される。
    警備システム等、関係者以外に開封されると困る機器にはこんな機能があります。
    
  セキュリティシール
    開封されると跡が残る。
    一般家庭向け機器にも採用例があります。
    機器を分解すると、製品に付与されているメーカー保証が適用外となっていました。


引用:
  LB-SL1, セキュリティシール, サンワサプライ株式会社
  http://www.sanwa.co.jp/product/syohin.asp?code=LB-SL1

 

f:id:cdi-teshima:20160422103518j:plain

 

 

 

組込機器診断では、弊社のエンジニアは
様々な耐タンパ機構を迂回できないかずっと考えています。

 

■搭載されているチップを調べる


基板上を眺めて搭載されているチップを調べ、
インターネット上からデータシートの入手を試みます。


経験上、大抵はチップベンダからデータシートが開示されていますが
データシートを開示していない、もしくは開示してもらえない場合もあります。
チップを調べるついでに、明らかに怪しい端子もアタリをつけます。
この5ピンはデバッグ用でしょうか。怪しいので、後ほど調べましょう。

 

f:id:cdi-teshima:20160428164542j:plain

 

裏面

 

f:id:cdi-teshima:20160428164553j:plain

 

 


データシートを調べることで、以下のことが分かります。

 

  • アーキテクチャ
  • 各端子の用途
  • チップに備わっている機能
  • バスでの通信方式
  • コマンド


・・・etc。

 


診断に使える時間は限られていますので
スムーズに診断を行うため、データシートが欠かせません。
データシートを使ってできることの例を挙げます。


例えば、もし複数のチップに暗号化する機能がついていれば
チップ間でやり取りされるデータは暗号化されているかもしれません。

ここから以下のような推測が可能です。

 

  • 盗聴しても、暗号化されているので時間の無駄となる?
  • 暗号化方式は?
  • 鍵は何ビット?総当たりは無理?
  • どうやって鍵を共有する?プロトコルに穴は無いか?
  • 鍵は取り出せる?取り出せない? 

 

■信号探索について


基板上には様々な端子があり、コマンドやデータがやりとりされています。
その端子から、我々は信号を拾い、ユーザIDやパスワード、鍵情報を盗めないか試行します。


また、製品開発時はデバッグログを収集しながらコーディングやテストをしていると思われます。
もしデバッグ用インタフェースが残っていると、こんな影響があります。

 

  • 攻撃に有効なログが出力される?
  • IDや認証情報が流れている?
  • ひょっとしてコンソールからコマンドを打てる?
  • メモリダンプやメモリ書込できるコマンドもある?
  • つまりは、任意のコード実行=マルウェアが仕込まれたも同義?!

 


家庭用の機器はまだしも、産業用組込機器においては
デバッグ用インタフェースは残っていないことが望ましいです。

 

 

・・・前置きが長くなりました。
信号探索をしていく流れ、使うツールを紹介します。

 

 

探索の流れ:

  • テスタで電圧とGNDピンを確認
  • オシロスコープで信号の有無を探す
  • ロジック・アナライザで信号の内容を解析
  • 通信モジュールを接続

 

 

まずは電圧を確認します。
後ほど用いるオシロスコープやロジック・アナライザを接続して良いか確かめるためです。
ついでにピンアサインを確認し、ひとまずGNDピンだけでも見つけます。

 

f:id:cdi-teshima:20160428164717j:plain

 

 

オシロスコープを当てます。
先ほど怪しいと睨んでいた5ピンから、何らかの通信が見つかりました。

 

f:id:cdi-teshima:20160428164734j:plain

 

 

 

 


Saleae社のロジック・アナライザを接続したところ。

 

 

f:id:cdi-teshima:20160428164800p:plain

 

 

拡大していくと、読める文字列が見つかりました!
このため、このピンからはデバッグログが出てることが分かりました。

他にも、ロジック・アナライザでは通信速度や通信方式も解析できます。

 

f:id:cdi-teshima:20160428164824p:plain

 


そこで、 秋月電子で購入した通信モジュールを接続してみると・・・


秋月電子通商 FT232RL USBシリアル変換モジュール
http://akizukidenshi.com/catalog/g/gK-01977/

 

 

f:id:cdi-teshima:20160428164839p:plain

 

 

 

ログが取得できました!

 

更にシリアルコンソールを眺めていると・・・

 

 

f:id:cdi-teshima:20160428164851p:plain

 

ログインするためのプロンプトが出てきました。
試しに root/password を入力してみましたが、間違っているようです。
簡単な辞書攻撃をしてみましたが、うまくいきません。

 


・・・うーむ。
ハッシュ化されている可能性もありますが、正しいパスワード

または認証情報は、間違いなくこの機器の中に保存されているはずですよね。

そこで、次はフラッシュメモリを吸い出します。

 

 

 

■フラッシュメモリの吸い出し

 

通常の診断ではROMライタを使用しますが、
今回は技術研究を兼ねて Bus Pirateを使って吸い出します。

 

f:id:cdi-teshima:20160422171315j:plain


フラッシュメモリを剥がします。
温度調節機能付きのはんだごてを使うと、
基板のプリントパターンを傷つけることなく綺麗にはんだを取れます。
はんだ吸い取り線のヤニで茶色くなっていますが、
イソプロピルアルコールで拭うと綺麗に落ちます。(=病院の消毒液)

 

f:id:cdi-teshima:20160428165039j:plain

 

 

データシートから ピンアサインを確認します。

 

f:id:cdi-teshima:20160428165051j:plain

 

 

Bus Piratesと接続します。

 

f:id:cdi-teshima:20160428165102j:plain

 

 

f:id:cdi-teshima:20160428165119j:plain


データシートを見ると、読み出しコマンドは 0x03 です。
読み出しコマンド 0x03 を送った後に
アドレスを 24ビット分送って指定するようなので
先頭の 0x000000 を指定しました。

 

 

 

SPI>{ 0x03 0 0 0


/CS ENABLED
WRITE: 0x03 READ: 0x00
WRITE: 0x00 READ: 0x00
WRITE: 0x00 READ: 0x00
WRITE: 0x00 READ: 0x00

 

 

まずは 32KB 読むことに成功!

 

SPI>r:32768
READ: 0x10 0x00 0x01 0x03 0x24 0x1A 0x00 0x00 0x10 0x00 0x02.....

 

 

以後、フラッシュメモリの中身をずっと読み、
バイナリに固めたのがこちら!

 

f:id:cdi-teshima:20160428165212j:plain

 


吸い出したフラッシュメモリの解析は次の記事に記載します。

 

 

 



BusPiratesでの読み取り時、データが稀にビット反転を起こしているため
後ほど、この吸い出したイメージは壊れていることが分かりました・・・。

 

今回は社内での実験とノウハウ蓄積を兼ねているため BusPiratesを使用しましたが
実際の診断では ROMライタを使用し、読み出した情報が正確かどうか確認しています。

 

 

 

今回見つかった情報のまとめ

今回のターゲットは産業用機器では無いため、堅牢なつくりではありません。

見つかった情報からは、以下のような攻撃が可能と思われます。

 

デバッグログが取得可能

 

全て開示することはできませんでしたが、起動時には様々なログが出力されました。

中には、フラッシュメモリのパーティションやファイルシステムに関わる情報も存在しました。

攻撃者にとっては有利となります。

 

フラッシュメモリの内容が暗号化されていない 

 

内容やパーティションがはっきりと分かりました。

改ざんしたファームウェアを書きこむことで、攻撃者は任意のコードを実行させることが可能です。

 

ログインプロンプトが存在

 

IDとパスワードさえ判明していれば、機器を制御可能になります。

また、認証に際して失敗可能な回数が制限されていませんでした。

総当たり攻撃を試すことが可能です。

 

 

 

 

次回の記事の紹介をします


・吸い出したフラッシュメモリの解析
・公式サイトで配布されているFWの解析
・JTAGデバッガを繋いでみる
・工具の紹介

 


つづく!

 

株式会社サイバーディフェンス研究所 技術部 手島