簡単なバッファオーバーフローを再現してみる(4)

前回に引き続き、こちらの参考書の内容を元にBOFサンプルを手元で再現し、動作を追ってみたいと思います。

(1)~(3)はスタックセグメントにおけるBOF発生を再現していましたが、今回はヒープセグメントと環境変数を使ったサンプルを再現してみたいと思います。(環境は(2)と同じ、最近のgdbだと同じ挙動を再現しませんでした。)

とりあえず書いた脆弱性を持ったサンプルがこちら。書籍にあるサンプルプログラムはmallocのエラーチェックとか、エラー出力専用の関数などが用意されていましたが、長くなるので今回はギリギリまで削って、必要最低限だけ抜き出して書き直しました。

プログラムの動作はコード見てもらえればわかりやすいと思います。同じディレクトリにあるmemo_app_dataに対して、プログラムの実行ユーザのIDと、引数で与えた文字列を記録するだけの簡単なメモ帳プログラムですね。

 

コンパイルしたら、このプログラムの所有者をrootにして、suidを設定して、gdbデバッグ開始

注目するべきは16,17行目のmalloc()関数です。malloc()関数はヒープセグメントに領域を確保して、先頭アドレスを返します。buffer(引数で与えるメモ帳に書き込む文字列)は100byte、datafile(ファイル名を格納しておく変数)は20byteそれぞれ確保されていますね。今まで変数宣言したときに、確保されるスタックセグメントとは、違った領域にアドレスが設定されています。

まずはそれを確認してみます。

Screen Shot 2013-04-11 at 14.15.06

上の画像が、変数宣言を終えてすぐのそれぞれの変数のアドレスと、その中身。

スタックセグメントの規則通り、最初に宣言されているものから順番に上に詰まれていっているのがわかります。

次はmalloc()でbufferとdatafileに領域を確保した後

Screen Shot 2013-04-11 at 14.22.06

 

bufferとdatafileの中にアドレスが格納されているのがわかります。このアドレスに飛んで中身を見てみます。

Screen Shot 2013-04-11 at 14.24.16

 

bufferの後ろに4byte多く確保され(計104byte)、その後ろにdarafileの領域が確保されていることがわかります。bufferの中身を見てみます。

Screen Shot 2013-04-11 at 14.23.43

 

中身がほとんど0なのは、偶然です。(必ず0になるわけではないということ)必要があれば明示的に初期化する必要があります。datafileのほうは、strcpy()関数で初期化されているので、無論その値が入ります。

Screen Shot 2013-04-11 at 14.32.54

では104byte確保されている変数bufferの中に104byteの文字をつっこんでみます。

Screen Shot 2013-04-16 at 12.49.44

前々回でも利用したように、perlスクリプトを利用して、Aを104文字出力させています。この状態で変数buffer, datafileの中身を確認してみます。

まずはbuffer

Screen Shot 2013-04-16 at 12.53.08

16byteずつ表示しているのが6列と8byte、計104byte、A(asciiコードで0x41)という文字が格納されているのがわかります。この状態で次はdatafileを見てみます。

Screen Shot 2013-04-16 at 12.56.39

 

中身は空っぽです。bufferには確保されている104byteの文字を格納しただけで、先ほど通常に動作させた場合のdatafileに格納されていた”./memo_app_data”という文字列はありません。

これは文字列の最後をnullで判定しているためです。bufferが104byte確保されている場合、104byteの文字列を代入すると、そこには104byteの文字列+1byteのnull文字の計105byteが変数bufferの先頭アドレスから格納されていきます。

なので、bufferから105byte目の変数datafileのアドレスはbufferの末尾に付加されたnullを指している、ということになります。だからこの場合、datafileの中身は空っぽになってます。

今度はbufferの中に104byte以上の文字列を挿入してみます。

Screen Shot 2013-04-16 at 14.27.47

これで104byteの”A”に”komaifile”という文字を追加したものをbufferに入れてみます。

Screen Shot 2013-04-16 at 14.50.32

bufferの中身をみてみると、引数に渡した文字列全てが入っており、datafileには、105byte以降の文字が入っています。

このことから、105byte以降には、任意の文字列を指定することができます。今回、このアプリケーションはメモ帳アプリなので、datafileの中身は、メモを書き込むファイルだということが推測されます。ここで大切なのは、SUIDが設定されているので、root権限でmemo_app_dataというファイルに書き込みが行われている、ということです。

つまり、datafileの文字列を任意に指定できる、ということは、任意のファイルにroot権限で書き込みができる可能性がある、ということになります。

 

ただし、制約もあります。書き込む対象ファイルがdatafileに入ってるのと同じように、書き込む文字列はbufferに入っています。

ということは、上記の場合Aが104文字にkomaifileが追加された文字列が、ファイルkomaifileに書き込むことができる、ということです。任意のファイルに書き込むことはできますが、書き込む文字列は任意にできるのは104byteまで、末尾にはファイル名がついてしまう、という制約が、この場合は確実につきます。

 

