これまでに解いた問題: https://hackmd.io/@Xornet/BkemeSAhU
部分的なshow機能とより部分的なedit機能があるHeap問題、普通のHeap問題と違うのはmallocではなくreallocを使うことと、bssセクションにある変数に0でない値を入れて特定メニューを選択するとシェルが起動することである
free後にポインタを破棄しないため、UAFがあるが、チャンク指定がインデックスでは無く、bk, keyの位置に対応するidで行われるのでtcacheでこれをするにはまず、Heap leakをしなくてはならない。そのためにfastbinを利用してHeap leakをする
今回は任意の値の書き込みが実質bkにしか出来ないのでsmallbinの末端チャンクのbkを書き換えてから目標のアドレスをsmallbinに繋ぐ
次にsmallbinからmallocされる時にtcacheにチャンク群が入るのだが、この時のunlinkでチェックをすり抜けた上に良い感じに目標アドレスに値が入ってくれるのでここでシェル起動メニューを選択すればシェルが起動する
このレベルのCTFでPIE無効だと逆に不穏さを感じる(実際生半可な問題では無かった)
authorized
変数が0でなければシェルを起動するallocate時には次のような構造体が作られる
sizeは実際のチャンクサイズではなく、ユーザーがコマンドで指定した値である。
.bssセクションの構造だが次のようになっている
今回はname + 0x10
の部分をsmallbinにまず繋ぐ、ここで名前入力時にnameにbkを偽装したチャンクを作る
name + 0x18
がname + 0x10
をチャンクとみなした際のbkになるのでここにauthorized - 0x10
を入れておくとsmallbinは(smallbin top ->) ... -> name + 0x10 -> authorized
のようになってくれる
ここで下記に示すtcache stashing unlink attackを行うことでname + 0x10
までのチャンクがtcacheに入るようにする。その際のunlinkでauthorizedのfdにはmain_arenaのbins[size]
に対応するアドレスが入ってくれる
指定したチャンクのサイズを変更する関数で、この問題では何故かこいつが使われている
今回は(殆どの場合)出来ないが、サイズを小さくする際はチャンクを分割し、サイズヘッダを書き換える(分割されたチャンクのサイズが0x20を下回る場合は別の処理が走る)
大きくする場合は下が空いている場合はそこから削り出し、空いていない場合は該当チャンクをfreeしてから別のチャンクを取ってきてポインタを返す。よって部分的にfreeもmallocも出来る
まずはheap leakをする。free時にポインタは破棄されずUAFがあるので簡単に出来るかと思いきや、tcacheはkeyメンバによってidを潰してしまうのでheap leakをするためにheap leakが必要になってしまう
そこでfastbinを使う。tcacheを全部埋めてからfreeしてfastbinに送ればidは生きているのでshowでsizeに相当する部分でfdが開示されHeap leakが出来る
smallbinからチャンクを取得する際に、tcacheに空きがあるならbkを順に辿っていくことでtcacheに格納される。
この時smallbinがFIFOなのに対してtcacheがLIFOなのでsmallbinに入っていた時とは逆の順番で格納される
なお、この際にsmallbinからunlinkされるのだが、通常のunlinkとは異なり、victim->bk->fd == victim
のチェックが走らない。よってsmallbin中のあるチャンクのbkを変な値にしたところで怒られは発生しない(但し、実際はELFが関与できないアドレスで怒られが発生するのでそういう意味で問題無いアドレスにしておく必要がある)
このチェックの無さを悪用すればtcacheに任意アドレスを繋ぐことが出来る。具体的にはsmallbin中のbkの値を書き換える。
但し、tcacheへの格納終了条件が
bin->bk == bin
になる)であり、bkを書き換えると2. の条件を満たすことは無い。よってタイミング良くtcacheが満杯になるタイミングでname + 0x10
をtcacheに繋ぐ必要がある。この際のunlinkで良い感じにauthorized
に値が入ってくれる
上記の攻撃を実現するにはchange_id
コマンドを実行する。このためには該当するチャンクのidを知る必要があるが、smallbinの末端のチャンクが対象であるので対応するbinのアドレスが必要である(main_arenaのbins[size]
)。
binの先頭にあるチャンクのfdにはbinのアドレスがあるのでここを読みたいのだが、それにもIDが必要になる。そこでleakしたHeapのアドレスが使われる
binの先頭にあるチャンクのbkは次に確保されるチャンクであるのでHeap leakが済んでいればそれを利用してidの特定が出来る。これでmain_arena中のbins[size]
のアドレスをリークし、change_idで末端のチャンクのbkをauthorizedがsmallbin中で次に確保されるように書き換える、具体的にはnameの位置を指定する。
すると事前にname
を上手く構成しておいたのでsmallbinは(smallbin top ->) A -> B -> C -> D -> E -> F -> name + 0x10 -> authorized
のようになる。
これで上記のstashing unlinkが行われた時にname + 0x10
までがtcacheに入り、authorized
のfdにbins[size]
のアドレスが入る
どのidをどうやって入手するのかがやや複雑
change_id
で書き換える当日は別のチームメイトが提出しましたが鯖が生きていたのでやりました
TSGCTF{Realloc_is_all_you_need~}
(フラグ見て思い出したけどそういえばextend使って無い…)
TSG CTF 2020 - Detectiveもそうでしたが普通のHeap問題にありがちなhookにシェル起動アドレスを放り込むといった問題が少なくて典型問題に慣れているだけでは解けない良問が揃っていました
今回は特にインデックスではなくidで対象のチャンクを選ぶというシステムだったのでfree後に各binに入った際にfd, bk(tcacheの場合はnext, key)がどうなるかを把握している必要があり、良い復習になりました
tcache stashing unlink attackのようなtcacheを優先して使わせるという仕様を利用した問題もこれが初めてで非常に面白かったです。
(追記)
tcache stashing unlink attackの解説書きました: tcache stashing unlink attack