問題
SSHの情報が記載されているので、とりあえずSSHで接続する。
接続するとflag.txt
とq4
という実行ファイルが用意されている。
もちろんflag.txt
から直接フラグを取ることはできない。
解法
q4
を実行してみると名前を聞かれ、その後「no」を入力するまで同じ質問が繰り返される。
実行するだけでは何も分からないので、ここからはローカルに落として解析してみる。
まずはstringsコマンドを実行する……が特に情報を得られず。
次にGhidraでデコンパイルしてみる。ちなみにGhidraを使うのは初めてなので使い方はよく分かっていない('ω')
とりあえずGhidraで開き、解析結果のmain関数を確認すると以下のようになっていた。
解析結果によると、上図のlocal_18
が0
の場合にフラグを出力するっぽい。
しかし、local_18
の値は常に1
であるため、普通に実行してもフラグを取得することはできない。
バッファオーバーフローでもするのかな?と考えたが、違うっぽい。
ここで手詰まりになったので、Write-upを少しだけ見てヒントを確認した。
どうやらこの問題では書式文字列攻撃(フォーマット文字列攻撃)を利用するらしい。
書式文字列攻撃についての知識は全くなかったが、ハリネズミ本で紹介されていることに気づいたので、この本を参考に攻撃を行ってみた。
printfに直接文字列を入力できる場合に発生する脆弱性らしい。
とりあえずAAAA %p %p %p %p %p %p %p
を引数に入れて実行すると、以下のような結果になった。
%pは対応するスタックの値を16進数で出力している。
よく見ると6番目の%pの箇所に0x41414141
がされており、これはASCIIコードの「AAAA」を16進数で表した値になっている。
つまり、最初の引数で入力した「AAAA」(ローカル変数の値)が6番目のスタックに存在していることが分かる。
次に、char*型で指定されたアドレスの文字列を出力する「%s」を使ってみる。
また、%6$s
と書くことで6番目の値を%sで表示させている。
上記のように入力した場合、0x41414141番地に存在する文字列を出力しようとしているため、「Segmentation fault」が発生している。
つまり、第一引数に読み込みたいアドレス値を指定することで、その中身を読み出すことができる。
ここまでは任意アドレスの読み込みだったが、「%n」を使うことで任意のアドレスへ書き込みことも可能。
この脆弱性を使って、↑のmain関数の画像の15行目にある
putchar(10);
のputchar関数のアドレスを、18行目以降のif文内の処理のアドレスに書き換えることで、19行目のフラグを取得する処理を実行することができそう。
Ghidraの解析結果を確認すると、Global Offsets Table (GOT)にputchar関数のアドレスが記述されていた。
ここでは080499e0
になっている。
書き込み先のアドレスは、if文(JNZ命令?)以降の適当なアドレスを指定する。
ここでは08048691
を指定してみる。
これらをまとめると、以下のような攻撃値になる。
最初の\xe0\x99\x04\x08
でputchar関数のアドレスを指定している。リトルエンディアンで記述する必要があるので注意。
次の%134514317x
は、書き込み先のアドレスである「080499e0」を10進数に変換(134514321)し、さらに-4した値になっている。先頭にアドレス指定用の4バイトがあるため、-4する必要があるらしい。
あとは%6$n
と書くことで、\xe0\x99\x04\x08
のアドレスの内容を%134514317x
の内容に書き換えるようにする。
上記のコマンドを実行すると、見事フラグが出力された。
補足
これは厳密には「GOT Overwrite」という攻撃らしい。
RELROが「No RELRO」もしくは「Partial RELRO」の場合に攻撃可能とのこと。
セキュリティ機構のチェックはchecksecで行った。
所感
なんとなくは理解したが難しい。。
書式文字列攻撃だけではなく、フォーマット指定子についても全然知識がなかったので結構時間がかかった。
今回はwrite-upを少しだけ読んでしまったが、とりあえず初pwn!