SECCON 2017 Online CTF writeup

SECCON2017のオンライン予選に参加したのでそのwriteupです。結果は700ptで321位。頑張らねば・・・。

Vigenere3d (crypto 100)

$ python Vigenere3d.py SECCON{**************************} **************
POR4dnyTLHBfwbxAAZhe}}ocZR3Cxcftw9

出力結果が上の暗号文になるような平文を探せという問題。まずタイトルからヴィジュネル暗号を使っていると分かります。 平文の一部が分かっているので、それと暗号文の情報から鍵を計算するスクリプトを書きました。

実行すると、鍵の一部が分かります。

$ ./search_key.py 
plain:  SECCON{**************************}
key:    _KP2Za_**************************a
crypt:  POR4dnyTLHBfwbxAAZhe}}ocZR3Cxcftw9

鍵長は14ですが、ここで与えられた暗号化スクリプトの15行目をみると、鍵の8~14文字目は1~7文字目を反転したものであると分かるので、鍵は以下のようになります。

key: _KP2Za__aZ2PK_

鍵が割り出せたので、暗号文と鍵の情報から平文を計算するスクリプトを書きます。

実行すればflagが出ます。

$ ./decrypt.py 
POR4dnyTLHBfwbxAAZhe}}ocZR3Cxcftw9
_KP2Za__aZ2PK__KP2Za__aZ2PK__KP2Za
SECCON{Welc0me_to_SECCON_CTF_2017}

flag:

SECCON{Welc0me_to_SECCON_CTF_2017}

Run Me! (programming 100)

単純に実行すればflagが出そうですが、実際は再帰が深すぎてうまくいきません。みるからにフィボナッチ数列のプログラムなので、再帰を使わない方法で実装します。

実行すればflagが出ます。

$ ./Run_Me2.py 
SECCON{65076140832331717667772761541872}

flag:

SECCON{65076140832331717667772761541872}

putchar music (programming 100)

見やすく整形し、コンパイルが通るように書き直します。

$ gcc ./putchar_music2.c -lm
$ ./a.out > output.wav
$ aplay output.wav

あの有名な曲が流れてきます。

flag:

SECCON{STAR_WARS}

SHA-1 is dead (crypto 100)

SHA-1が衝突する2つのファイルを探す問題。条件として2つのファイルは2017KiB以上2018KiB以下である必要があります。SHA-1は既にGoogleなどによって衝突が発見されており、同じハッシュ値を持った内容の異なるファイルが公開されています。SHA-1の仕組み上、この2つのファイルの末尾に同じデータを追加してもこれらのハッシュ値は変わらないので、それを使っていい感じにファイルサイズを調整します。

$ ./sha-1_is_dead.py 
SHA-1 hash
file1: 108fa3cd125fbf1b14f05fef51fca3a0a12f1c97
file2: 108fa3cd125fbf1b14f05fef51fca3a0a12f1c97
$ ls
file1           sha-1_is_dead.py    shattered-2.pdf
file2           shattered-1.pdf
$

出来たファイルをsubmitしてflagを得ます。

flag:

SECCON{SHA-1_1995-2017?}

Log Search (web 100)

