(Pwn)のWriteupはじめにAlpacaHackはkeymoonとminaminaoが開発したCTFプラットフォームで、現在は個人戦をメインにした定期CTFが開催されています。過去大会の問題にも挑戦できるため、復習にも便利なサイトです。今回の問題も解き直すことができますので、参加を逃した方も挑戦してみてください。solves)問題概要グローバル変数に定義されたint型配列slotに値を1つ入れられます。exit(0);}プログラムにはフラグを出力するwin関数が定義されており、これを呼び出すことが目的です。PIEであることが確認できます。No脆弱性slotにアクセスするインデクスを入力したあと、それが10以上である場合は範囲外参照としてプログラムを終了します。}しかし、indexはint型で定義されているため、負数であるかも検査すべきです。slotよりも負の方向に何があるか、gdbで確認してみましょう。Table)が書き換え可能領域*3にあるのがわかります。ここにはlibcなどの外部ライブラリの関数を呼び出すときに使われる関数ポインタが格納されています。ある関数のGOTをwin関数のアドレスで上書きすると、その関数が呼ばれたときにwin関数が呼び出されます。overwriteと呼びます。overwriteという方針が立ったら、次にどの関数のGOTを上書きするかを決めます。関数が呼び出される必要があるので、GOTを書き換えたあとに呼び出せる関数である必要があります。ソースコードを確認すると、printfとexitが該当します。exit(0);}ではprintfのGOTをwin関数に書き換えることはできるでしょうか?残念ながら今回の問題では使えません。slotはint型の配列だからです。printfはプログラム中ですでに何度か呼ばれているため、libc中のprintf関数のアドレスに解決されています。libcのアドレスは6バイトあるため、int型(4バイト)の書き換え1度では不十分です。exitはまだ呼び出されていないため、アドレスが解決されていません。PIEの場合アドレスは通常3バイトに収まる範囲に置かれます*4。したがって、exitのGOTであればint値の書き込みでもwin関数のアドレスに制御することができます。solves)問題概要グローバルバッファにデータを入れたあと、main関数のローカルバッファにstrcpyかstrcatを使ってコピーできます。両方ともバッファのサイズは0x100なのでstrcpyは問題ありません。strcatはバッファサイズに関係なくデータを付加してしまうため、スタックバッファオーバーフローが発生します。canaryがないので、main関数のリターンアドレスをwin関数のアドレスに書き換えることを目標にしましょう。No攻撃リターンアドレスを書き換える上で2つ問題があります。NULL終端なので6バイトのリターンアドレスを1度に3バイトのwin関数のアドレスにできないfgetsで入力を受け取るため、終端に改行コードが入ってしまう1つめの問題は、何度かstrcatを呼び出すことで解決できます。はじめに適当な長さの文字列でリターンアドレスの後半をNULLバイトを潰し、長さを1ずつ減らしていくことでNULLバイトを後ろから連続して書き込めます。2つめの問題は、fgetsに最大量の入力を入れることで解決できます。fgetsは通常改行コードで終端しますが、「受け付ける入力サイズ-1」の長さを入力として与えると、最後をNULL終端として改行コードが入ることなく入力できます。したがって、まず適度な長さの入力でリターンアドレスの高位ビットをNULL埋めし、次にfgetsに最大量の入力を与えることでwin関数のアドレスを正しく与えられます。solves)問題概要メッセージと名前を入力できるだけのプログラムです。No脆弱性get_name関数を見ると、128バイトのバッファに対してscanfで128バイトのデータを入力しています。scanfで文字列を入力するときは与えられたサイズに関係なくNULL終端を強要するため、NULLバイトが1バイトだけバッファの外に書き込めてしまいます。このような脆弱性をoff-by-nullと呼びます。Programming)今回のプログラムをgdbなどで解析すると、書き換えられるのはget_nameのスタックフレームに保存されたrbpレジスタであることがわかります。ret;命令でリターンしています。leave命令は以下の命令列と等価です。rbpの最下位バイトが0でなかった場合)でmain関数に戻ったときのRSPが壊れます。rbpの最下位バイトが大きかった場合、RSPが本来の場所より大きく低いアドレスにずれることになります。低いアドレスはget_nameのスタックフレームがあった場所であり、そこにはnameバッファがあります。したがって、main関数からリターンする再にget_nameのnameバッファ中のデータをリターンアドレスとして解釈してしまう可能性があり、ROPが可能です。nameバッファの不要な箇所にはret命令のgadgetを敷き詰めておくことで、その周辺のどこからROPが始まってもret命令の部分はスキップされるため、ROPの成功確率が上がります。gadgetでlibcのアドレスをリークをしましょう。ローカル変数を出力する箇所ではrbpが使われるので、get_nameの出力箇所を対象にしました。rbp-0x80のアドレスを引数に渡しているので、リークしたいアドレス+0x80をrbpに設定してここを呼び出せば良いです。overwriteLibcのアドレスをリークしたら再度mainに戻ってROPをしたいところですが、現在のスタックポインタがBSSを指しているため難しいです。printfがかなりの量のスタックを使うため、どうしても読み込み専用領域にスタックポインタが移動してしまいます。overwriteすることを考えます。Libcリークと同様に、scanfの少し前から呼び出すことで自由なアドレスにscanfでデータを書き込むこともできます。想定解のexploitではsetbuf関数のGOTをsystem関数に置き換え、stderrの場所に呼び出したいコマンドを書き込んでいます。これにより、スタックを対象に消費することなくsystem関数を呼び出せます。Exploitコード以下が想定解のexploit例になります。leakできる方法があるためレビュー段階でmediumに下がったようです。writeupをAlpacaHackに投稿してくれている方もいるようなので、簡単な方法については解いてくれた方のwriteupを是非読んでみてください。最後はよくあるnote形式のヒープ問です。noteのサイズとポインタはグローバル変数で管理されています。deleteの4つの機能があります。脆弱性noteのインデクスを取得するとき、以下のようにインデクスを検査しています。idx;}配列に対するsizeofは配列の長さを返すのではなく、データサイズを返すため、範囲外参照が発生します。(本来は>=であるべきところが>になっているのも問題です。)メモリを確認すると、size_listは次のようになっています。shortの2バイトで、このメモリレイアウトではnote_list[0]の下位2バイトを書き換えられてしまいます。反対に、note_list[0]をセットすることでインデクス=16のサイズが壊れます。攻撃まずインデクス=16にサイズ0のnoteを作ります。その後インデクス=0に適当なデータを作ることで、インデクス=16のサイズが壊れ、ヒープバッファオーバーフローが発生します。poisoningをしました。(当然、ポインタを壊す方向でもexploitは可能ですが、オーバーフローの方が簡単なのでこちらを選びました。)Tcacheのリンクリストを破壊したら、stderrに向けることでstderrを破壊し、FSROPによりシェルを取っています。ブログを報告する引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。