これまでに解いた問題: https://hackmd.io/@Xornet/BkemeSAhU
シェルを模したプログラムでファイルに関係するコマンドはだいたい出来る
ファイルやディレクトリの作成時にnode構造体, 名前の為の領域が固定サイズのmallocで確保される。また、ファイルへの書き込みの際には可変サイズでmallocが起こり、ファイル内容のためのバッファが確保される
削除時にfreeが起こるが、ディレクトリの場合はnode構造体のみが、ファイルの場合は内容を書き込んだ場合はnodeに加えてバッファもfreeされる。
また、親ディレクトリからもunlinkされ、この際にポインタが削除されることから普通はDouble FreeやUAFは出来ない
のだが、別ディレクトリにあるファイルを削除する時に限り、ファイル内容のバッファを指すポインタが生き残る。以降も削除, 編集が出来るのでDouble Free, UAFになる
ここまで行くとHeap領域のコントロールが面倒なだけの問題であるが、肝心のshow機能が存在しない(catコマンドがあるが"実装してないよ"って言われる)
では、ファイル名やディレクトリ名のある領域にlibcのアドレスを出現させてそれをlsコマンドで読もうというのが考えられるがASCII文字列として無効なバイトが混じっていると即exitする
ではshowless leakということでstdoutやstderrをPartial Overwriteで弄って強引に出現させようとするが、全ての出力がwrite関数で行われており、そもそもstdout, stderrは使えない
最早絶望的だが実はpwdコマンドだけASCIIチェックが無い。これを利用してカレントディレクトリの文字列が入っているところにUnsorted Binの切り出しでlibcのアドレスを出現させる。
後はDouble Freeが普通にあるのでtcache poisoningでシェル起動アドレスを__free_hook
に放り込む
今回使う定数や構造体の定義は次の通り
mkdirコマンドが呼ばれるとディレクトリ名のチェック等を経てからmk関数が呼ばれる。重要な部分だけを抜粋すると次のようになっている
node構造体を新しく作成し、名前用の領域も確保している。type
にはDIR
が格納される
mkdirコマンドのファイル版で、type
にFIL
が入るところ以外はだいたい同じ
機能の大部分はrmコマンドのサブモジュールであるsub_rm
関数とunlink_child
関数で行われている
コメントのdouble free??
の部分に脆弱性がある(元からあったわけではなく私が追記したものです)
カレントディレクトリに無いファイルを相対パス指定で削除する際にunlink_child(target)
が発生しない。よってfreeして以降もファイルが生き残っていることからDouble Freeが存在する。
更に後述するechoコマンドでUAF(write)も出来る
echo自体は特に面白いことはない、重要なのはリダイレクトをする際に呼ばれるwrite2file
関数である
これは指定したファイルのバッファにバイト列を書き込んでくれる
target->buf
がnullなら新しく確保する。この際arg>
というプロンプトが表示された後にsysbuf
に書き込んだサイズだけmallocで要求される
sysbuf
からtarget->buf
への書き込み時には\r
に遭遇するまで書き込まれる。
よって単にmallocだけをしたい時は先頭に\r
を置いたバイト列を書き込むようにすればいい、但しnodeのsizeメンバは0になる
既にtarget->buf
が存在する場合は書き込みたいサイズとtarget->size
の大きさが比較され、前者の方が小さい場合は別のバッファが確保される。
そしてmemcpyで内容を書き込む
カレントディレクトリを変える
カレントディレクトリを表示する。次で説明するascii_check
が唯一無い表示コマンドである
これを見れば分かる通り、printableで無い文字列と大半の記号を弾いている。大半の表示系コマンドで使われており、これによって例えばファイル名のあるチャンクにlibc中のアドレスを降らせてlsコマンド(ここで解説していないがある)で読むことは叶わない(先頭から0x7fなので)。
はい
以上のコマンド群を利用してとりあえずlibc leakする、というかこれが出来れば9割クリアしたようなものである
方針としてはまともなshow機能が無いのでHeap Leakは諦め、UAF(write)を利用したPartial Overwriteでチャンクのサイズを一発でUnsorted Binに送ることが出来るように書き換える。
続いて下のチャンクを整えるついでに大きなチャンク内にどこかのディレクトリの名前が入るチャンクをオーバーラップさせる。
オーバーラップしたチャンクに入っている名前のディレクトリに移動し、Unsorted Binからの切り出しを発生させて名前の部分にmain_arenaのtopに相当するアドレスを出現させ、それをpwdコマンドで読んでlibc leakする
操作するポインタは出来るだけファイルの内容のバッファにする。自由な書き込みが出来る上にサイズも融通が効く。
下記コードで最初にnodeやname分のチャンクを作ってtcacheに放り込んでいるのはHeap上でファイルバッファが出来るだけ連続するようにしているからである。
まずはtcacheのリンクリストを書き換えてチャンクのサイズヘッダを書き換える。Heapのアドレスは当然知らないのでサイズの部分とデータが入る部分が0x10しか異ならないことを利用してPartial Overwriteをする。
これでA -> B
だったリンクリストをA -> B - 0x10
のようにし、echoコマンドでBのサイズをUnsorted Binに入るような大きさにする(今回は0x420)、またUnsorted Binに入れる際のチェックが無駄に走らないようにPREV_INUSEフラグは立てたままにする
この状態でBをfreeすればUnsorted Binに放り込まれようとするのだが、もちろん下のチャンクを整えていないのでabortする。というわけで再びファイルを作って偽装チャンクを作るようなechoコマンドを発生させる。
また、Unsorted Binの切り出しでlibc中のアドレスがカレントディレクトリの名前に降ってきて欲しいので大きくしたBのチャンクに中にあるディレクトリの名前が来るようにし、その上でそのディレクトリ上でコマンドを叩くようにする。
ここが1番大変で、というのも上手くtcacheを操作しないとカレントディレクトリの名前の上のチャンクがそのディレクトリのnodeになってしまい、下手に切り出したり下手にechoすることでカレントディレクトリのnodeが壊れてしまいSIGSEGVを連発する(しました)
結局最終的に次のような配置を強引に作ってからBをfreeした
この状態でまた適当なファイルを作成し、サイズを上手いこと調整してY->name
にmain_arenaのtopに対応するアドレスが降ってくるようにする。
ここも鬼門でファイル作成時に必ず発生する固定サイズのmalloc(0xb0, 0x30のチャンクが作られる)が面倒なので適当にtcacheにこれらのサイズを退避させておいてUnsorted Binから切り出されないようにする。
そしてファイルのバッファ作成時にサイズを指定出来るのでここでカレントディレクトリの名前部分にアドレスを降らせる。
この際、node Yのデータが上書きされて死なないようにechoで\r
を先頭に送り込んでmallocだけが行われるようにしておく
libc leakだけなのに長かった。
あとはrmで他のディレクトリにあるファイルを利用してDouble Free出来るのでtcache poisoningで__free_hook
にシェル起動アドレスを放り込むだけである。
"/bin/sh"
を叩き込むのが面倒だったので「お願い!!One Gadget!!」したら通った、良かった
当日は解けませんでしたが、まだ鯖が生きていたので解きました
TSGCTF{beer_is_delicious_if_you_dont_taste_it_6592867821310}
想定解がHouse of Corrosionなところまでは当日わかりましたが(show機能が貧弱過ぎるので)、理解と実装が追いつかなくて当日解けませんでした。
そういうわけで、pwdにASCIIチェックが無いことを利用した非想定解の方で解きましたがこちらもかなり大変でした
create時に2つのmallocが走る上に自由な書き込みが出来るmallocは更にもう一段階欲しいせいでHeapの構造がグッッッッッッチャグチャになって発狂してました(実際昨日はそのせいでやる気が死滅した)
TSGCTF 2020で自分が触った問題の復習が無事に終わったので次からはまた別の問題探しに勤しみます。2.23の環境が無事に用意できたので最新の問題の内2.23で動いているものをやろうかと思っています
また、今回のようにn択のHeap問題ではなく、汎用的なプログラムとHeapの対応を見つけて解くような難しめの問題も探して挑んでみたいです、良問情報がありましたらよろしくお願いします