WebサーバのアクセスログをElastic searchで検索できるサービスが与えられます。検索窓にSQLインジェクションXSSなど試してもSomething wrong... X(というメッセージが出てくるだけで対策されているっぽいので、適当にflagで検索してステータスが200になっているログを探してアクセスしたらflagが出てきました。

http://logsearch.pwn.seccon.jp//flag-b5SFKDJicSJdf6R5Dvaf2Tx5r4jWzJTX.txt

flag:

SECCON{N0SQL_1njection_for_Elasticsearch!}

おそらくもっとちゃんとした解き方があると思いますが想定解が分かりません。

JPEG file (binary 100)

壊れたjpegファイルが与えられます。ファイル中のどこか1bit修正すれば表示できるようになるらしいです。試しにGIMPで開こうとすると以下のメッセージが出ました。

Corrupt JPEG data: premature end of data segment Unsupported marker type 0xfc

よって画像ファイル中から0xfcとなっている箇所を探して修正すれば良いと分かります。JPEGでは、0xffの後にマーカータイプが来るので、0xfffcを探します。

$ hexdump -C problem.jpeg | grep 'ff fc'
$

ところが上のコマンドを実行しても何も出てきませんでした。調べたところ、hexdumpの出力がちょうど0xffと0xfcの間で改行が入っていることが原因でした。次のスクリプトを実行してファイルを修正します。

ちゃんと画像が表示されてflagをゲット出来ました。

f:id:tuz358:20171211225607p:plain

flag:

SECCON{jp3g_study}

Thank you for playing!

flag:

SECCON{We have done all the challenges. Enjoy last 12 hours. Thank you!}

pwn解いていない&100点問題しか解いてないので来年までに強くなってもっと高い点数を取れるようにしたいです。

PythonによるWebスクレイピング入門に参加してきた

11月1日にキャンパスプラザ京都で行われたPythonによるWebスクレイピング入門に参加してきました。

まず初めにスクレイピングをするにあたっての注意点やマナーについての話がありました。スクレイピングするときは、やる前にアクセス先がどれぐらいのトラフィックまで耐えられるか考えたり、WebAPIがあるか確認したり、スクレイピングしようとしてるデータの著作権的な問題を確認したり、、、など知らないことがたくさんあったのでとても勉強になりました。

次にPythonとかBeautifulSoupの簡単な説明があったあとハンズオンに移りました。ハンズオンでは3つの課題が出されました。最初の課題はhtmlから指定された日本語テキストを抜き出すだけだったのですぐ出来るかと思いきや出力が文字化けしてて正しいのかどうかが分からず、、、。時間もあまり無かったので多分出来てるということにして課題2と3をやりました。2と3はそんなに難しくなくて、天気情報が存在するhtmlから最高気温と最低気温を抜き出すというものでした。今回はBeautifulSoupを使いましたが他にもPythonにはスクレイピングに使えるライブラリが色々あるそうです。

ハンズオンの後は企業講演がありました。楽天さんの開発現場やfreeeさんのオフィス(人をダメにするチェアとか笑)の話が聞けて良かったです。

交流会では他の参加者とプログラミングとかについて色々話せたので楽しかったです。様々な知見が得られたのでとても有意義な時間でした。

関西の方の勉強会に参加するのは初めてでしたがとても楽しかったです。

Pythonでニューラルネットを実装する

「Pythonで多層パーセプトロンを実装する」では、多層パーセプトロンによってXOR関数を近似しましたが、重みや閾値などのパラメータは自分で決めていました。そこで今回は誤差逆伝播法を使ったニューラルネットワークを実装することでパラメータを自動で学習させてみます。

パーセプトロンとの違い

パーセプトロンニューラルネットワークも脳の神経細胞が行う情報処理の仕組みを模倣したものであることは同じですが、ニューラルネットワークには以下のようなパーセプトロンとは異なる特徴があります。

特徴

  • 出力が2値でない
  • 活性化関数がステップ関数でない
  • 損失関数

活性化関数

パーセプトロンでは、ニューロンが発火するかどうかは閾値 \textstyle \thetaによって決められ、出力される値は \textstyle 0 \textstyle 1のどちらかでした。しかしニューラルネットワークでは活性化関数というものを導入することでより複雑な発火の表現ができるようになっています。つまり活性化関数はニューロンがどのように発火するかを決めている関数といえます。活性化関数には種類があり、代表的なものでは以下の3種類があります。

シグモイド関数

 f(x) = \dfrac{1}{1 + exp(-x)}
f:id:tuz358:20171023202846p:plain:w480

ReLU関数

 {\displaystyle  f(x) = \left\{ \begin{array}{1} x  \hspace{1em} ( x > 0 ) \\ 0 \hspace{1em} ( x \leqq 0 ) \end{array} \right.  }

f:id:tuz358:20171023202843p:plain:w480

tanh

 f(x) = tanh(x)
f:id:tuz358:20171023202840p:plain:w480

シグモイド関数やReLUは微分計算が簡単であることから広く使われています。tanhはRNNやLSTMなどの再帰ニューラルネットによく使われます。

損失関数

損失関数とは、ニューラルネットワークの出力が正解データからどれだけ離れているか(誤差)を計算する関数です。一般的に回帰問題では2乗和誤差、分類問題ではクロスエントロピー誤差が用いられます。

2乗和誤差

 E = \displaystyle \frac{1}{2} \sum_{i} (y_i-t_i)^2

クロスエントロピー誤差

 E = \displaystyle - \sum_{i} t_i logy_i

ニューラルネットワークの順伝播

ニューラルネットを実装するにあたり、まず順伝播の計算式を導出します。今回は以下のような構造のネットワークを考えます。

f:id:tuz358:20171024084516p:plain:w300

順伝播の流れを分かりやすくするために活性化関数( \textstyle \sigma)を明示的に書きました。 \textstyle x_iは入力信号、 \textstyle w_{ij}は重み、 \textstyle y_iは出力信号を表しています。 \textstyle b_iはバイアスと呼ばれるもので、入力信号 \textstyle x_iと重み \textstyle w_{ij}をかけたものにこのバイアスを加えることで前回のエントリでいう閾値 \textstyle \thetaと同じ役割を果たします。図では、バイアス項を加える処理を、入力 \textstyle 1 \textstyle b_iをかけるという処理で表しています。以上のことを踏まえると、まず活性化関数の手前までの計算は次のように表せます。

 y_1 = x_1 w_{11} + x_2 w_{21} + b_1
 y_2 = x_1 w_{12} + x_2 w_{22} + b_2

バイアス項が加わっただけで後はパーセプトロンの計算と変わりありません。 また、上の式はベクトルと行列を用いて一つの式にまとめることが出来ます。

 \displaystyle \begin{pmatrix} y_1 \quad y_2 \end{pmatrix} = \begin{pmatrix} x_1 \quad x_2 \end{pmatrix} \begin{pmatrix} w_{11} \quad w_{12} \\\ w_{21} \quad w_{22} \end{pmatrix} + \begin{pmatrix} b_1 \quad b_2 \end{pmatrix}

ベクトルや行列を使って表すことで、プログラムに落とし込むときに線形代数のライブラリを活用でき、さらに一つの式にまとめることで入力や出力の数がどんなに多くなっても一層分の順伝播の計算は一回の計算で行えるようになります。ここで、

 \boldsymbol{Y} = \begin{pmatrix} y_1 \quad y_2 \end{pmatrix},\quad \boldsymbol{X} = \begin{pmatrix} x_1 \quad x_2 \end{pmatrix},\quad  \boldsymbol{W} = \begin{pmatrix} w_{11} \quad w_{12} \\\ w_{21} \quad w_{22} \end{pmatrix},\quad  \boldsymbol{B} = \begin{pmatrix} b_1 \quad b_2 \end{pmatrix}

とすれば、上の式は

 \boldsymbol{Y} = \boldsymbol{X} \cdot \boldsymbol{W} + \boldsymbol{B}

と非常に簡単な式になります。この計算は線形変換と呼ばれます。よって活性化関数を含めたこのニューラルネットワーク全体の順伝播の式は、

 \boldsymbol{out} = \sigma \begin{pmatrix} \boldsymbol{Y} \end{pmatrix} = \sigma \begin{pmatrix} \boldsymbol{X} \cdot \boldsymbol{W} + \boldsymbol{B} \end{pmatrix}

となります。さらに学習時には上の出力が損失関数に入力として与えられ、例えば2乗誤差を使う場合、

 \displaystyle E = \frac{1}{2}\sum_{i}(out_i - t_i)^2

が誤差 \textstyle Eとして出てきます。そしてこの誤差 \textstyle Eから各パラメータ(重みやバイアスなど)に対する微分を効率的に計算するアルゴリズムが次に説明する誤差逆伝播法です。

誤差逆伝播

ニューラルネットワークを学習させるにあたり、ニューラルネットワークが出力した誤差をもとに各パラメータをどれだけ修正するかを求める必要があります。この修正量は「あるパラメータを一定量変化させたときの誤差の変化量」と言い換えることができ、それは誤差に対する各パラメータの微分で表せます。この微分値を効率良く計算するアルゴリズム誤差逆伝播法で、その原理の基本は連鎖律によって説明できます。

では上の順伝播計算で得た誤差から、学習すべきパラメータの誤差に対する微分を求めてみます。誤差逆伝播法では、出力層から逆順に誤差が伝播していくため、今回のネットワーク構造の場合は損失関数 -> 活性化関数 -> 線形変換の順に誤差が伝わっていきます。

損失関数の逆伝播

損失関数では学習するパラメータはありませんが後ろの層に誤差を伝えるため、 \textstyle \frac{\partial E}{\partial y}を計算する必要があります。今回は損失関数に2乗和誤差を用いるので \textstyle \frac{\partial E}{\partial y}は以下のようにして求められます。

 \begin{eqnarray*} \dfrac{\partial E}{\partial y} &=& \displaystyle \dfrac{\partial}{\partial y} \begin{pmatrix} \dfrac{1}{2} \sum_{i}(y_i - t_i)^{2} \end{pmatrix} \\\ \\\ &=& 2 \cdot \dfrac{1}{2} \sum_{i}(y_i - t_i) \cdot (y_i - t_i)' \\\ \\\ &=& \sum_{i}(y_i - t_i) \end{eqnarray*}

これと順伝播の式を合わせて損失関数をPythonで書いてみます。

活性化関数の逆伝播

活性化関数についても同様に、学習すべきパラメータは無いので単純に誤差を入力信号で微分して後ろの層へ伝播させるだけです。今回はシグモイド関数とReLUを使います。まずシグモイド関数微分してみると、

 \begin{eqnarray*} \dfrac{d}{dx}f(x) &=& \dfrac{ -(1 + exp(-x))' }{ (1 + exp(-x))^{2} } \\\ \\\ &=& \dfrac{ exp(-x) }{ (1 + exp(-x))^{2} } \\\ \\\ &=& \dfrac{ 1 }{ 1 + exp(-x) } \dfrac{ exp(-x) }{ 1 + exp(-x) } \\\ \\\ &=& \dfrac{ 1 }{ 1 + exp(-x) } \dfrac{ 1 + exp(-x) -1}{ 1 + exp(-x) } \\\ \\\ &=& \dfrac{ 1 }{ 1 + exp(-x) } \begin{pmatrix} 1 - \dfrac{ 1 }{ 1 + exp(-x) } \end{pmatrix} \end{eqnarray*}

まとめると、

 \dfrac{d}{dx}f(x) = f(x) (1 - f(x))

導関数シグモイド関数自身で表せます。これは重要な性質で、微分の計算をするときに順伝播で求めた値を再利用できるということです。Pythonによるシグモイド関数の実装は以下のようになります。

次にReLUの逆伝播を考えます。ReLUの微分はとても簡単です。

 { \dfrac{d}{dx}f(x) = \left\{ \begin{array}{1} 1  \hspace{1em} ( x > 0 ) \\ 0 \hspace{1em} ( x \leqq 0 ) \end{array} \right.  }

Pythonでの実装はこちらのリポジトリが参考になりました。

線形変換の逆伝播

線形変換では、重み \textstyle w_{ij}とバイアス \textstyle b_{i}の2つの学習パラメータがあります。そのため \tfrac{\partial E}{\partial \boldsymbol{W}},  \tfrac{\partial E}{\partial \boldsymbol{B}}を求めます。当然後ろの層へ誤差を伝えるために \tfrac{\partial E}{\partial \boldsymbol{X}}も求めます。

また、微分の計算を分かりやすくするために、図のように線形変換を計算グラフで表します。

f:id:tuz358:20171024215155p:plain:w360

上の図は式  \textstyle y_1 = x_1 w_{11} + x_2 w_{21} + b_1 を表しています。さらに、上の式の計算途中の中間値を以下のように保存しておきます。

 z_1 = x_1 w_{11} , \quad z_2 = x_2 w_{21} , \quad z_3 = z_1 + z_2

まず例として、出力から逆にたどって誤差に対する x_1成分の微分を計算します。すると \tfrac{\partial E}{\partial x_1}は連鎖律を用いて、

 \dfrac{\partial E}{\partial x_1} = \dfrac{\partial E}{\partial z_3} \dfrac{\partial z_3}{\partial z_1} \dfrac{\partial z_1}{\partial x_1}

と書けます。ここで、加算の部分は前の層から伝わってきた誤差をそのまま次の層に渡し、乗算の部分は入力信号をひっくり返したものを伝播させるので、

 \dfrac{\partial E}{\partial x_1} = \dfrac{\partial E}{\partial y_1} w_{11}

となります。他の成分も同様に誤差に対する微分を求めると、

 \dfrac{\partial E}{\partial x_2} = \dfrac{\partial E}{\partial z_3} \dfrac{\partial z_3}{\partial z_2} \dfrac{\partial z_2}{\partial x_2} = \dfrac{\partial E}{\partial y_1} w_{21}

 \dfrac{\partial E}{\partial w_{11}} = \dfrac{\partial E}{\partial z_3} \dfrac{\partial z_3}{\partial z_1} \dfrac{\partial z_1}{\partial w_{11}} = \dfrac{\partial E}{\partial y_1} x_1

 \dfrac{\partial E}{\partial w_{21}} = \dfrac{\partial E}{\partial z_3} \dfrac{\partial z_3}{\partial z_2} \dfrac{\partial z_2}{\partial w_{21}} = \dfrac{\partial E}{\partial y_1} x_2

 \dfrac{\partial E}{\partial b_1 } = \dfrac{\partial E}{\partial y_1}

となります。

f:id:tuz358:20171024215159p:plain:w360

また、上の計算グラフは式  \textstyle y_2 = x_1 w_{12} + x_2 w_{22} + b_2 を表しており、計算途中の値は以下のように保存します。

 z_4 = x_1 w_{12} , \quad z_5 = x_2 w_{22} , \quad z_6 = z_4 + z_5

これも先ほどと同様に誤差に対する各成分の微分を求めると、

 \dfrac{\partial E}{\partial x_1} = \dfrac{\partial E}{\partial z_6} \dfrac{\partial z_6}{\partial z_4} \dfrac{\partial z_4}{\partial x_1} = \dfrac{\partial E}{\partial y_2} w_{12}

 \dfrac{\partial E}{\partial x_2} = \dfrac{\partial E}{\partial z_6} \dfrac{\partial z_6}{\partial z_5} \dfrac{\partial z_5}{\partial x_5} = \dfrac{\partial E}{\partial y_2} w_{22}

 \dfrac{\partial E}{\partial w_{12}} = \dfrac{\partial E}{\partial z_6} \dfrac{\partial z_6}{\partial z_4} \dfrac{\partial z_4}{\partial w_{12}} = \dfrac{\partial E}{\partial y_2} x_1

 \dfrac{\partial E}{\partial w_{22}} = \dfrac{\partial E}{\partial z_6} \dfrac{\partial z_6}{\partial z_5} \dfrac{\partial z_5}{\partial w_{22}} = \dfrac{\partial E}{\partial y_2} x_2

 \dfrac{\partial E}{\partial b_2 } = \dfrac{\partial E}{\partial y_2}

これで誤差に対する全ての成分の微分が計算出来たのでそれらをまとめます。

 \begin{eqnarray*} \dfrac{\partial E}{\partial \boldsymbol{X} } &=& \begin{pmatrix} \dfrac{\partial E}{\partial x_1} \quad \dfrac{\partial E}{\partial x_2} \end{pmatrix} \\\ &=& \begin{pmatrix} \dfrac{\partial E}{\partial y_1} w_{11} +  \dfrac{\partial E}{\partial y_2} w_{12} \quad \dfrac{\partial E}{\partial y_1} w_{21} + \dfrac{\partial E}{\partial y_2} w_{22} \end{pmatrix} \\\ &=& \begin{pmatrix} \dfrac{\partial E}{\partial y_1} \quad \dfrac{\partial E}{\partial y_2} \end{pmatrix} \cdot \begin{pmatrix} w_{11} \quad w_{21} \\\ w_{12} \quad w_{22} \end{pmatrix} \\\ &=& \dfrac{\partial E}{\partial \boldsymbol{Y}} \cdot \boldsymbol{W}^T \end{eqnarray*}

 \begin{eqnarray*} \dfrac{\partial E}{\partial \boldsymbol{W} } &=& \begin{pmatrix} \dfrac{\partial E}{\partial w_{11}} \quad \dfrac{\partial E}{\partial w_{21}} \\\ \dfrac{\partial E}{\partial w_{12}} \quad \dfrac{\partial E}{\partial w_{22}} \end{pmatrix} \\\ &=& \begin{pmatrix} \dfrac{\partial E}{\partial y_1} x_1 \quad \dfrac{\partial E}{\partial y_1} x_2 \\\ \dfrac{\partial E}{\partial y_2} x_1 \quad \dfrac{\partial E}{\partial y_2} x_2 \end{pmatrix} \\\ &=& \begin{pmatrix} x_1 \\\ x_2 \end{pmatrix} \cdot \begin{pmatrix} \dfrac{\partial E}{\partial y_1} \quad \dfrac{\partial E}{\partial y_2} \end{pmatrix} \\\ &=& \boldsymbol{X}^T \cdot \dfrac{\partial E}{\partial \boldsymbol{Y}} \end{eqnarray*}  \dfrac{\partial E}{\partial \boldsymbol{B} } = \begin{pmatrix} \dfrac{\partial E}{\partial b_1} \quad \dfrac{\partial E}{\partial b_2} \end{pmatrix} = \begin{pmatrix} \dfrac{\partial E}{\partial y_1} \quad \dfrac{\partial E}{\partial y_2} \end{pmatrix} = \dfrac{\partial E}{\partial \boldsymbol{Y}}

すなわち、

 \dfrac{\partial E}{\partial \boldsymbol{W} } =  \boldsymbol{X}^T \cdot \dfrac{\partial E}{\partial \boldsymbol{Y}}

 \dfrac{\partial E}{\partial \boldsymbol{X} } = \dfrac{\partial E}{\partial \boldsymbol{Y}} \cdot \boldsymbol{W}^T

 \dfrac{\partial E}{\partial \boldsymbol{B} } = \dfrac{\partial E}{\partial \boldsymbol{Y}}

したがってPythonでの実装は次のようになります。

勾配法

誤差逆伝播法によって各パラメータをどれだけ修正すれば良いか分かったので、次は実際にパラメータを調整してニューラルネットワークを学習させていきますが、その際に勾配法というアルゴリズムを使ってパラメータの値を更新していきます。勾配法(この場合は勾配降下法)では、損失関数の出力を最小にするために損失関数の勾配を活用します。具体的には、損失関数の勾配が小さくなる方へ進むことで損失関数の最小値を探します。式で表すと以下のようになります。

パラメータの更新式

 \boldsymbol{W_{new}} = \boldsymbol{W_{old} } - \eta \dfrac{\partial E}{\partial \boldsymbol{W_{old}}}

 \boldsymbol{B_{new}} = \boldsymbol{B_{old} } - \eta \dfrac{\partial E}{\partial \boldsymbol{B_{old}}}

上が重みパラメータ、下がバイアスパラメータの更新を行う式です。式中の \textstyle \etaは学習率というもので、誤差逆伝播法で得た修正量をどれだけ反映するか(どれだけパラメータを更新するか)を調整する係数です。Pythonで実装すると、以下のようになります。

今回実装するネットワークでは学習パラメータが存在するのは線形変換の部分のみなのでシグモイド関数やReLUでは何もしません。

実装

実装するのに必要な式が一通り表せたので、いよいよPythonニューラルネットを実装します。今回もXOR関数を学習させてみます。まず以下のようにニューラルネットを訓練するクラスを書きました。

nnetモジュールには今までに書いた線形変換や活性化関数などのクラスがまとめてあります。また、上の訓練クラスを使ってXORの学習を行うコードスニペットの一例を以下にのせます。

さらに、学習したモデルを使って推論を行うスクリプトを作成しました。

上のコードでニューラルネットワークを訓練した結果、以下のようにXOR関数をうまく学習できました。

$ ./xor_predict.py ./xor_sample_weight.pkl
in: [ 0.  0.]  ->  out: 0 (0.00486784050737)
in: [ 0.  1.]  ->  out: 1 (0.997805826021)
in: [ 1.  0.]  ->  out: 1 (0.997805828447)
in: [ 1.  1.]  ->  out: 0 (0.00486784050737)
$

今回作ったプログラムは以下のリポジトリに置いてあります。 github.com

CEATEC JAPAN 2017に行ってきた

www.ceatec.com

10/3 ~ 10/6の4日間、幕張メッセにてCEATEC JAPAN 2017が行われていたので最先端のテクノロジーに触発されに行ってきました。 f:id:tuz358:20171006204316j:plain

まず個人的に最も気になっていたASUKANETのブースを見に行きました。ASUKANETはAIプレートという特殊なパネルを使った空中投影やデジタルサイネージに関する製品やサービスを提供しています。 f:id:tuz358:20171006204329j:plain 展示では実際に物体が目の前にあるかのように映像が飛び出していました。

f:id:tuz358:20171006204043j:plain 他にも映像から危険な行動をしている人を検知するシステムであったり、

f:id:tuz358:20171006204324j:plain 日本語の文字を書くロボットだったり様々な最新技術に触れることが出来ました。

全体的にIoTとかスマートホーム関連の出店が多かったかなあと思います。唐突にホームオートメーションとかやってみたくなりました。

MapboxでSSHの不正アクセスを可視化してみる

オープンソースの地図プラットフォームであるMapboxを使ってSSHブルートフォースアタックを可視化してみます。

Mapboxとは

www.mapbox.com

MapboxはWeb上で簡単に地図を作成、公開できるサービスです。OpenstreetMapをベースとして作られています。某G**gle Mapも似たようなサービスを提供していますが、Mapboxであればデザインや表示する情報などを自分好みにカスタムした地図を使えます。

きっかけ

ある日Mapboxからこんな感じのメールが送られてきました。

Happy Friday!

It's #weekendmaphack time. Show us your take on our latest designer maps styles. Build something with one (or more) of the four below, and I’ll send you our new stickers.

Share screenshots and videos of what you’re creating with @mapbox on Twitter, using the hashtag #weekendmaphack. I’ll be retweeting and liking your tweets all weekend!

Happy hacking.

ということでMapboxを使って何か作ったらそのスクリーンショットとかを#weekendmaphackタグをつけて呟くとステッカーをくれるらしいです。当時ハニーポットに興味を持っていたので(ステッカー欲しかったので)週末を使って不正アクセスがきた場所を地図上に表示するというものを作ってみることにしました。

準備

実装する前にまず実際の不正アクセスのデータを用意する必要があります。今回はSSHに対するブルートフォースアタックのデータを集めます。本来はセキュリティを考慮してハニーポットによって不正アクセスのデータを集めますが、週末の限られた時間しかないので直接ログを収集することにしました。自宅のPC(Linux)でSSHサーバーを動作させ、22番ポートが開いた状態で一定時間放置しておきます(こんなログの集め方でいいのだろうか・・・)。ポートを開けてから15分ぐらいで最初の攻撃が来始め、数時間ぐらい経つと可視化するのに十分な量のログを収集できました。

ログ解析

次に収集したログを解析していきます。Linuxでは認証関係のログは/var/log/auth.logに記録されます。SSHサーバーに対してブルートフォースアタックが来た場合、例えば以下のようなログが見られます。

この攻撃を地図上に可視化するとき最も大事な情報は攻撃元の位置情報を得るためのIPアドレスです。後は攻撃が来た時間や攻撃が来た回数も重要でしょう。上のログのように、auth.logには、"Failed password"という文字列が含まれている行に攻撃元のIPアドレス情報も乗っています。というわけで、まずauth.logから攻撃元のIPアドレスと攻撃が来た時間を抜き出してみます。

>>> import re
>>> f = open('/var/log/auth.log')
>>> lines = f.read().splitlines()
>>> lines = [x for x in lines if 'Failed password' in x]
>>>
>>> ip_ptrn = re.compile(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}')
>>> time_ptrn = re.compile(r'^\S+\s\d{1,2}\s\d{1,2}:\d{1,2}:\d{1,2}')
>>>
>>> access_times = [time_ptrn.findall(x)[0] for x in lines]
>>> ips = [ip_ptrn.findall(x)[0] for x in lines]

まず"Failed password"という文字列が含まれる行のみを抽出したあと正規表現を使って攻撃元IPアドレスの一覧を得ます。攻撃が来た時間についても同様に正規表現を用います。

ただし、ブルートフォースアタックの性質上同一IPアドレスから何度も攻撃が来るので以下のようにIPアドレスが重複する箇所があります。

>>> ips[:5]
['xxx.xxx.xxx.xxx', 'yyy.yyy.yyy.yyy', 'yyy.yyy.yyy.yyy', 'zzz.zzz.zzz.zzz', 'zzz.zzz.zzz.zzz']
>>>

そこでさらに以下のような処理を行って重複を取り除きつつIPアドレスごとに攻撃を受けた回数を調べます。

>>> hosts = {x: ips.count(x) for x in ips}.items()
>>> hosts[:3]
[('xxx.xxx.xxx.xxx', 681), ('yyy.yyy.yyy.yyy', 5), ('zzz.zzz.zzz.zzz', 1)]
>>>

最後にIPアドレス、時刻、攻撃回数の情報を統合します。

>>> atks = [{'date':access_times[ips.index(hosts[x][0])], 'ip':hosts[x][0], 'num':hosts[x][1]} for x in xrange(len(hosts))]
>>> atks[:2]
[{'date': 'Jul 23 20:48:58', 'ip': 'xxx.xxx.xxx.xxx', 'num': 681}, {'date': 'Jul 23 20:32:54', 'ip': 'yyy.yyy.yyy.yyy', 'num': 5}]
>>>

GeoIP

ログを解析したら、freegeoip.netを使ってIPアドレスから位置情報を取得します。freegeoip.netではAPIが提供されており、以下のフォーマットに従ってHTTPリクエストを送ることでIPアドレスから攻撃元の位置情報を得ることができます。

freegeoip.net/{format}/{IP_or_hostname}

{format}にはjsoncsvなど自分が欲しいデータの形式を入れます。今回はjsonフォーマットで返して欲しいので、リクエストを送るURLは例えば以下のようになります。

http://freegeoip.net/json/xxx.xxx.xxx.xxx

では先ほど解析したログの情報から位置情報を取得してみます。

>>> import requests
>>> import json
>>>
>>> geoips = [json.loads(requests.get('http://www.freegeoip.net/json/{0}'.format(x['ip'])).text) for x in atks]

この処理は各IPアドレスごとに上記URLに対してHTTPリクエストを行なっているため、終了するまで少し時間がかかります。

また、後ほど説明しますがMapboxで位置情報のデータを扱う際にgeojson形式である必要があるのでgeojson形式に直しておきます。

>>> import copy
>>> point = {"type":"Feature", "properties":{"ip":"ip", "date":"date", "num":"num"}, "geometry":{"type":"Point","coordinates":[10,20]}}
>>> points = []
>>> for x in xrange(len(geoips)):                                               
...     tmp = copy.deepcopy(point)
...     tmp['geometry']["coordinates"] = [geoips[x]["longitude"], geoips[x]["latitude"]]
...     tmp["properties"]["ip"] = geoips[x]["ip"]
...     tmp["properties"]["date"] = atks[x]["date"]
...     tmp["properties"]["num"] = atks[x]["num"]
...     points.append(tmp)
... 
>>> geojson = {"type": "FeatureCollection", "features": points}
>>> json.dump(geojson, open('attacks.geojson', "w"))
>>>

実装(可視化)

攻撃元の位置情報が得られたら、いよいよMapboxを使って攻撃を可視化します。Mapboxを使う際にMapboxアカウントが必要になるので持っていない人は作る必要があります。実装するにあたってはMapbox GL JS API | Mapboxにサンプルコードがたくさんあるのでここから適当にコードを引っ張り出して使います。

アップロードしたgeojsonファイルにある座標を地図上に表示するスクリプトです。38行目の'your access token'の所に自分のAPIアクセストークンを入れ、46行目のvar geojson = の所に自分が作ったgeojsonファイルの内容を貼り付けます。また、マーカー用画像を上のHTMLファイルと同じディレクトリにmarker.pngという名前で置いておきます。出来たHTMLファイルをブラウザで表示すればこんな風に攻撃元の位置情報が表示されます。

おまけ(本題)

出来たものを公開してから1 ヶ月ぐらい経った後にMapboxからステッカーが送られてきました。

f:id:tuz358:20170903185803j:plain

OSS関連のノベルティが貰えるとやっぱり嬉しいです。ちなみに今回作ったものはここに置いてあります。 github.com

セキュリティ・キャンプ全国大会2017に参加してきた

8/14(月) ~ 8/18(金)の5日間、クロスウェーブ府中にて開催されたセキュリティ・キャンプ全国大会2017に参加してきたのでその感想を書こうと思います。

www.ipa.go.jp

セキュリティ・キャンプとは、22歳以下の学生を対象とした情報セキュリティに関する合宿形式のイベントです。情報セキュリティ業界の最前線で活躍する人たちから最先端の講義を受けることができます。ちなみに宿泊費や交通費等は全て主催者側で負担してくれます。

Day 1

真面目に5日分の服を準備して持っていきましたが、荷物が重すぎて会場までの移動が大変でした。会場で洗濯できるので5日分も要らないです。当日は受付開始30分前ぐらいに会場に着きましたが、既に結構たくさんの参加者が会場入りしていて、その人たちと名刺交換したり自己紹介したりしました。

開会式が行われた後、まず上野さんによる「セキュリティ基礎」という講義がありました。

AIによる自動化が進む中で、今後も残るセキュリティの仕事とは何かという議題で同じ班の人とディスカッションしました。

次にJPCERT/CCの小宮山さんによる特別講義がありました。

海外で情報セキュリティの仕事をする上での苦労話や体験秘話などを聞くことができました。あと単純にお金が欲しいだけならセキュリティエンジニアになるよりひよこ鑑定士になった方が良いという話も聞きました(笑)。 doda.jp

次にサイバーディフェンス社の大徳さんによる特別講義を受けました。

主にフォレンジックの話でした。大徳さんは本当に自分が好きなことを仕事にできているんだなという印象を受けました。

Day 2

この日から専門講義が始まります。選択コースの人は7月頃にアンケートで受けたい講義を聞かれます。自分は出来るだけ広い分野を学びたかったのでA〜Eトラックまで万遍なく選ぶようにしました。結果全講義第一希望で通ったので良かったです。

A1 Powershellベースのマルウェアとその防御手法

最初の専門講義はマクニカネットワークスの凌翔太さんによるPowershellを使ったマルウェアについての講義でした。

講義の前半は、マルウェアとは?というところから始まりPowershellの基礎やPowershellベースのマルウェアの特徴などを学びました。山手線ゲーム風にマルウェアの名前を言ったりもしました(笑)。後半は2チームに分かれ、ShinoBOTを使って実際にC2サーバと通信するPowershellスクリプトを書きました。自分はコマンドの実行結果をC2サーバに送信する部分の処理を担当しました。どちらのチームが先にPCから特定のファイルを窃取できるかを競い、チームで協力して先にファイルの内容を読み取れました。講義の終わりにはBadUSBからPowershellスクリプトを起動したり実行後Powershellスクリプトのプロンプトを見えないようにするデモを凌さんに見せてもらいました。Powershell恐ろしい・・・と言わざるを得ませんでした。凌さんに会えたのも嬉しかったですし、内容的にも一番興味がある講義だったので受けることが出来て良かったです。想像通りというか想像以上に面白かったです。

D2~3 カーネルエクスプロイトによるシステム権限奪取

後半は神戸大学の木村さんによるカーネルエクスプロイトの講義でした。正直キャンプでやるまで事前課題でしかLinuxカーネルは触れてこなかったので当日ついていけるかどうか不安でした。前半はまずLinuxメモリ管理の基礎ということでセグメント・ページング機構やリングプロテクションなどについて学んだりBadIRET(CVE-2014-9322)を使って単純にカーネルをクラッシュさせたりしました。その次にWebkit脆弱性(CVE-2014-1303)を使ってpidを取得したりファイルシステムのダンプを読み取ったりしました。ファイルシステムのダンプを取るには3つのシステムコールを呼ぶ必要があったのですがヘルパー関数があったので自分でROPを組んで実装するシステムコールは1つだけでした。それでもかなり時間がかかり、結局ファイルシステムのダンプに成功したのは制限時間ギリギリでした。BadIRETで権限昇格を行う仕組みについては理解できなかったので復習しようと思います。その後最後の1時間でカーネルエクスプロイトを防ぐ手法についてディスカッションを行いました。結構多くの人がintelのチップレベルで実装したROP防止機構について話していました。とても内容が難しくて理解できなかった部分もありましたがその分知的好奇心を刺激された感じがして面白かったです。

Day 3

E4 サイバー犯罪捜査の現場

3日目前半は千葉県警の方々によるサイバー犯罪捜査の現場に関する講義でした。

サイバー犯罪捜査班の仕事内容や現在のサイバー犯罪の現状などについて話を聞いた後、捜索・差し押さえ演習を行ってデータを解析し証拠を集めました。あまり詳しいことは書けませんがかなり面白い講義でした。来年もあれば受講することをお勧めします。

A5 Availability Challenge ~サービスの可用性を確保せよ~

3日目後半はラックの川口さんによる講義でした。いわゆるHardeningのような感じで、ECサイトが動作しているサーバーの可用性を確保し続けてチームごとに売上を競うという内容でした。サーバーには脆弱性のあるサービスがインストールされていたり定期的に様々な攻撃が飛んできます。僕はチームの中で売上の監視を担当したり時々サービスの状態を確認したりしました。最初の方でWebページの改ざんに気付くのが遅れたことが少し悔しかったですが、結果的に一番売上が高かったので楽しかったです。

専門講義が終わった後は2つのBoFとサイバーディフェンス社の企業プレゼンを受けて寝ました。

Day 4

専門講義最終日です。

C6 スマートフォン向けゲームのセキュリティ

午前中はDeNAの杉山さんによるゲームのチート対策についての講義でした。

事前課題が面白かったので講義もとても楽しみでした。Androidエミュレータを使ってゲームを動かし、メモリを改ざんして敵のHPを0にしたりなど様々なチートを行いました。途中でゲームデータがリセットされてしまったのが悲しかったです・・・。

A7 ファジング実習

IPAの小林さんとシマンテックの山下さんによるファジングの講義でした。

前半の2時間は小林さんによるpeachを使ったネットワーク経由でのファジングに関する講義でした。事前課題をあんまりしっかりやっていなかったので少々不安でしたがpeachの使い方など一から順番に説明する感じの講義だったので非常に分かりやすい内容でした。後半2時間は講師が山下さんに交代し、aflを使ってC言語のプログラムに対してのファジングを行いました。初めてaflを使ったのですが短時間で結構クラッシュが見つかったので便利だなあと思いました。CTFのpwn問でも使えそうです。

専門講義の後はCybozuの企業プレゼンを聞いてグループワークをやって寝ました。なかなか意見がまとまらなかったので深夜まで頑張ってくれた資料作成担当の人には頭が上がりません。あとサプライズプレゼントということで書籍やステッカーなどのグッズがもらえました。年齢の若い順に好きなものを取っていく感じだったので結構自分が欲しかったものが手に入りました(特にShinoBOTステッカー)。

Day 5

グループワークの発表がありました。発表担当だったのですが自分が作っていないスライドを使って発表するのは初めてだったのでちゃんと意図が伝わるか不安でした。一応形になったとは思います。その後修了証が配られたり閉会式があったりして終わりました。あと#seccampがトレンド入りしてました。

最後に

たくさんの書籍やグッズを頂きました。5日分の服にこれらが加わり、帰りの荷物が重すぎてダメでした。 f:id:tuz358:20170821212949j:plain

セキュリティ・キャンプはとにかく充実していて最高でした。今後はチューターに応募するなどしてセキュリティ・キャンプに関わり続けていきたいと思います。関係者の皆様本当にありがとうございました。


PythonでグローバルIPアドレスを取得する

最近グローバルIPアドレスを調べることが多くなったのでまとめておくことにする。

DynDNS

dyn.com

DynDNSは、ダイナミックDNSのサービスです。自分のグローバルIPアドレスを知りたい場合はここにアクセスします。返ってくる結果には余分な文字列が含まれるので少しだけ追加の処理を行う必要があります。

>>> import urllib2 as u
>>> url = 'http://checkip.dyndns.com/'
>>> res = u.urlopen(url)
>>> html = res.read()
>>> html
'<html><head><title>Current IP Check</title></head><body>Current IP Address: xxx.xxx.xxx.xxx</body></html>\r\n'
>>> html.split(':')[1].strip().rstrip('</body></html>\r\n')
'xxx.xxx.xxx.xxx'
>>> 

ieServer.Net

www.ieserver.net

ieServer.NetもDynDNSと同様にダイナミックDNSを提供してくれるサービスで、無料で利用することが出来ます。http://ipcheck.ieserver.net/にアクセスすると自分のグローバルIPアドレスを教えてもらえます。

>>> import requests
>>> url = 'http://ipcheck.ieserver.net/'
>>> res = requests.get(url)
>>> res.text
u'xxx.xxx.xxx.xxx\n'
>>> str(res.text.rstrip('\n'))
'xxx.xxx.xxx.xxx'
>>>

IP address information

http://inet-ip.info/

このWebサイトは接続してきたクライアントのIPアドレス、ホスト名や国名などの情報を表示してくれます。URLの末尾にipを付加するとIPアドレスのみが取得でき、jsonを付加するとIPアドレス以外の情報も含めた様々な情報をjson形式で取得できます。

>>> import requests
>>> res = requests.get('http://inet-ip.info/ip')
>>> res.text
u'xxx.xxx.xxx.xxx'
>>>

ifconfig.io

http://ifconfig.io/

こちらもグローバルIPアドレスやその他のネットワーク情報を出力してくれるWebサイトです。ただし、純粋にIPアドレスの文字列のみを返して欲しい場合はUser-Agentにcurlを設定してあげる必要があります。

>>> import requests
>>> headers = {'User-Agent': 'curl'}
>>> res = requests.get('http://ifconfig.io/', headers=headers)
>>> res.text
u'xxx.xxx.xxx.xxx\n'
>>> str(res.text.rstrip('\n'))
'xxx.xxx.xxx.xxx'
>>>

その他

ipify.org

>>> import requests
>>> res = requests.get('http://api.ipify.org/')
>>> res.text
u'xxx.xxx.xxx.xxx'
>>>

ident.me

>>> import requests
>>> res = requests.get('http://ident.me')
>>> res.text
u'xxx.xxx.xxx.xxx'
>>>

icanhazip.com

>>> import requests
>>> res = requests.get('http://icanhazip.com')
>>> res.text
u'xxx.xxx.xxx.xxx\n'
>>>

trackip.net

>>> import requests
>>> res = requests.get('http://www.trackip.net/ip')
>>> res.text
u'xxx.xxx.xxx.xxx'
>>>

dnsomatic.com

>>> import requests
>>> res = requests.get('http://myip.dnsomatic.com')
>>> res.text
u'xxx.xxx.xxx.xxx'
>>> 

ちなみにグローバルIPアドレスをプログラムから利用したりしない場合はわざわざPythonスクリプトにしなくてもcurlとか使った方が速いです。