/etc/passwdを指定して任意のユーザを追加してみます。

まずは/etc/passwdの書式を確認してみましょう。

Screen Shot 2013-04-16 at 16.36.04

検証用にローカルに立てているサーバなので、公開しても問題ないものです。

1行に一人のユーザの情報が格納されていて、書式は

ユーザ名:パスワードハッシュ:ユーザID:グループID:メモ的な何か:ホームディレクトリ:デフォルトで仕様するシェル

というようになっています。パスワードが共通してXになっているのはシャドウファイルが利用されているということです。

シャドウファイルが利用されているパスワードは、/etc/shadowに別途暗号化され格納されていますが、ここに直接パスワードハッシュを格納することもできます。よって

hacker:パスワードハッシュ:0:0:hogehoge…..:/root:/bin/bash

という1行をこのファイルに挿入することができれば、root権限を保持したhackerというユーザをシステムに追加することができるようになる、ということです。

/etc/passwdと/etc/shadowに関して、こちらに詳しく書かれています⇨参考リンク

 

次はパスワードハッシュをどのようにして得るか、ということが問題になります。パスワードハッシュは、DES,MD5,SHA-256, SHA-512のなかから選択することができます。ただし、DESは本来使うべきではありません。(強度が他に比べて極端に低いので)僕が普段利用している公開サーバも、デフォルトでSHA-512が利用されています。

ちなみに、/etc/shadowを見ると、それぞれのユーザのパスワードが、どの方式でハッシュ値得ているか、がわかります。

  1. $1$で始まるパスワード⇨MD5 ($1$から$まではsalt)
  2. $5$で始まるパスワード⇨SHA-256 ($1$から$まではsalt)
  3. $6$で始まるパスワード⇨SHA-512 ($1$から$まではsalt)
  4. 何もついていないもの⇨DES (頭二文字がsalt)

となっています。

さきほどあげた参考リンクにもありますが、DESとMD5はperlのcrypt()関数で簡単に得ることができます。

コマンドラインから実行して

一つ目の引数がハッシュ値を得たい、平文のパスワード

二つ目がsaltの指定

になっています。シェルから$ perl -e “print crypt(‘planetext’, ‘salt’)”で呼び出すと便利です。

これで平文:password、salt:AAでDES暗号にしたものがこちらです

Screen Shot 2013-04-19 at 12.07.46

これで、情報が揃いました。

hacker:AA6tOYSfGxd/A:0:0:hogehoge…..:/root:/bin/bash

これでroot権限を持ったログインID:hacker、パスワード:passwordというユーザの情報が作れました。これを/etc/passwdに追加できれば成功です。

ただ、先ほど挙げたように、今回BOFさせる文字列には制限があります。文字列の最後が/etc/passwdになっていなければ、/etc/passwdに追加できない、ということです。つまり先ほどのユーザ情報の文字列は

hacker:AA6tOYSfGxd/A:0:0:hogehoge…..:……/etc/passwd

となっていなければなりません。しかしそれだと、最後の項目がユーザのシェル、というユーザ情報が壊れてしまいます。

 

そこで、末尾に/etc/passwdで終わっても、シェルへのパスを指すように工夫します。ここではシンボリックリンクを利用します。通常、利用されるシェルがbashの場合はbashのパス表示します。

Screen Shot 2013-04-20 at 20.33.46

まぁだいたいは/bin/bashだと思います。このファイルに対してシンボリックリンクを作成します。

Screen Shot 2013-04-20 at 20.39.13

こんな感じに、/tmp/etc/passwdファイルを作成して、/bin/bashへのシンボリックリンクにします。

こうすることで、/tmp/etc/passwdがシェルへのリンクになりました。

読ませるファイルは/etc/passwd

バッシュへのパスは/tmp/etc/passwd

これで先ほどの制約をクリアできる形になりました。

よって

hacker:AA6tOYSfGxd/A:0:0:hogehoge(ここで文字数調整):/root:/tmp(ここまでで104文字)/etc/passwd

の1行をメモ帳プログラムに引数として渡せば、ログインID:hacker、パスワード:passwordのroot権限をもったユーザが追加されることになります。文字数を上手に調整して、引数に渡します

Screen Shot 2013-04-20 at 21.17.13

 

これを実行した後に、/etc/passwdの中身を見てみます

Screen Shot 2013-04-20 at 21.26.46

 

/etc/passwdの末尾に先ほどの文字列が挿入されていますね。これが手元で再現したときは感動大きかったです(笑)

そのまま先ほど追加した行に書いたログインIDでログインできていることも、root権限になっていることも確認できると思います。

 

 

こんな感じでした。perlコマンドの-eオプションは汎用性非常に高いですね。ここまででBOF再現は以上です。更に詳しい解説だったり、他の手法もいくつか参考書には掲載されているので興味があるかたは是非読んでみて下さい。

 

 

 

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です